// @ts-nocheck
/**
 * Signal-strength position sizing.
 *
 * Currently: fixed 3% risk regardless of score magnitude.
 * Tests: size up on stronger signals, down on weaker.
 *
 * Baseline: L16/S8 cap420 wait30 minSoft25 roundMoat=$25 adaptive-imm
 * Realistic maker-SL model.
 */
import fs from 'node:fs'; import readline from 'node:readline'
type Bar={ts:number;o:number;h:number;l:number;c:number;v?:number}
type Side='long'|'short'
type Cfg={label:string;baseRisk:number;strongExtra:number;strongThresh:number;weakThresh:number;weakRisk:number}
type Trade={gross:number;net:number;month:string;side:Side;bar:number;sizingBps:number;isSl:boolean;isHardSl:boolean;reason:string;hold:number;riskPct:number;score:number}

async function loadBars(files:string[]):Promise<Bar[]>{const seen=new Set<number>(),all:Bar[]=[];for(const f of files){if(!fs.existsSync(f))continue;const rl=readline.createInterface({input:fs.createReadStream(f),crlfDelay:Infinity});for await(const line of rl){if(!line.trim())continue;const b=JSON.parse(line);if(!seen.has(b.ts)){seen.add(b.ts);all.push(b)}}}all.sort((a,b)=>a.ts-b.ts);return all}
function lbRet(b:Bar[],i:number,n:number){return i>=n&&b[i-n].c>0?(b[i].c-b[i-n].c)/b[i-n].c*10000:0}
function closePos(b:Bar){return b.h>b.l?(b.c-b.l)/(b.h-b.l):0.5}
function avgCP(b:Bar[],i:number,n:number){let s=0;for(let j=Math.max(0,i-n+1);j<=i;j++)s+=closePos(b[j]);return s/Math.min(n,i+1)}
function rsi(b:Bar[],i:number,n:number){if(i<n)return 50;let u=0,d=0;for(let j=i-n+1;j<=i;j++){const x=b[j].c-b[j-1].c;if(x>0)u+=x;else d-=x}return d===0?100:100-100/(1+u/d)}
function atrBps(b:Bar[],i:number,n:number){let s=0,c=0;for(let j=Math.max(1,i-n+1);j<=i;j++){const p=b[j-1].c;const tr=Math.max(b[j].h-b[j].l,Math.abs(b[j].h-p),Math.abs(b[j].l-p));s+=tr/b[j].c*10000;c++}return c?s/c:0}
function computeScore(b:Bar[],i:number){const d=new Date(b[i].ts),h=d.getUTCHours(),m=d.getUTCMinutes(),dw=d.getUTCDay();let w=0,hW=0,tW=0;const add=(dr:number,wt:number,hl:number)=>{w+=dr*wt;hW+=hl*wt;tW+=wt};if(dw===4)add(-1,20,240);if(dw===3)add(1,19.05,240);if(dw===0)add(1,15.05,240);if(dw===1)add(1,10.61,240);if(dw===5)add(-1,6,240);if(h===22)add(1,2.86,120);if(h===21)add(1,17.9,120);if(h===20)add(1,9.49,60);if(h===23)add(-1,8.94,30);if(i>=60){const bp=avgCP(b,i,60);if(bp>0.58)add(1,15.49,240);if(bp<0.42)add(-1,8,240)}if(i>=60&&(h===13||(h===14&&m<=15))&&m<=15){const gap=lbRet(b,i,60);if(Math.abs(gap)>5)add(gap>0?-1:1,15,120)}if(i>=60){const r=rsi(b,i,60);if(r<30)add(1,4.63,120)}if(i>=1440){const dy=lbRet(b,i,1440);if(Math.abs(dy)>30)add(dy>0?-1:1,5,240)}return{score:w,hold:tW>0?Math.max(60,Math.min(240,Math.round(hW/tW))):120}}
const SN=5,SL=480
function cl(b:Bar[],i:number,dir:1|-1,rp?:number){const N=i+1,pr=rp??b[i].c,ca:number[]=[];ca.push(dir>0?Math.floor(pr/1000)*1000:Math.ceil(pr/1000)*1000,dir>0?Math.floor(pr/500)*500:Math.ceil(pr/500)*500,dir>0?Math.floor(pr/250)*250:Math.ceil(pr/250)*250);let bs:number|null=null,bd=Infinity;const se=N-SN-1,ss=Math.max(SN,N-SL-SN);for(let j=se;j>=ss;j--){let ok=true;for(let k=1;k<=SN&&ok;k++){if(dir>0){if((b[j-k]?.l??Infinity)<=b[j].l)ok=false;if((b[j+k]?.l??Infinity)<=b[j].l)ok=false}else{if((b[j-k]?.h??0)>=b[j].h)ok=false;if((b[j+k]?.h??0)>=b[j].h)ok=false}}if(!ok)continue;const l=dir>0?b[j].l:b[j].h;if(dir>0?l>=pr:l<=pr)continue;const di=Math.abs(pr-l);if(di<bd){bd=di;bs=l}}if(bs!==null)ca.push(bs);const d=new Date(b[N-1].ts);d.setUTCHours(0,0,0,0);const td=d.getTime();let ph=0,pl=Infinity;for(let j=N-1;j>=0;j--){if(b[j].ts>=td)continue;if(b[j].ts<td-86400000)break;if(b[j].h>ph)ph=b[j].h;if(b[j].l<pl)pl=b[j].l}if(dir>0&&pl<pr&&pl>0)ca.push(pl);if(dir<0&&ph>pr&&ph>0)ca.push(ph);const vm=new Map<number,number>();for(let j=N-1;j>=0&&b[j].ts>=td;j--){const bk=Math.round(b[j].c/50)*50;vm.set(bk,(vm.get(bk)??0)+(b[j].v??0))}let mv=0,pc=0;vm.forEach((v,p)=>{if(v>mv){mv=v;pc=p}});if(pc>0&&(dir>0?pc<pr:pc>pr))ca.push(pc);const v=ca.filter(l=>dir>0?l<=pr:l>=pr);if(!v.length)return dir>0?Math.floor(pr/250)*250:Math.ceil(pr/250)*250;return v.reduce((a,b)=>Math.abs(pr-b)<Math.abs(pr-a)?b:a)}
function sc(b:Bar[],i:number,dir:1|-1,rp:number){const N=i+1,pr=rp,c=[dir>0?Math.floor(pr/1000)*1000:Math.ceil(pr/1000)*1000,dir>0?Math.floor(pr/500)*500:Math.ceil(pr/500)*500,dir>0?Math.floor(pr/250)*250:Math.ceil(pr/250)*250];const d=new Date(b[N-1].ts);d.setUTCHours(0,0,0,0);const td=d.getTime();let ph=0,pl=Infinity;for(let j=N-1;j>=0;j--){if(b[j].ts>=td)continue;if(b[j].ts<td-86400000)break;if(b[j].h>ph)ph=b[j].h;if(b[j].l<pl)pl=b[j].l}if(dir>0&&pl<pr&&pl>0)c.push(pl);if(dir<0&&ph>pr&&ph>0)c.push(ph);return[...new Set(c)].filter(l=>dir>0?l<=pr:l>=pr).sort((a,b)=>Math.abs(pr-a)-Math.abs(pr-b))}
function sl(b:Bar[],i:number,dir:1|-1,rp:number,mb=0){const c=sc(b,i,dir,rp),pr=rp,v=mb>0?c.filter(l=>Math.abs(pr-l)/pr*10000>=mb):c;if(v.length)return v[0];if(c.length)return c.at(-1)!;return dir>0?Math.floor(pr/250)*250:Math.ceil(pr/250)*250}
function isRound(x:number){return Number.isFinite(x)&&Math.abs(x/250-Math.round(x/250))*250<=0.05}
function mA(px:number,dir:1|-1,usd:number){return dir>0?px-usd:px+usd}
function mB(px:number,dir:1|-1,bps:number){return dir>0?px*(1-bps/10000):px*(1+bps/10000)}
function db(a:number,b:number){return Math.abs(a-b)/a*10000}
function cc(b:Bar,dir:1|-1,px:number){return dir>0?b.c<=px:b.c>=px}
function th(b:Bar,dir:1|-1,px:number){return dir>0?b.l<=px:b.h>=px}
function rf(b:Bar,dir:1|-1,px:number){return dir>0?b.h>=px:b.l<=px}
function pb(dir:1|-1,e:number,x:number){return dir*(x-e)/e*10000}
function clamp(n:number,l:number,h:number){return Math.max(l,Math.min(h,n))}
function immBps(b:Bar[]):number{const a=atrBps(b,b.length-1,60);return clamp(a*0.75,4,10)}
function riskForScore(score:number,cfg:Cfg):number{
  const abs=Math.abs(score)
  if(abs>=cfg.strongThresh)return cfg.baseRisk+cfg.strongExtra
  if(abs<=cfg.weakThresh)return cfg.weakRisk
  return cfg.baseRisk
}

