File size: 6,746 Bytes
17edfa9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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()