# The explanation diagrams: genome.svg (what a strategy is and how two
# breed) and guards.svg (the three checks against luck). No data in them.
import sys
out = sys.argv[1] if len(sys.argv) > 1 else "."
SANS = "Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif"
MONO = "JetBrains Mono, ui-monospace, Menlo, monospace"
BG="#0b0c10"; CARD="#111319"; LINE="#262a33"; INK="#e9eaee"; DIM="#9a9ca8"; FAINT="#656874"
ACC="#7c84ff"; ACC2="#aeb3ff"; UP="#2ebd85"; DOWN="#f0616d"; WARN="#e8a849"
A_FILL="#171a33"; A_LINE="#3a4180"; B_FILL="#2a2414"; B_LINE="#5a4a22"
def svg(w,h,body): return f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" width="{w}" height="{h}" font-family="{SANS}">\n<rect width="{w}" height="{h}" rx="14" fill="{BG}"/>\n{body}\n</svg>\n'
def t(x,y,s,size=14,fill=INK,anchor="start",weight=400,font=None):
    f = f' font-family="{font}"' if font else ""
    return f'<text x="{x}" y="{y}" font-size="{size}" fill="{fill}" text-anchor="{anchor}" font-weight="{weight}"{f}>{s}</text>'
def box(x,y,w,h,fill,stroke,rx=8,extra=""): return f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{rx}" fill="{fill}" stroke="{stroke}" {extra}/>'

# 1. The genome
W=960
NAMES=["score","lookback","skip","universe","long","short","rebalance","weighting"]
A=["momentum","7","0","25","20%","20%","7 days","equal"]
B=["momentum","30","3","40","30%","10%","3 days","inv. vol"]
C=[("momentum","A"),("30","B"),("0","A"),("25","A"),("30%","B"),("20%","A"),("14 days","M"),("inv. vol","B")]
x0,gw,gg=130,96,6
b=[t(40,42,"A strategy is a genome of eight genes",18,INK,weight=600),
   t(40,66,"Three genes pick and tune the Flowscript score; five say how the book is built from it.",13,DIM)]
for i,n in enumerate(NAMES):
    x=x0+i*(gw+gg)
    b.append(t(x+gw/2,104,n,11,ACC2 if i<3 else DIM,"middle",font=MONO))
b.append(f'<line x1="{x0}" x2="{x0+3*(gw+gg)-gg}" y1="112" y2="112" stroke="{ACC}" stroke-width="2"/>')
b.append(f'<line x1="{x0+3*(gw+gg)}" x2="{x0+8*(gw+gg)-gg}" y1="112" y2="112" stroke="{FAINT}" stroke-width="2"/>')
def row(y,label,vals,src):
    r=[t(40,y+27,label,14,INK,weight=600)]
    for i,v in enumerate(vals):
        x=x0+i*(gw+gg); s=src[i]
        fill,stroke=(A_FILL,A_LINE) if s=="A" else (B_FILL,B_LINE) if s=="B" else (A_FILL,DOWN)
        r.append(box(x,y,gw,42,fill,stroke,extra=f'stroke-width="{3 if s=="M" else 1}"'))
        r.append(t(x+gw/2,y+26,v,14,INK,"middle",font=MONO))
    return r
b+=row(126,"Parent A",A,"A"*8)
b+=row(180,"Parent B",B,"B"*8)
b.append(f'<line x1="40" x2="{W-40}" y1="240" y2="240" stroke="{LINE}"/>')
b+=row(256,"Child",[v for v,_ in C],[s for _,s in C])
lx=x0
for fill,stroke,lab in [(A_FILL,A_LINE,"from parent A"),(B_FILL,B_LINE,"from parent B"),(A_FILL,DOWN,"mutated")]:
    b.append(box(lx,320,16,16,fill,stroke,rx=4,extra=f'stroke-width="{2 if stroke==DOWN else 1}"'))
    b.append(t(lx+24,333,lab,12,DIM)); lx+=150
steps=["40 genomes","score each on the training span only","keep the best 2","breed the rest from tournaments of 3"]
x=40; y=370
for i,s in enumerate(steps):
    w=len(s)*7.4+28
    b.append(box(x,y,w,34,CARD,LINE,rx=17)); b.append(t(x+w/2,y+22,s,13,INK,"middle"))
    x+=w
    if i<len(steps)-1: b.append(t(x+11,y+23,"→",16,FAINT,"middle")); x+=22
b.append(t(40,y+62,"↺ repeat for 25 generations, then score the best once on the holdout",13,ACC2,weight=600))
open(f"{out}/genome.svg","w").write(svg(W,452,"\n".join(b)))

# 2. The guards
W=960
b=[t(40,42,"Three ways the search tries to prove its best is luck",18,INK,weight=600),
   t(40,66,"A genetic search is built to find the best-looking backtest. These checks ask whether it is any good.",13,DIM)]
tx0,tx1=40,W-40; cut=tx0+(tx1-tx0)*0.7
b.append(box(tx0,92,cut-tx0-3,48,A_FILL,A_LINE)); b.append(box(cut+3,92,tx1-cut-3,48,B_FILL,B_LINE))
b.append(t(tx0+16,121,"Training span: every generation is scored here",14,ACC2,weight=600))
b.append(t(cut+19,121,"Holdout: never seen",14,"#e8c77a",weight=600))
b.append(t(tx0+16,160,"the search ranks candidates by Sharpe ratio on these days only",12,DIM))
b.append(t(cut+19,160,"the pick is scored here once",12,DIM))
cw=(W-80-2*20)/3; cy=186; chh=168
cards=[("1. The holdout",["The last 30% of the window is kept","from the search. A strategy that","learned something should still","make money there; one that","memorised noise usually doesn't."]),
       ("2. Random and stale twins",["The same book is run 16 times","ranking markets at random, and 4","times on the score as it stood 7,","14, 28 and 56 days earlier. If they","do as well, the score isn't working."]),
       ("3. Deflated Sharpe ratio",["The best of hundreds of tries","scores well by luck alone. The","deflated Sharpe raises the bar for","every distinct setting tried, and","gives the odds the edge is real."])]
for k,(h,ls) in enumerate(cards):
    x=40+k*(cw+20)
    b.append(box(x,cy,cw,chh,CARD,LINE,rx=12))
    b.append(t(x+18,cy+32,h,15,INK,weight=600))
    for j,l in enumerate(ls): b.append(t(x+18,cy+58+j*19,l,12.5,DIM))
open(f"{out}/guards.svg","w").write(svg(W,cy+chh+24,"\n".join(b)))
