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() |