<?php
namespace Forecast;

/*
  Erwartet $bars chronologisch alt->neu:
  $bars = [
    ['t'=>'YYYY-MM-DD','o'=>float,'h'=>float,'l'=>float,'c'=>float,'v'=>int], ...
  ]
*/

function sma(array $a,int $n): array {
  $n=max(1,$n); $out=[]; $sum=0; $q=[];
  foreach($a as $v){ $q[]=$v; $sum+=$v; if(count($q)>$n){ $sum-=array_shift($q); } $out[]=$sum/count($q); }
  return $out;
}
function rsi(array $c,int $n=14): array {
  $N=count($c); $r=array_fill(0,$N,null); if($N<2) return $r;
  $avgU=0; $avgD=0;
  for($i=1;$i<$N;$i++){
    $ch=$c[$i]-$c[$i-1]; $up=max(0,$ch); $dn=max(0,-$ch);
    if($i<=$n){ $avgU+=$up; $avgD+=$dn; if($i==$n){ $avgU/=$n; $avgD/=$n; $rs=$avgD==0?1e9:$avgU/$avgD; $r[$i]=100-100/(1+$rs); } }
    else{ $avgU=($avgU*($n-1)+$up)/$n; $avgD=($avgD*($n-1)+$dn)/$n; $rs=$avgD==0?1e9:$avgU/$avgD; $r[$i]=100-100/(1+$rs); }
  }
  return $r;
}
function atr(array $h,array $l,array $c,int $n=14): array {
  $N=count($c); $tr=[]; for($i=0;$i<$N;$i++){ $tr[]=$i?max($h[$i]-$l[$i],abs($h[$i]-$c[$i-1]),abs($l[$i]-$c[$i-1])):($h[$i]-$l[$i]); }
  $out=[]; $ema=0; $k=2/($n+1); foreach($tr as $i=>$v){ $ema = $i? $v*$k+$ema*(1-$k) : $v; $out[]=$ema; } return $out;
}
function pct_change(array $c,int $n): array {
  $N=count($c); $out=array_fill(0,$N,null);
  for($i=$n;$i<$N;$i++){ $prev=$c[$i-$n]; $out[$i]=$prev!=0?($c[$i]/$prev-1.0):null; }
  return $out;
}
function quantile(array $arr,float $q){
  $n=count($arr); if($n==0) return null; sort($arr); $idx=(int)floor($q*($n-1)); return $arr[$idx];
}

function build_precalc(array $bars): array {
  $c=array_column($bars,'c'); $h=array_column($bars,'h'); $l=array_column($bars,'l'); $v=array_column($bars,'v');
  $s20=sma($c,20); $s50=sma($c,50); $s200=sma($c,200);
  $rsi14=rsi($c,14); $atr14=atr($h,$l,$c,14);
  $mom20=pct_change($c,20); $mom63=pct_change($c,63);
  $v50=sma($v,50);
  $atrpct=[]; foreach($c as $i=>$cv){ $atrpct[$i]=($cv>0&&$atr14[$i]!==null)?$atr14[$i]/$cv:null; }
  $volratio=[]; foreach($v as $i=>$vv){ $volratio[$i]=($v50[$i]??0)>0? $vv/$v50[$i] : null; }
  $dist52h=[]; for($i=0;$i<count($c);$i++){ $s=max(0,$i-251); $mx=max(array_slice($c,$s,$i-$s+1)); $dist52h[$i]=$mx>0?($c[$i]/$mx-1):null; }
  return compact('c','h','l','v','s20','s50','s200','rsi14','atr14','mom20','mom63','v50','atrpct','volratio','dist52h');
}

function make_feat(array $P,int $i): ?array {
  if($i<200) return null;
  return [
    'rsi14'     => $P['rsi14'][$i],
    'mom20'     => $P['mom20'][$i],
    'mom63'     => $P['mom63'][$i],
    'atrpct'    => $P['atrpct'][$i],
    'vol_ratio' => $P['volratio'][$i],
    't20'       => $P['c'][$i]>$P['s20'][$i]?1:0,
    't50'       => $P['c'][$i]>$P['s50'][$i]?1:0,
    't200'      => $P['c'][$i]>$P['s200'][$i]?1:0,
    'dist52h'   => $P['dist52h'][$i],
  ];
}

function fit_norm(array $rows): array {
  $mu=[];$sd=[];$cnt=[];
  foreach($rows as $r){ foreach($r['f'] as $k=>$v){ if($v===null) continue; $mu[$k]=($mu[$k]??0)+$v; $sd[$k]=($sd[$k]??0)+$v*$v; $cnt[$k]=($cnt[$k]??0)+1; } }
  foreach($cnt as $k=>$c){ $m=$mu[$k]/$c; $mu[$k]=$m; $sd[$k]=sqrt(max(1e-9,$sd[$k]/$c - $m*$m)); }
  return ['mu'=>$mu,'sd'=>$sd];
}
function znorm(array $f,array $stats): array {
  $o=[]; foreach($f as $k=>$v){ $m=$stats['mu'][$k]??0; $s=$stats['sd'][$k]??1; $o[$k]=($v===null||$s==0)?0:(($v-$m)/$s); } return $o;
}
function dist2(array $a,array $b): float { $s=0; foreach($a as $k=>$v){ $d=$v-($b[$k]??0); $s+=$d*$d; } return $s; }

