BetaTwins / app.py
jashii78's picture
Create app.py
17edfa9 verified
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()