function sim(b:Bar[],cfg:Cfg,si=20160,ei=b.length):Trade[]{
  const tr:Trade[]=[],stc=new Map<string,number>()
  let cd=Math.max(si,20160)
  const EXT=8,WAIT=30,CAP=420,CFM=4,SCD=30,MW=30,N=b.length
  const fi=Math.max(si,20160),la=Math.min(ei,N-60)
  for(let i=fi;i<la;i++){if(i<cd)continue
    const{score,hold}=computeScore(b,i);if(score===0)continue
    const dir:1|-1=score>0?1:-1,sd:Side=dir>0?'long':'short',tt=dir>0?16:8
    if(Math.abs(score)<tt)continue
    let cf=true;for(let k=1;k<CFM;k++){const sk=computeScore(b,i-k).score;if(sk*dir<tt){cf=false;break}}if(!cf)continue
    if(dir*lbRet(b,i,4320)<-600)continue;if(dir*lbRet(b,i,10080)<-700)continue;if(dir*lbRet(b,i,20160)<-800)continue
    const day=new Date(b[i].ts).toISOString().slice(0,10),ck=`${day}_${sd}`;if((stc.get(ck)??0)>=2)continue
    const tgt=cl(b,i,dir);const ib=immBps(b);const d0=db(b[i].c,tgt)
    let ep=b[i].c,eb=i,filled=d0<=ib
    if(!filled){for(let j=i+1;j<=Math.min(N-1,i+WAIT);j++){const hit=dir>0?b[j].l<=tgt:b[j].h>=tgt;if(hit){filled=true;eb=j;ep=tgt;break}}}if(!filled)continue
    const rp=riskForScore(score,cfg)
    const sr=dir>0?tgt-0.01:tgt+0.01;const bl2=sl(b,i,dir,sr,25)
    const bb=db(tgt,bl2);const is=bb>=15&&bb<=200;const cs=is?bl2:(dir>0?tgt*0.99:tgt*1.01)
    const moat=(is&&isRound(bl2))?25:0;const soft=moat>0?mA(cs,dir,moat):cs
    const l3r=dir>0?soft-0.01:soft+0.01;const l3=sl(b,i,dir,l3r,0)
    const gap=Math.max(10,Math.min(100,db(soft,l3)));const hard=is?mB(soft,dir,gap):(dir>0?tgt*(1-1.25/100):tgt*(1+1.25/100))
    const sb=db(tgt,soft);const hb2=db(tgt,hard)+8;const sz=is?db(tgt,hard):125
    if(sb<5||sz<15||sz>350)continue
    const hc=i+CAP;let dl=i+clamp(hold,60,240);let ex=-1,slf=false,hs=false,gross=0,reason='time'
    for(let j=eb+1;j<Math.min(N,hc+1);j++){
      if(th(b[j],dir,hard)){ex=j;slf=true;hs=true;reason='hard_sl';gross=-hb2;break}
      if(cc(b[j],dir,soft)){slf=true;let done=false
        for(let k=j+1;k<=Math.min(N-1,j+MW,hc);k++){if(th(b[k],dir,hard)){ex=k;hs=true;reason='hard_sla';gross=-hb2;done=true;break};if(rf(b[k],dir,soft)){ex=k;reason='soft_maker';gross=pb(dir,ep,soft);done=true;break}}
        if(!done){ex=Math.min(N-1,j+MW,hc);reason='soft_to';gross=pb(dir,ep,b[ex].c)}break}
      if(j>=i+60){const es=computeScore(b,j);if(es.score*dir>=EXT){const pr=Math.min(j+clamp(es.hold,60,240),hc);if(pr>dl)dl=pr}}
      if(j>=dl){ex=j;reason='time';gross=pb(dir,ep,b[j].c);break}}
    if(ex<0){ex=Math.min(N-2,hc);reason='cap';gross=pb(dir,ep,b[ex].c)}
    const dt=new Date(b[i].ts);tr.push({gross,net:gross,reason,isSl:slf,isHardSl:hs,month:dt.toISOString().slice(0,7),side:sd,bar:i,sizingBps:sz,hold:ex-eb,riskPct:rp,score})
    if(slf)stc.set(ck,(stc.get(ck)??0)+1);cd=ex+(slf?SCD:5)}return tr}