function knn_forecast(array $bars,int $h=20,int $k=200): ?array {
  $P=build_precalc($bars); $N=count($bars);
  if($N<$h+220) return null;

  // Index bauen
  $idx=[];
  for($i=200;$i<$N-$h;$i++){
    $f=make_feat($P,$i); if(!$f) continue;
    $ret = $bars[$i+$h]['c']/$bars[$i]['c'] - 1.0;
    $win = array_column(array_slice($bars,$i,$h+1),'c');
    $min = min($win); $maxdd = $min/$win[0]-1.0;
    $idx[]=['f'=>$f,'ret'=>$ret,'maxdd'=>$maxdd];
  }
  if(count($idx)<$k+20) return null;

  // Normierung
  $stats=fit_norm($idx);
  $f_now=make_feat($P,$N-1); if(!$f_now) return null;
  $f_now=znorm($f_now,$stats);

  // Distanzen
  $pool=[];
  foreach($idx as $row){ $pool[]=['d'=>dist2($f_now, znorm($row['f'],$stats)), 'ret'=>$row['ret'],'maxdd'=>$row['maxdd']]; }
  usort($pool, fn($a,$b)=> $a['d']<=>$b['d']);
  $nn=array_slice($pool,0,$k);

  // Gewichte (inverse Distanz)
  $eps=1e-9;
  $w=array_map(fn($x)=>1.0/max($eps, sqrt($x['d'])), $nn);
  $ws=array_sum($w);

  // Weighted means
  $exp=0; $maxdd=0; $p_up_w=0;
  foreach($nn as $i=>$row){
    $wi=$w[$i]/$ws;
    $exp   += $wi*$row['ret'];
    $maxdd += $wi*$row['maxdd'];
    if($row['ret']>0) $p_up_w += $wi;
  }

  // Weighted quantiles
  $pairs=[]; foreach($nn as $i=>$row){ $pairs[]=['ret'=>$row['ret'],'w'=>$w[$i]]; }
  usort($pairs, fn($a,$b)=> $a['ret']<=>$b['ret']);
  $cum=0; $p10=$pairs[0]['ret']; $p50=$p10; $p90=end($pairs)['ret'];
  foreach($pairs as $pr){
    $cum += $pr['w']/$ws;
    if($cum>=0.10 && $p10===$pairs[0]['ret']) $p10=$pr['ret'];
    if($cum>=0.50 && $p50===$pairs[0]['ret']) $p50=$pr['ret'];
    if($cum>=0.90){ $p90=$pr['ret']; break; }
  }

  // Confidence: Verhältnis der mittleren k-Distanz zum Pool-Median (kleiner = besser)
  $allD = array_column($pool,'d'); sort($allD);
  $ref  = $allD[(int)floor(0.5*(count($allD)-1))] ?: 1.0;
  $meanK= array_sum(array_column($nn,'d'))/count($nn);
  $conf = max(0.0, min(1.0, 1.0 - sqrt($meanK)/max($eps, sqrt($ref))));

  return ['p_up'=>$p_up_w,'p10'=>$p10,'p50'=>$p50,'p90'=>$p90,'exp_ret'=>$exp,'exp_maxdd'=>$maxdd,'k'=>$k,'conf'=>$conf];
}

function run_detectors(array $bars): array {
  $N=count($bars); if($N<260) return [];
  $c=array_column($bars,'c'); $h=array_column($bars,'h'); $l=array_column($bars,'l'); $v=array_column($bars,'v');
  $s20=sma($c,20); $s50=sma($c,50); $s200=sma($c,200); $v50=sma($v,50);
  $i=$N-1; $dets=[];

  $volOK = ($v50[$i]??0)>0 ? (($v[$i]??0)/max(1,$v50[$i])) : 1; // robust

  // 52W-Breakout (vol optional)
  $s=max(0,$i-251); $mx=max(array_slice($c,$s,$i-$s+1));
  if ($c[$i] >= $mx*0.999 && $volOK >= 1.2) $dets[]=['id'=>'breakout_52w','confidence'=>0.7];

  // Trend-Continuation (Stacked MAs)
  if ($s50[$i] > $s200[$i] && $c[$i] > $s50[$i]) $dets[]=['id'=>'trend_continue','confidence'=>0.6];

  // 3-Bar Pullback im Aufwärtstrend
  $pull3 = $s50[$i] > $s200[$i]
        && $c[$i] < $s20[$i]
        && $c[$i] < $c[$i-1] && $c[$i-1] < $c[$i-2];
  if ($pull3) $dets[]=['id'=>'pullback_3bar_uptrend','confidence'=>0.55];

  // Volatility Contraction (VCP light): Range schrumpft
  $rng = function($j){ return max(1e-9, $GLOBALS['h'][$j]-$GLOBALS['l'][$j]); };
  $vcp = ($h[$i]-$l[$i]) < ($h[$i-5]-$l[$i-5]) && ($h[$i-5]-$l[$i-5]) < ($h[$i-10]-$l[$i-10]);
  if ($vcp && $c[$i] > $s50[$i]) $dets[]=['id'=>'vol_contraction','confidence'=>0.5];

  // Gap-Up + Close hoch
  $gapUp = $i>0 && $l[$i] > $h[$i-1]*1.01 && $c[$i] > ($l[$i] + 0.7*($h[$i]-$l[$i]));
  if ($gapUp) $dets[]=['id'=>'gap_up_follow','confidence'=>0.6];

  // RSI-Rebound (Mean Reversion)
  $rsi = rsi($c,14); $rebound = ($rsi[$i-1]??50) < 35 && ($rsi[$i]??50) > 40 && $c[$i] > $s200[$i];
  if ($rebound) $dets[]=['id'=>'rsi_rebound','confidence'=>0.5];

  return $dets;
}

