| import gradio as gr |
|
|
| CUSTOM_CSS = """ |
| body, .gradio-container { |
| background: linear-gradient(180deg, #08101e 0%, #0b1020 100%); |
| color: white !important; |
| font-family: Inter, Arial, sans-serif; |
| } |
| .hero { |
| padding: 24px; |
| border: 1px solid rgba(255,255,255,0.08); |
| border-radius: 20px; |
| background: rgba(255,255,255,0.03); |
| margin-bottom: 16px; |
| } |
| .metric { |
| padding: 16px; |
| border-radius: 16px; |
| background: rgba(255,255,255,0.03); |
| border: 1px solid rgba(255,255,255,0.08); |
| } |
| """ |
|
|
| def analyze_thesis(ticker, direction, horizon, position_size, thesis): |
| thesis_lower = thesis.lower() |
|
|
| thesis_score = 60 |
| evidence_score = 55 |
| risk_score = 45 |
|
|
| if "valuation" in thesis_lower: |
| thesis_score += 8 |
| evidence_score += 8 |
| if "risk" in thesis_lower: |
| thesis_score += 6 |
| if "earnings" in thesis_lower or "catalyst" in thesis_lower: |
| evidence_score += 8 |
| if "guaranteed" in thesis_lower or "100%" in thesis_lower: |
| risk_score += 20 |
| thesis_score -= 10 |
|
|
| if position_size > 20: |
| risk_score += 15 |
|
|
| thesis_score = max(0, min(100, thesis_score)) |
| evidence_score = max(0, min(100, evidence_score)) |
| risk_score = max(0, min(100, risk_score)) |
| capital_readiness = max(0, min(100, int(thesis_score * 0.5 + evidence_score * 0.3 + (100 - risk_score) * 0.2))) |
|
|
| contradictions = [] |
| if direction == "Bullish" and "overvalued" in thesis_lower: |
| contradictions.append("Bullish view conflicts with 'overvalued' wording.") |
| if direction == "Bearish" and "undervalued" in thesis_lower: |
| contradictions.append("Bearish view conflicts with 'undervalued' wording.") |
| if not contradictions: |
| contradictions.append("No major contradiction detected.") |
|
|
| missing = [] |
| if "valuation" not in thesis_lower: |
| missing.append("Valuation context missing.") |
| if "risk" not in thesis_lower: |
| missing.append("Risk definition missing.") |
| if "stop loss" not in thesis_lower and "invalidation" not in thesis_lower: |
| missing.append("Exit or invalidation plan missing.") |
| if "macro" not in thesis_lower: |
| missing.append("Macro sensitivity not discussed.") |
|
|
| counter_case = [] |
| if direction == "Bullish": |
| counter_case = [ |
| "Positive expectations may already be priced in.", |
| "Weak guidance can break the thesis quickly.", |
| "Position sizing may be too aggressive for current evidence." |
| ] |
| else: |
| counter_case = [ |
| "Negative sentiment may already be priced in.", |
| "A strong earnings beat can invalidate the bearish view.", |
| "Bear thesis may underestimate business resilience." |
| ] |
|
|
| memo = f""" |
| # BetaTwins Memo |
| |
| **Ticker:** {ticker} |
| **Direction:** {direction} |
| **Horizon:** {horizon} |
| **Position Size:** {position_size}% |
| |
| ## Thesis |
| {thesis} |
| |
| ## Scores |
| - Thesis Score: {thesis_score}/100 |
| - Evidence Score: {evidence_score}/100 |
| - Risk Score: {risk_score}/100 |
| - Capital Readiness: {capital_readiness}/100 |
| |
| ## Contradictions |
| - """ + "\n- ".join(contradictions) + """ |
| |
| ## Missing Factors |
| - """ + "\n- ".join(missing) + """ |
| |
| ## Counter-Case |
| - """ + "\n- ".join(counter_case) |
|
|
| summary = f""" |
| ### Executive Summary |
| **{ticker}** thesis reviewed with a **{direction.lower()}** stance. |
| |
| - Thesis Score: **{thesis_score}** |
| - Evidence Score: **{evidence_score}** |
| - Risk Score: **{risk_score}** |
| - Capital Readiness: **{capital_readiness}** |
| """ |
|
|
| return ( |
| f"<div class='metric'><b>Thesis Score</b><br><span style='font-size:32px'>{thesis_score}</span></div>", |
| f"<div class='metric'><b>Evidence Score</b><br><span style='font-size:32px'>{evidence_score}</span></div>", |
| f"<div class='metric'><b>Risk Score</b><br><span style='font-size:32px'>{risk_score}</span></div>", |
| f"<div class='metric'><b>Capital Readiness</b><br><span style='font-size:32px'>{capital_readiness}</span></div>", |
| summary, |
| "\n".join([f"- {x}" for x in contradictions]), |
| "\n".join([f"- {x}" for x in missing]), |
| "\n".join([f"- {x}" for x in counter_case]), |
| memo |
| ) |
|
|
| with gr.Blocks(css=CUSTOM_CSS, title="BetaTwins AI") as demo: |
| gr.HTML(""" |
| <div class="hero"> |
| <h1>Stress-test every investment thesis before capital is deployed.</h1> |
| <p>BetaTwins helps traders, investors, and fintech teams detect weak reasoning, hidden assumptions, and missing risks before a decision becomes expensive.</p> |
| </div> |
| """) |
|
|
| with gr.Tabs(): |
| with gr.Tab("Analyzer"): |
| with gr.Row(): |
| with gr.Column(scale=4): |
| ticker = gr.Textbox(label="Ticker", placeholder="e.g. NVDA") |
| direction = gr.Dropdown(["Bullish", "Bearish", "Neutral"], value="Bullish", label="Direction") |
| horizon = gr.Dropdown(["Short-Term", "Swing", "Long-Term"], value="Swing", label="Time Horizon") |
| position_size = gr.Slider(1, 100, value=10, step=1, label="Position Size %") |
| thesis = gr.Textbox(label="Investment Thesis", lines=10, placeholder="Write your thesis here...") |
| run_btn = gr.Button("Run Analysis") |
| with gr.Column(scale=6): |
| with gr.Row(): |
| score1 = gr.HTML() |
| score2 = gr.HTML() |
| score3 = gr.HTML() |
| score4 = gr.HTML() |
| with gr.Tabs(): |
| with gr.Tab("Executive Summary"): |
| summary = gr.Markdown() |
| with gr.Tab("Contradictions"): |
| contradictions = gr.Markdown() |
| with gr.Tab("Missing Factors"): |
| missing = gr.Markdown() |
| with gr.Tab("Counter-Case"): |
| counter = gr.Markdown() |
| with gr.Tab("Memo"): |
| memo = gr.Markdown() |
|
|
| run_btn.click( |
| fn=analyze_thesis, |
| inputs=[ticker, direction, horizon, position_size, thesis], |
| outputs=[score1, score2, score3, score4, summary, contradictions, missing, counter, memo] |
| ) |
|
|
| with gr.Tab("About"): |
| gr.Markdown(""" |
| ## BetaTwins AI |
| AI-powered thesis stress testing for smarter investment decisions. |
| |
| ### What it does |
| - Scores thesis quality |
| - Detects contradictions |
| - Finds missing factors |
| - Generates counter-case |
| - Builds memo-style output |
| |
| ### Disclaimer |
| This tool is for research and decision-support only. It is not financial advice. |
| """) |
|
|
| if __name__ == "__main__": |
| demo.launch() |