function stats(v:number[]){const n=v.length;if(n<3)return{n,mean:0,sd:0,t:0,wr:0};const m=v.reduce((a,b)=>a+b,0)/n;const sd=Math.sqrt(v.reduce((s,x)=>s+(x-m)**2,0)/(n-1));return{n,mean:m,sd,t:sd>0?m/(sd/Math.sqrt(n)):0,wr:v.filter(x=>x>0).length/n}}
function sig(t:number){const a=Math.abs(t);return a>3.89?'****':a>3.29?'***':a>2.58?'**':a>1.96?'*':''}
function mon(tr:Trade[],f=1500){const m:Record<string,number>={};for(const t of tr)m[t.month]=(m[t.month]??0)+f*t.net/10000;return m}
function am(tr:Trade[]){const m:Record<string,number>={};for(const t of tr){const n=500*t.riskPct/(t.sizingBps/10000);m[t.month]=(m[t.month]??0)+n*t.net/10000}return m}
function ir(v:number[]){if(v.length<3)return 0;const s=stats(v);return s.sd>0?s.mean/s.sd:0}
function mIR(tr:Trade[]){return ir(Object.values(mon(tr)))}
function aIR(tr:Trade[]){return ir(Object.values(am(tr)))}
function mDD(tr:Trade[]){let eq=500,pk=500,dd=0;for(const t of tr){eq+=eq*t.riskPct/(t.sizingBps/10000)*t.net/10000;if(eq>pk)pk=eq;dd=Math.max(dd,(pk-eq)/pk)}return dd}
function tot(tr:Trade[]){return Object.values(am(tr)).reduce((a,b)=>a+b,0)}
function pm(a:Record<string,number>,b:Record<string,number>){const mo=[...new Set([...Object.keys(a),...Object.keys(b)])].sort();const d=mo.map(m=>(b[m]??0)-(a[m]??0));const s=stats(d);return{n:d.length,imp:d.filter(x=>x>0).length,mean:s.mean,t:s.t,tot:d.reduce((x,y)=>x+y,0)}}
function met(label:string,tr:Trade[]){const s=stats(tr.map(t=>t.net));const sl=tr.filter(t=>t.isSl),hd=tr.filter(t=>t.isHardSl);return{label,n:s.n,mean:s.mean,t:s.t,wr:s.wr,moIR:mIR(tr),aIR:aIR(tr),DD:mDD(tr),slRate:tr.length?sl.length/tr.length:0,hdRate:sl.length?hd.length/sl.length:0,avgRisk:tr.reduce((a,t)=>a+t.riskPct,0)/Math.max(1,tr.length)}}
function wf(tr:Trade[],fee:number){return tr.map(t=>({...t,net:t.gross-fee}))}
function fmt(m:ReturnType<typeof met>,ref?:Trade[],tr?:Trade[]){let p='';if(ref&&tr){const pa=pm(am(ref),am(tr));p=` Δ=$${pa.mean.toFixed(0)}(${pa.imp}/${pa.n},t=${pa.t.toFixed(2)})`}return`${m.label.padEnd(16)} n=${String(m.n).padStart(4)} mean=${m.mean.toFixed(2).padStart(6)} t=${m.t.toFixed(2)}${sig(m.t).padEnd(4)} WR=${(m.wr*100).toFixed(1)}% moIR=${m.moIR.toFixed(2)} aIR=${m.aIR.toFixed(2)} DD=${(m.DD*100).toFixed(1)}% SL=${(m.slRate*100).toFixed(1)}% risk=${(m.avgRisk*100).toFixed(1)}%${p}`}
function yr(t:Trade,b:Bar[]){return new Date(b[t.bar].ts).getUTCFullYear()}
function by(tr:Trade[],b:Bar[],y:number){return tr.filter(t=>yr(t,b)===year)};let year:number