// Regime (kein Volumen nötig)
function regime_meta(array $bars): array {
  $c=array_column($bars,'c'); $h=array_column($bars,'h'); $l=array_column($bars,'l');
  $s50=sma($c,50); $s200=sma($c,200); $atr14=atr($h,$l,$c,14);
  $N=count($c); $i=$N-1;
  $trend = ($c[$i]>$s50[$i] && $s50[$i]>$s200[$i]) ? 'Uptrend' : (($c[$i]<$s50[$i] && $s50[$i]<$s200[$i])?'Downtrend':'Range');
  // ATR%-Perzentil der letzten 250 Tage
  $atrpct=[]; foreach($c as $k=>$cv){ $atrpct[$k]=($cv>0 && $atr14[$k]!==null)?$atr14[$k]/$cv:null; }
  $win = array_slice($atrpct, max(0,$N-250));
  $win = array_values(array_filter($win, fn($x)=>$x!==null));
  $dec='mid';
  if(count($win)>=20){
    $val=$atrpct[$i]; $cnt=0; foreach($win as $w){ if($w<=$val) $cnt++; }
    $p = ($cnt-1)/max(1,count($win)-1);
    $dec = $p<0.33?'low':($p>0.66?'high':'mid');
  }
  return ['trend'=>$trend,'vol'=>$dec];
}

// Risiko/Targets auf Close-Basis (kein Volumen nötig)
function risk_block(array $bars, array $knn, float $atr_mult=1.5): array {
  $c=array_column($bars,'c'); $h=array_column($bars,'h'); $l=array_column($bars,'l');
  $atr14=atr($h,$l,$c,14); $N=count($c); $i=$N-1;
  $close=$c[$i]; $atr=$atr14[$i]??null;
  if($close<=0 || $atr===null) return [];
  $stop = $close - $atr_mult*$atr;
  $t_p50 = $close * (1 + ($knn['p50'] ?? 0));
  $t_p90 = $close * (1 + ($knn['p90'] ?? 0));
  $risk_amt = max(1e-9, $close - $stop);
  $rr_p50 = ($t_p50 - $close) / $risk_amt;
  $rr_p90 = ($t_p90 - $close) / $risk_amt;
  return [
    'close'=>$close,
    'stop_mult'=>$atr_mult,'stop'=>$stop,'stop_pct'=>$stop/$close-1,
    'rr_p50'=>$rr_p50,'rr_p90'=>$rr_p90
  ];
}

function forecast_run(array $bars,int $horizon=20,float $atr_mult=1.5): array {
  $N=count($bars); if($N<320) return ['ok'=>false,'error'=>'Zu wenig Historie'];

  $reg = regime_meta($bars);
  $kUse = ($reg['trend']==='Uptrend') ? 150 : (($reg['trend']==='Range') ? 220 : 250);

  $knn=knn_forecast($bars,$horizon,$kUse);
  if(!$knn) return ['ok'=>false,'error'=>'Index zu klein'];

  $risk = risk_block($bars, ['p50'=>$knn['p50'],'p90'=>$knn['p90']], $atr_mult);

  return [
    'ok'=>true,
    'horizon_days'=>$horizon,
    'prob_up'=>$knn['p_up'],
    'retiles'=>['p10'=>$knn['p10'],'p50'=>$knn['p50'],'p90'=>$knn['p90']],
    'expectations'=>['exp_ret'=>$knn['exp_ret'],'exp_maxdd'=>$knn['exp_maxdd']],
    'detectors'=>run_detectors($bars),
    'meta'=>['k'=>$knn['k'], 'confidence'=>$knn['conf'], 'regime'=>$reg],
    'risk'=>$risk,
    'targets'=> isset($risk['close']) ? [
      't_p50'=>$risk['close']*(1+$knn['p50']),
      't_p90'=>$risk['close']*(1+$knn['p90'])
    ] : null
  ];
}