File size: 2,326 Bytes
645673f
d5ba3a3
 
 
 
645673f
 
 
 
 
 
 
d5ba3a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e813ce3
 
 
 
 
 
 
 
 
 
 
 
d5ba3a3
 
 
645673f
 
 
 
e813ce3
d5ba3a3
645673f
d5ba3a3
645673f
d5ba3a3
645673f
d5ba3a3
 
 
e813ce3
 
 
 
 
 
 
 
 
 
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
import re
from typing import Literal

from pydantic import BaseModel, Field

_HEADER_RE = re.compile(r"^#{1,4}\s+.*$", re.MULTILINE)


def _strip_headers(text: str) -> str:
    """Remove any markdown headers the LLM embedded in a field value."""
    return _HEADER_RE.sub("", text).strip()


class InvestmentVerdict(BaseModel):
    """Structured analyst verdict returned by the Senior Broker LLM."""

    quantitative_base: str = Field(
        description="Price vs calculated valuation, margin of safety math"
    )
    lynch_pitch: str = Field(
        description="What insiders are doing + the one catalyst"
    )
    munger_invert: str = Field(
        description="How an investor could lose money, the bear evidence"
    )
    verdict: Literal["STRONG BUY", "BUY", "WATCH", "AVOID"]
    bottom_line: str = Field(
        description="One-sentence final summary"
    )
    position_size: float = Field(
        default=0.0,
        description="Kelly-derived position size as % of portfolio (set post-LLM)",
    )
    kelly_win_rate: float = Field(
        default=0.0,
        description="Historical win rate used for Kelly calc (set post-LLM)",
    )
    kelly_total_trades: int = Field(
        default=0,
        description="Number of trades used for Kelly calc (set post-LLM)",
    )

    def to_report(self) -> str:
        """Render as the markdown report format the pipeline expects."""
        qb = _strip_headers(self.quantitative_base)
        lp = _strip_headers(self.lynch_pitch)
        mi = _strip_headers(self.munger_invert)

        report = (
            f"### THE QUANTITATIVE BASE (Graham / Asset Play)\n"
            f"{qb}\n\n"
            f"### THE LYNCH PITCH (Why I would own this)\n"
            f"{lp}\n\n"
            f"### THE MUNGER INVERT (How I could lose money)\n"
            f"{mi}\n\n"
            f"### FINAL VERDICT\n"
            f"**{self.verdict}** — {self.bottom_line}"
        )

        if self.position_size > 0:
            report += (
                f"\n\n### POSITION SIZING (Kelly Criterion)\n"
                f"**Recommended allocation: {self.position_size:.1f}% of portfolio**\n"
                f"Based on historical win rate of {self.kelly_win_rate * 100:.0f}% "
                f"across {self.kelly_total_trades} trades."
            )

        return report