;(async()=>{
  console.log('Loading bars...')
  const b=await loadBars(['data/klines/BTCUSDT-1m-2022-2025.jsonl','data/klines/BTCUSDT-1m-2022-2025b.jsonl','data/klines/BTCUSDT-1m.jsonl'])
  console.log(`${b.length.toLocaleString()} bars`)

  const cfgs:Cfg[]=[
    {label:'fixed3%',baseRisk:0.03,strongExtra:0,strongThresh:99,weakThresh:0,weakRisk:0.03},
    {label:'16-2% 23+4%',baseRisk:0.03,strongExtra:0.01,strongThresh:23,weakThresh:16,weakRisk:0.02},
    {label:'19+3.5% 16-2%',baseRisk:0.03,strongExtra:0.005,strongThresh:19,weakThresh:16,weakRisk:0.02},
    {label:'20+4%',baseRisk:0.03,strongExtra:0.01,strongThresh:20,weakThresh:0,weakRisk:0.03},
    {label:'22+4%',baseRisk:0.03,strongExtra:0.01,strongThresh:22,weakThresh:0,weakRisk:0.03},
    {label:'16-2%',baseRisk:0.03,strongExtra:0,strongThresh:99,weakThresh:16,weakRisk:0.02},
    {label:'16-1.5%',baseRisk:0.03,strongExtra:0,strongThresh:99,weakThresh:16,weakRisk:0.015},
    {label:'25+5% 16-1.5%',baseRisk:0.03,strongExtra:0.02,strongThresh:25,weakThresh:16,weakRisk:0.015},
  ]
  const sims=cfgs.map(cfg=>({cfg,trades:sim(b,cfg)}))

  for(const fee of [0,4]){
    console.log(`\n==== SIGNAL-SIZE feeRT=${fee} ====`)
    const ref=wf(sims[0].trades,fee)
    for(const sim of sims){
      const tr=wf(sim.trades,fee)
      console.log(fmt(met(sim.cfg.label,tr),sim.cfg.label==='fixed3%'?undefined:ref,sim.cfg.label==='fixed3%'?undefined:tr))
    }
  }
})().catch(e=>{console.error(e);process.exit(1)})
