tradelab 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +75 -12
- package/bin/tradelab-mcp.js +7 -0
- package/bin/tradelab.js +29 -0
- package/dist/cjs/data.cjs +149 -26
- package/dist/cjs/index.cjs +1893 -1003
- package/dist/cjs/live.cjs +134 -25
- package/dist/cjs/ta.cjs +339 -0
- package/docs/api-reference.md +46 -0
- package/docs/backtest-engine.md +112 -0
- package/docs/live-trading.md +51 -0
- package/docs/mcp.md +64 -0
- package/docs/research.md +103 -0
- package/docs/superpowers/plans/2026-00-overview.md +101 -0
- package/docs/superpowers/plans/2026-01-metrics-correctness.md +873 -0
- package/docs/superpowers/plans/2026-02-indicator-library.md +677 -0
- package/docs/superpowers/plans/2026-03-overfitting-toolkit.md +882 -0
- package/docs/superpowers/plans/2026-04-async-signals-seeding.md +981 -0
- package/docs/superpowers/plans/2026-05-mcp-server.md +758 -0
- package/docs/superpowers/plans/2026-06-parallel-param-sweep.md +508 -0
- package/docs/superpowers/plans/2026-07-funding-carry-costs.md +535 -0
- package/docs/superpowers/plans/2026-08-live-dashboard.md +547 -0
- package/docs/superpowers/plans/HANDOFF.md +88 -0
- package/examples/liveDashboard.js +33 -0
- package/examples/llmSignal.js +33 -0
- package/examples/optimize.js +25 -0
- package/package.json +16 -2
- package/src/engine/asyncSignal.js +28 -0
- package/src/engine/backtest.js +13 -1
- package/src/engine/backtestAsync.js +27 -0
- package/src/engine/backtestTicks.js +13 -2
- package/src/engine/barSystemRunner.js +96 -41
- package/src/engine/execution.js +39 -0
- package/src/engine/grid.js +15 -0
- package/src/engine/llmSignal.js +84 -0
- package/src/engine/optimize.js +86 -0
- package/src/engine/optimizeWorker.js +67 -0
- package/src/engine/walkForward.js +1 -0
- package/src/index.js +9 -0
- package/src/live/dashboard/server.js +120 -0
- package/src/live/engine/liveEngine.js +2 -2
- package/src/live/index.js +1 -0
- package/src/mcp/schemas.js +48 -0
- package/src/mcp/server.js +31 -0
- package/src/mcp/tools.js +142 -0
- package/src/metrics/annualize.js +32 -0
- package/src/metrics/benchmark.js +55 -0
- package/src/metrics/buildMetrics.js +34 -13
- package/src/metrics/finite.js +17 -0
- package/src/research/combinations.js +18 -0
- package/src/research/cpcv.js +47 -0
- package/src/research/deflatedSharpe.js +35 -0
- package/src/research/index.js +6 -0
- package/src/research/monteCarlo.js +88 -0
- package/src/research/pbo.js +69 -0
- package/src/research/stats.js +78 -0
- package/src/strategies/builtins.js +96 -0
- package/src/strategies/index.js +30 -0
- package/src/ta/channels.js +67 -0
- package/src/ta/index.js +16 -0
- package/src/ta/oscillators.js +70 -0
- package/src/ta/trend.js +78 -0
- package/src/utils/random.js +33 -0
- package/templates/dashboard.html +174 -0
- package/types/index.d.ts +154 -0
- package/types/live.d.ts +15 -0
- package/types/ta.d.ts +45 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/ta/oscillators.js
|
|
2
|
+
import { ema } from "../utils/indicators.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wilder's RSI. Returns a full-length array; warmup positions are `undefined`.
|
|
6
|
+
*/
|
|
7
|
+
export function rsi(closes, period = 14) {
|
|
8
|
+
const out = new Array(closes.length).fill(undefined);
|
|
9
|
+
if (closes.length <= period) return out;
|
|
10
|
+
|
|
11
|
+
let gainSum = 0;
|
|
12
|
+
let lossSum = 0;
|
|
13
|
+
for (let i = 1; i <= period; i += 1) {
|
|
14
|
+
const change = closes[i] - closes[i - 1];
|
|
15
|
+
if (change >= 0) gainSum += change;
|
|
16
|
+
else lossSum -= change;
|
|
17
|
+
}
|
|
18
|
+
let avgGain = gainSum / period;
|
|
19
|
+
let avgLoss = lossSum / period;
|
|
20
|
+
out[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
|
|
21
|
+
|
|
22
|
+
for (let i = period + 1; i < closes.length; i += 1) {
|
|
23
|
+
const change = closes[i] - closes[i - 1];
|
|
24
|
+
const gain = change > 0 ? change : 0;
|
|
25
|
+
const loss = change < 0 ? -change : 0;
|
|
26
|
+
avgGain = (avgGain * (period - 1) + gain) / period;
|
|
27
|
+
avgLoss = (avgLoss * (period - 1) + loss) / period;
|
|
28
|
+
out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* MACD line, signal line, histogram. All full-length, aligned to input.
|
|
35
|
+
*/
|
|
36
|
+
export function macd(closes, fast = 12, slow = 26, signalPeriod = 9) {
|
|
37
|
+
const emaFast = ema(closes, fast);
|
|
38
|
+
const emaSlow = ema(closes, slow);
|
|
39
|
+
const macdLine = closes.map((_, i) => emaFast[i] - emaSlow[i]);
|
|
40
|
+
const signalLine = ema(macdLine, signalPeriod);
|
|
41
|
+
const histogram = macdLine.map((v, i) => v - signalLine[i]);
|
|
42
|
+
return { macd: macdLine, signal: signalLine, histogram };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Stochastic oscillator %K (smoothed close position in the high-low range) and
|
|
47
|
+
* %D (SMA of %K). `bars` need { high, low, close }.
|
|
48
|
+
*/
|
|
49
|
+
export function stochastic(bars, kPeriod = 14, dPeriod = 3) {
|
|
50
|
+
const k = new Array(bars.length).fill(undefined);
|
|
51
|
+
for (let i = kPeriod - 1; i < bars.length; i += 1) {
|
|
52
|
+
let hh = -Infinity;
|
|
53
|
+
let ll = Infinity;
|
|
54
|
+
for (let j = i - kPeriod + 1; j <= i; j += 1) {
|
|
55
|
+
if (bars[j].high > hh) hh = bars[j].high;
|
|
56
|
+
if (bars[j].low < ll) ll = bars[j].low;
|
|
57
|
+
}
|
|
58
|
+
const range = hh - ll;
|
|
59
|
+
k[i] = range === 0 ? 0 : ((bars[i].close - ll) / range) * 100;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const d = new Array(bars.length).fill(undefined);
|
|
63
|
+
for (let i = 0; i < bars.length; i += 1) {
|
|
64
|
+
if (i < kPeriod - 1 + dPeriod - 1) continue;
|
|
65
|
+
let sum = 0;
|
|
66
|
+
for (let j = i - dPeriod + 1; j <= i; j += 1) sum += k[j];
|
|
67
|
+
d[i] = sum / dPeriod;
|
|
68
|
+
}
|
|
69
|
+
return { k, d };
|
|
70
|
+
}
|
package/src/ta/trend.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/ta/trend.js
|
|
2
|
+
import { atr } from "../utils/indicators.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Supertrend. Returns { line, direction } full-length.
|
|
6
|
+
* direction: 1 = uptrend (line is support below price), -1 = downtrend.
|
|
7
|
+
*/
|
|
8
|
+
export function supertrend(bars, period = 10, mult = 3) {
|
|
9
|
+
const range = atr(bars, period);
|
|
10
|
+
const line = new Array(bars.length).fill(undefined);
|
|
11
|
+
const direction = new Array(bars.length).fill(undefined);
|
|
12
|
+
|
|
13
|
+
let prevUpper = Infinity;
|
|
14
|
+
let prevLower = -Infinity;
|
|
15
|
+
let prevDir = 1;
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < bars.length; i += 1) {
|
|
18
|
+
if (range[i] === undefined) continue;
|
|
19
|
+
const mid = (bars[i].high + bars[i].low) / 2;
|
|
20
|
+
const basicUpper = mid + mult * range[i];
|
|
21
|
+
const basicLower = mid - mult * range[i];
|
|
22
|
+
const close = bars[i].close;
|
|
23
|
+
const prevClose = i > 0 ? bars[i - 1].close : close;
|
|
24
|
+
|
|
25
|
+
const upper = basicUpper < prevUpper || prevClose > prevUpper ? basicUpper : prevUpper;
|
|
26
|
+
const lower = basicLower > prevLower || prevClose < prevLower ? basicLower : prevLower;
|
|
27
|
+
|
|
28
|
+
let dir = prevDir;
|
|
29
|
+
if (prevDir === 1 && close < lower) dir = -1;
|
|
30
|
+
else if (prevDir === -1 && close > upper) dir = 1;
|
|
31
|
+
|
|
32
|
+
line[i] = dir === 1 ? lower : upper;
|
|
33
|
+
direction[i] = dir;
|
|
34
|
+
|
|
35
|
+
prevUpper = upper;
|
|
36
|
+
prevLower = lower;
|
|
37
|
+
prevDir = dir;
|
|
38
|
+
}
|
|
39
|
+
return { line, direction };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function dayKeyUTC(timeMs) {
|
|
43
|
+
const d = new Date(timeMs);
|
|
44
|
+
return d.getUTCFullYear() * 10000 + (d.getUTCMonth() + 1) * 100 + d.getUTCDate();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Session VWAP, reset on each UTC calendar day. `bars` need
|
|
49
|
+
* { time, high, low, close, volume }. When volume is missing/zero, falls back
|
|
50
|
+
* to an unweighted cumulative typical-price average for that day.
|
|
51
|
+
*/
|
|
52
|
+
export function vwap(bars) {
|
|
53
|
+
const out = new Array(bars.length).fill(undefined);
|
|
54
|
+
let currentDay = null;
|
|
55
|
+
let cumPV = 0;
|
|
56
|
+
let cumV = 0;
|
|
57
|
+
let cumTP = 0;
|
|
58
|
+
let count = 0;
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < bars.length; i += 1) {
|
|
61
|
+
const day = dayKeyUTC(bars[i].time);
|
|
62
|
+
if (day !== currentDay) {
|
|
63
|
+
currentDay = day;
|
|
64
|
+
cumPV = 0;
|
|
65
|
+
cumV = 0;
|
|
66
|
+
cumTP = 0;
|
|
67
|
+
count = 0;
|
|
68
|
+
}
|
|
69
|
+
const tp = (bars[i].high + bars[i].low + bars[i].close) / 3;
|
|
70
|
+
const vol = Number.isFinite(bars[i].volume) ? bars[i].volume : 0;
|
|
71
|
+
cumPV += tp * vol;
|
|
72
|
+
cumV += vol;
|
|
73
|
+
cumTP += tp;
|
|
74
|
+
count += 1;
|
|
75
|
+
out[i] = cumV > 0 ? cumPV / cumV : cumTP / count;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function xmur3(str) {
|
|
2
|
+
let h = 1779033703 ^ str.length;
|
|
3
|
+
for (let i = 0; i < str.length; i += 1) {
|
|
4
|
+
h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
|
|
5
|
+
h = (h << 13) | (h >>> 19);
|
|
6
|
+
}
|
|
7
|
+
return () => {
|
|
8
|
+
h = Math.imul(h ^ (h >>> 16), 2246822507);
|
|
9
|
+
h = Math.imul(h ^ (h >>> 13), 3266489909);
|
|
10
|
+
return (h ^= h >>> 16) >>> 0;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function mulberry32(seed) {
|
|
15
|
+
let state = seed >>> 0;
|
|
16
|
+
return () => {
|
|
17
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
18
|
+
let t = Math.imul(state ^ (state >>> 15), state | 1);
|
|
19
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
20
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Returns a deterministic () => float-in-[0,1) generator seeded by `seed`. */
|
|
25
|
+
export function makeRng(seed = "tradelab") {
|
|
26
|
+
const seedFn = xmur3(String(seed));
|
|
27
|
+
return mulberry32(seedFn());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Integer in [0, maxExclusive) from an rng produced by makeRng. */
|
|
31
|
+
export function randInt(rng, maxExclusive) {
|
|
32
|
+
return Math.floor(rng() * maxExclusive);
|
|
33
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>tradelab live</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
margin: 0;
|
|
14
|
+
font:
|
|
15
|
+
14px/1.5 ui-monospace,
|
|
16
|
+
SFMono-Regular,
|
|
17
|
+
Menlo,
|
|
18
|
+
monospace;
|
|
19
|
+
background: #0f172a;
|
|
20
|
+
color: #e2e8f0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
header {
|
|
24
|
+
padding: 16px 24px;
|
|
25
|
+
border-bottom: 1px solid #1e293b;
|
|
26
|
+
display: flex;
|
|
27
|
+
gap: 24px;
|
|
28
|
+
align-items: baseline;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
h1 {
|
|
32
|
+
font-size: 16px;
|
|
33
|
+
margin: 0;
|
|
34
|
+
color: #38bdf8;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.grid {
|
|
38
|
+
display: grid;
|
|
39
|
+
grid-template-columns: 1fr 1fr;
|
|
40
|
+
gap: 16px;
|
|
41
|
+
padding: 24px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.card {
|
|
45
|
+
background: #111827;
|
|
46
|
+
border: 1px solid #1e293b;
|
|
47
|
+
border-radius: 8px;
|
|
48
|
+
padding: 16px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.card h2 {
|
|
52
|
+
font-size: 12px;
|
|
53
|
+
text-transform: uppercase;
|
|
54
|
+
letter-spacing: 0.08em;
|
|
55
|
+
color: #64748b;
|
|
56
|
+
margin: 0 0 12px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.big {
|
|
60
|
+
font-size: 28px;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.pos {
|
|
65
|
+
color: #4ade80;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.neg {
|
|
69
|
+
color: #f87171;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
ul {
|
|
73
|
+
list-style: none;
|
|
74
|
+
margin: 0;
|
|
75
|
+
padding: 0;
|
|
76
|
+
max-height: 320px;
|
|
77
|
+
overflow: auto;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
li {
|
|
81
|
+
padding: 6px 0;
|
|
82
|
+
border-bottom: 1px solid #1e293b;
|
|
83
|
+
display: flex;
|
|
84
|
+
gap: 12px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.halt {
|
|
88
|
+
color: #fbbf24;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
time {
|
|
92
|
+
color: #475569;
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
95
|
+
</head>
|
|
96
|
+
<body>
|
|
97
|
+
<header>
|
|
98
|
+
<h1>tradelab live</h1>
|
|
99
|
+
<span id="symbol">-</span>
|
|
100
|
+
<span id="conn">connecting...</span>
|
|
101
|
+
</header>
|
|
102
|
+
<div class="grid">
|
|
103
|
+
<div class="card">
|
|
104
|
+
<h2>Equity</h2>
|
|
105
|
+
<div class="big" id="equity">-</div>
|
|
106
|
+
<div>Day PnL: <span id="dayPnl">-</span></div>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="card">
|
|
109
|
+
<h2>Open position</h2>
|
|
110
|
+
<div id="position">flat</div>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="card">
|
|
113
|
+
<h2>Risk</h2>
|
|
114
|
+
<div id="risk">-</div>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="card">
|
|
117
|
+
<h2>Recent events</h2>
|
|
118
|
+
<ul id="events"></ul>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
<script>
|
|
122
|
+
const fmt = (n) =>
|
|
123
|
+
typeof n === "number" ? n.toLocaleString(undefined, { maximumFractionDigits: 2 }) : "-";
|
|
124
|
+
|
|
125
|
+
function applyState(s) {
|
|
126
|
+
document.getElementById("symbol").textContent = s.symbol ?? s.id ?? "portfolio";
|
|
127
|
+
document.getElementById("equity").textContent = fmt(s.equity);
|
|
128
|
+
const dp = document.getElementById("dayPnl");
|
|
129
|
+
dp.textContent = fmt(s.dayPnl);
|
|
130
|
+
dp.className = (s.dayPnl ?? 0) >= 0 ? "pos" : "neg";
|
|
131
|
+
const p = s.openPosition;
|
|
132
|
+
document.getElementById("position").textContent = p
|
|
133
|
+
? `${p.side} ${fmt(p.size)} @ ${fmt(p.entryFill ?? p.entry)} | uPnL ${fmt(p.unrealizedPnl)}`
|
|
134
|
+
: "flat";
|
|
135
|
+
document.getElementById("risk").textContent = s.risk ? JSON.stringify(s.risk) : "ok";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function pollState() {
|
|
139
|
+
try {
|
|
140
|
+
applyState(await (await fetch("/state")).json());
|
|
141
|
+
} catch {}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pollState();
|
|
145
|
+
setInterval(pollState, 3000);
|
|
146
|
+
|
|
147
|
+
const list = document.getElementById("events");
|
|
148
|
+
const es = new EventSource("/events");
|
|
149
|
+
es.onopen = () => (document.getElementById("conn").textContent = "live");
|
|
150
|
+
es.onerror = () => (document.getElementById("conn").textContent = "disconnected");
|
|
151
|
+
es.onmessage = (e) => {
|
|
152
|
+
const msg = JSON.parse(e.data);
|
|
153
|
+
const li = document.createElement("li");
|
|
154
|
+
const halt = msg.event.startsWith("risk:");
|
|
155
|
+
li.innerHTML =
|
|
156
|
+
"<time>" +
|
|
157
|
+
new Date(msg.t).toLocaleTimeString() +
|
|
158
|
+
"</time>" +
|
|
159
|
+
'<span class="' +
|
|
160
|
+
(halt ? "halt" : "") +
|
|
161
|
+
'">' +
|
|
162
|
+
msg.event +
|
|
163
|
+
"</span>" +
|
|
164
|
+
"<span>" +
|
|
165
|
+
(msg.payload?.symbol ?? "") +
|
|
166
|
+
"</span>";
|
|
167
|
+
list.prepend(li);
|
|
168
|
+
while (list.children.length > 100) list.removeChild(list.lastChild);
|
|
169
|
+
if (["equity:update", "position:opened", "position:closed"].includes(msg.event))
|
|
170
|
+
pollState();
|
|
171
|
+
};
|
|
172
|
+
</script>
|
|
173
|
+
</body>
|
|
174
|
+
</html>
|
package/types/index.d.ts
CHANGED
|
@@ -82,6 +82,7 @@ export interface TradeExit {
|
|
|
82
82
|
time: number;
|
|
83
83
|
reason: string;
|
|
84
84
|
pnl: number;
|
|
85
|
+
financing?: number;
|
|
85
86
|
exitATR?: number;
|
|
86
87
|
}
|
|
87
88
|
|
|
@@ -238,6 +239,9 @@ export interface SignalResult {
|
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
export type SignalFunction = (context: SignalContext) => SignalResult | null;
|
|
242
|
+
export type AsyncSignalFunction = (
|
|
243
|
+
context: SignalContext
|
|
244
|
+
) => SignalResult | null | Promise<SignalResult | null>;
|
|
241
245
|
|
|
242
246
|
export interface PendingOrder {
|
|
243
247
|
side: Side;
|
|
@@ -268,6 +272,15 @@ export interface ExecutionCostOptions {
|
|
|
268
272
|
commissionPerUnit?: number;
|
|
269
273
|
commissionPerOrder?: number;
|
|
270
274
|
minCommission?: number;
|
|
275
|
+
carry?: {
|
|
276
|
+
longAnnualBps?: number;
|
|
277
|
+
shortAnnualBps?: number;
|
|
278
|
+
};
|
|
279
|
+
funding?: {
|
|
280
|
+
rateBps?: number;
|
|
281
|
+
intervalMs?: number;
|
|
282
|
+
anchorMs?: number;
|
|
283
|
+
};
|
|
271
284
|
}
|
|
272
285
|
|
|
273
286
|
export interface MfeTrailOptions {
|
|
@@ -337,6 +350,11 @@ export interface BacktestOptions {
|
|
|
337
350
|
strict?: boolean;
|
|
338
351
|
}
|
|
339
352
|
|
|
353
|
+
export interface BacktestAsyncOptions extends Omit<BacktestOptions, "signal"> {
|
|
354
|
+
signal: AsyncSignalFunction;
|
|
355
|
+
signalBudgetMs?: number;
|
|
356
|
+
}
|
|
357
|
+
|
|
340
358
|
export interface BacktestTickOptions {
|
|
341
359
|
ticks: Tick[];
|
|
342
360
|
symbol?: string;
|
|
@@ -357,6 +375,7 @@ export interface BacktestTickOptions {
|
|
|
357
375
|
collectEqSeries?: boolean;
|
|
358
376
|
collectReplay?: boolean;
|
|
359
377
|
queueFillProbability?: number;
|
|
378
|
+
seed?: string;
|
|
360
379
|
oco?: OCOOptions;
|
|
361
380
|
}
|
|
362
381
|
|
|
@@ -542,6 +561,81 @@ export interface ArtifactPaths {
|
|
|
542
561
|
metrics: string | null;
|
|
543
562
|
}
|
|
544
563
|
|
|
564
|
+
export interface LlmSignalOptions {
|
|
565
|
+
resolve: AsyncSignalFunction;
|
|
566
|
+
budgetMs?: number;
|
|
567
|
+
onError?: "skip" | "throw";
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export interface LlmDecisionLogEntry {
|
|
571
|
+
index: number;
|
|
572
|
+
time?: number;
|
|
573
|
+
close?: number;
|
|
574
|
+
latencyMs: number;
|
|
575
|
+
result?: SignalResult | null;
|
|
576
|
+
error?: string;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export interface StrategyParamSpec {
|
|
580
|
+
type: string;
|
|
581
|
+
default?: unknown;
|
|
582
|
+
description?: string;
|
|
583
|
+
[key: string]: unknown;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export interface StrategyDefinition {
|
|
587
|
+
description: string;
|
|
588
|
+
params: Record<string, StrategyParamSpec>;
|
|
589
|
+
factory: (params?: Record<string, unknown>) => SignalFunction;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export interface StrategySummary {
|
|
593
|
+
name: string;
|
|
594
|
+
description: string;
|
|
595
|
+
params: Record<string, StrategyParamSpec>;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export interface ResearchPercentileBands {
|
|
599
|
+
p5: number;
|
|
600
|
+
p25?: number;
|
|
601
|
+
p50: number;
|
|
602
|
+
p75?: number;
|
|
603
|
+
p95: number;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export interface MonteCarloResult {
|
|
607
|
+
iterations: number;
|
|
608
|
+
blockSize: number;
|
|
609
|
+
finalEquity: Required<ResearchPercentileBands>;
|
|
610
|
+
maxDrawdown: Required<ResearchPercentileBands>;
|
|
611
|
+
pathBands: Array<Pick<ResearchPercentileBands, "p5" | "p50" | "p95">>;
|
|
612
|
+
probProfit: number;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export interface PboResult {
|
|
616
|
+
pbo: number;
|
|
617
|
+
combos: number;
|
|
618
|
+
medianLogit: number;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export interface CpcvSplit {
|
|
622
|
+
train: number[];
|
|
623
|
+
test: number[];
|
|
624
|
+
testGroups: number[];
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export interface OptimizeResultEntry {
|
|
628
|
+
params: Record<string, unknown>;
|
|
629
|
+
metrics?: Partial<BacktestMetrics>;
|
|
630
|
+
error?: string;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export interface OptimizeResult {
|
|
634
|
+
results: OptimizeResultEntry[];
|
|
635
|
+
leaderboard: OptimizeResultEntry[];
|
|
636
|
+
best: OptimizeResultEntry | null;
|
|
637
|
+
}
|
|
638
|
+
|
|
545
639
|
/**
|
|
546
640
|
* Run a candle-based backtest.
|
|
547
641
|
*
|
|
@@ -550,7 +644,18 @@ export interface ArtifactPaths {
|
|
|
550
644
|
* chart-friendly replay frames/events in `replay`.
|
|
551
645
|
*/
|
|
552
646
|
export function backtest(options: BacktestOptions): BacktestResult;
|
|
647
|
+
export function backtestAsync(options: BacktestAsyncOptions): Promise<BacktestResult>;
|
|
553
648
|
export function backtestTicks(options: BacktestTickOptions): BacktestResult;
|
|
649
|
+
export function grid(spec?: Record<string, unknown | unknown[]>): Array<Record<string, unknown>>;
|
|
650
|
+
export function optimize(options: {
|
|
651
|
+
candles: Candle[];
|
|
652
|
+
signalModulePath: string;
|
|
653
|
+
parameterSets: Array<Record<string, unknown>>;
|
|
654
|
+
interval?: string;
|
|
655
|
+
backtestOptions?: Partial<BacktestOptions>;
|
|
656
|
+
concurrency?: number;
|
|
657
|
+
scoreBy?: keyof BacktestMetrics | string;
|
|
658
|
+
}): Promise<OptimizeResult>;
|
|
554
659
|
export function backtestPortfolio(options: {
|
|
555
660
|
systems: PortfolioSystem[];
|
|
556
661
|
equity?: number;
|
|
@@ -581,6 +686,55 @@ export function buildMetrics(input: {
|
|
|
581
686
|
eqSeries?: EquityPoint[];
|
|
582
687
|
}): BacktestMetrics;
|
|
583
688
|
|
|
689
|
+
export class LlmSignal {
|
|
690
|
+
constructor(options: LlmSignalOptions);
|
|
691
|
+
resolve: AsyncSignalFunction;
|
|
692
|
+
budgetMs: number;
|
|
693
|
+
onError: "skip" | "throw";
|
|
694
|
+
log: LlmDecisionLogEntry[];
|
|
695
|
+
signal(context: SignalContext): Promise<SignalResult | null>;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function registerStrategy(name: string, def: StrategyDefinition): void;
|
|
699
|
+
export function listStrategies(): StrategySummary[];
|
|
700
|
+
export function getStrategy(name: string): StrategyDefinition["factory"];
|
|
701
|
+
|
|
702
|
+
export namespace research {
|
|
703
|
+
function monteCarlo(options: {
|
|
704
|
+
tradePnls: number[];
|
|
705
|
+
equityStart?: number;
|
|
706
|
+
iterations?: number;
|
|
707
|
+
blockSize?: number;
|
|
708
|
+
seed?: string | number;
|
|
709
|
+
}): MonteCarloResult;
|
|
710
|
+
function deflatedSharpe(options: {
|
|
711
|
+
sharpe: number;
|
|
712
|
+
sampleSize: number;
|
|
713
|
+
numTrials?: number;
|
|
714
|
+
sharpeStd?: number;
|
|
715
|
+
skew?: number;
|
|
716
|
+
kurtosis?: number;
|
|
717
|
+
}): number;
|
|
718
|
+
function sweepHaircut(options: { numTrials: number; sharpeStd: number }): {
|
|
719
|
+
expectedMaxSharpe: number;
|
|
720
|
+
numTrials: number;
|
|
721
|
+
};
|
|
722
|
+
function probabilityOfBacktestOverfitting(
|
|
723
|
+
performanceMatrix: number[][],
|
|
724
|
+
options?: { groups?: number }
|
|
725
|
+
): PboResult;
|
|
726
|
+
function combinatorialPurgedSplits(options: {
|
|
727
|
+
nObservations: number;
|
|
728
|
+
nGroups?: number;
|
|
729
|
+
nTestGroups?: number;
|
|
730
|
+
embargo?: number;
|
|
731
|
+
}): CpcvSplit[];
|
|
732
|
+
function combinations(n: number, k: number): number[][];
|
|
733
|
+
function normalCdf(x: number): number;
|
|
734
|
+
function normalPpf(p: number): number;
|
|
735
|
+
function moments(values: number[]): { mean: number; std: number; skew: number; kurtosis: number };
|
|
736
|
+
}
|
|
737
|
+
|
|
584
738
|
export function getHistoricalCandles(options?: HistoricalDataOptions): Promise<Candle[]>;
|
|
585
739
|
export function backtestHistorical(options: BacktestHistoricalOptions): Promise<BacktestResult>;
|
|
586
740
|
export function fetchHistorical(
|
package/types/live.d.ts
CHANGED
|
@@ -330,6 +330,21 @@ export class LiveOrchestrator {
|
|
|
330
330
|
getStatus(): Record<string, unknown>;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
export interface DashboardServer {
|
|
334
|
+
start(): Promise<string>;
|
|
335
|
+
close(): Promise<void>;
|
|
336
|
+
server: import("node:http").Server;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function createDashboardServer(options: {
|
|
340
|
+
source: {
|
|
341
|
+
eventBus: EventBus;
|
|
342
|
+
getStatus?: () => Record<string, unknown>;
|
|
343
|
+
};
|
|
344
|
+
port?: number;
|
|
345
|
+
maxBuffer?: number;
|
|
346
|
+
}): DashboardServer;
|
|
347
|
+
|
|
333
348
|
export function createEventBus(): EventBus;
|
|
334
349
|
export function createLogger(options?: {
|
|
335
350
|
level?: "debug" | "info" | "warn" | "error" | "silent";
|
package/types/ta.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// types/ta.d.ts
|
|
2
|
+
export type Candle = {
|
|
3
|
+
time: number;
|
|
4
|
+
open: number;
|
|
5
|
+
high: number;
|
|
6
|
+
low: number;
|
|
7
|
+
close: number;
|
|
8
|
+
volume?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function ema(values: number[], period?: number): number[];
|
|
12
|
+
export function atr(bars: Candle[], period?: number): (number | undefined)[];
|
|
13
|
+
export function rsi(closes: number[], period?: number): (number | undefined)[];
|
|
14
|
+
export function macd(
|
|
15
|
+
closes: number[],
|
|
16
|
+
fast?: number,
|
|
17
|
+
slow?: number,
|
|
18
|
+
signalPeriod?: number
|
|
19
|
+
): { macd: number[]; signal: number[]; histogram: number[] };
|
|
20
|
+
export function stochastic(
|
|
21
|
+
bars: Candle[],
|
|
22
|
+
kPeriod?: number,
|
|
23
|
+
dPeriod?: number
|
|
24
|
+
): { k: (number | undefined)[]; d: (number | undefined)[] };
|
|
25
|
+
export function bollinger(
|
|
26
|
+
closes: number[],
|
|
27
|
+
period?: number,
|
|
28
|
+
mult?: number
|
|
29
|
+
): { middle: (number | undefined)[]; upper: (number | undefined)[]; lower: (number | undefined)[] };
|
|
30
|
+
export function donchian(
|
|
31
|
+
bars: Candle[],
|
|
32
|
+
period?: number
|
|
33
|
+
): { upper: (number | undefined)[]; lower: (number | undefined)[]; middle: (number | undefined)[] };
|
|
34
|
+
export function keltner(
|
|
35
|
+
bars: Candle[],
|
|
36
|
+
emaPeriod?: number,
|
|
37
|
+
atrPeriod?: number,
|
|
38
|
+
mult?: number
|
|
39
|
+
): { upper: (number | undefined)[]; lower: (number | undefined)[]; middle: (number | undefined)[] };
|
|
40
|
+
export function supertrend(
|
|
41
|
+
bars: Candle[],
|
|
42
|
+
period?: number,
|
|
43
|
+
mult?: number
|
|
44
|
+
): { line: (number | undefined)[]; direction: (number | undefined)[] };
|
|
45
|
+
export function vwap(bars: Candle[]): (number | undefined)[];
|