Spaces:
Running
Running
File size: 4,662 Bytes
da0ce7b | 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | import { loadFieldMetrics, simulateTrades, type FieldMetricsRow, type SimulationSummary } from './paperTradeSimulation.ts';
// Simple seeded RNG (LCG)
function makeRng(seed: number) {
let s = seed >>> 0;
return () => {
s = (s * 1664525 + 1013904223) >>> 0;
return s / 0xffffffff;
};
}
function parseArg(name: string, def: number) {
const ix = process.argv.findIndex(a => a === `--${name}`);
if (ix !== -1 && process.argv[ix + 1]) return Number(process.argv[ix + 1]);
return def;
}
interface TrialResult {
finalBalance: number;
maxDrawdown: number;
netPnl: number;
}
function bootstrapRows(rows: FieldMetricsRow[], window: number, length: number, rng: () => number, noiseSigma: number, starts?: number[]): FieldMetricsRow[] {
const out: FieldMetricsRow[] = [];
const maxStart = Math.max(0, rows.length - window);
const pool = (starts && starts.length > 0) ? starts : Array.from({ length: maxStart + 1 }, (_, i) => i);
while (out.length < length) {
const pick = Math.floor(rng() * pool.length);
const start = pool[pick];
const chunk = rows.slice(start, start + window);
for (const r of chunk) {
if (out.length >= length) break;
const Lnoise = (rng() * 2 - 1) * noiseSigma * r.L; // symmetric noise
out.push({
...r,
L: Math.max(1e-9, r.L + Lnoise)
});
}
}
// Re-time the series to maintain monotonic t
return out.map((r, i) => ({ ...r, t: i / 100 }));
}
function percentiles(values: number[], ps: number[]) {
const a = [...values].sort((x, y) => x - y);
return ps.map(p => {
const idx = (a.length - 1) * p;
const lo = Math.floor(idx);
const hi = Math.ceil(idx);
if (lo === hi) return a[lo];
const w = idx - lo;
return a[lo] * (1 - w) + a[hi] * w;
});
}
function main() {
const trials = parseArg('trials', 250);
const seed = parseArg('seed', 42);
const window = parseArg('window', 8);
const noise = parseArg('noise', 0.0); // fraction of L
const lengthMult = parseArg('lengthMult', 1);
const riskPct = parseArg('risk', 0); // 0 = disabled, else fraction of balance per trade
const cohThresh = parseArg('coh', 0); // 0 disables filtering
const energyThresh = parseArg('energy', 0);
const baseRows = loadFieldMetrics();
const rng = makeRng(seed);
const results: TrialResult[] = [];
// Precompute regime-filtered start indices if thresholds provided
let starts: number[] | undefined = undefined;
if (cohThresh > 0 || energyThresh > 0) {
const maxStart = Math.max(0, baseRows.length - window);
const s: number[] = [];
for (let i = 0; i <= maxStart; i++) {
const chunk = baseRows.slice(i, i + window);
const avgC = chunk.reduce((a, r) => a + r.C_nonlin, 0) / chunk.length;
const avgE = chunk.reduce((a, r) => a + r.G_eff, 0) / chunk.length;
if ((cohThresh === 0 || avgC >= cohThresh) && (energyThresh === 0 || avgE >= energyThresh)) {
s.push(i);
}
}
if (s.length > 0) starts = s;
}
for (let i = 0; i < trials; i++) {
const boot = bootstrapRows(baseRows, window, baseRows.length * Math.max(1, lengthMult), rng, noise, starts);
// Configure risk percent for this trial (constant across a run)
if (riskPct > 0) process.env.SIM_RISK_PCT = String(riskPct);
const summary: SimulationSummary = simulateTrades(boot);
results.push({
finalBalance: summary.finalBalance,
maxDrawdown: summary.maxDrawdown,
netPnl: summary.netPnl
});
}
const finals = results.map(r => r.finalBalance);
const drawdowns = results.map(r => r.maxDrawdown);
const pnls = results.map(r => r.netPnl);
const [p05, p50, p95] = percentiles(finals, [0.05, 0.5, 0.95]);
const lossProb = finals.filter(v => v < 1000).length / finals.length;
const avgFinal = finals.reduce((a, b) => a + b, 0) / finals.length;
const avgPnl = pnls.reduce((a, b) => a + b, 0) / pnls.length;
const avgDd = drawdowns.reduce((a, b) => a + b, 0) / drawdowns.length;
console.log('=== Monte Carlo (bootstrap path) ===');
console.log(`Trials: ${trials} | seed: ${seed} | window: ${window} | noise: ${noise} | lengthMult: ${lengthMult} | risk: ${riskPct} | coh>=${cohThresh} | energy>=${energyThresh}`);
console.log(`Final balance avg: $${avgFinal.toFixed(2)} | p05: $${p05.toFixed(2)} | p50: $${p50.toFixed(2)} | p95: $${p95.toFixed(2)}`);
console.log(`Loss probability (<$1000): ${(lossProb * 100).toFixed(1)}%`);
console.log(`Avg net PnL: $${avgPnl.toFixed(2)} | Avg max drawdown (ΔL): ${avgDd.toFixed(4)}`);
}
main();
|