tradelab 1.0.1 → 1.2.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 +112 -0
- package/README.md +188 -328
- 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 +1917 -1005
- package/dist/cjs/live.cjs +536 -25
- package/dist/cjs/ta.cjs +339 -0
- package/docs/README.md +32 -66
- package/docs/api-reference.md +283 -112
- package/docs/backtest-engine.md +210 -252
- package/docs/data-reporting-cli.md +114 -156
- package/docs/examples.md +6 -6
- package/docs/live-trading.md +263 -92
- package/docs/mcp.md +285 -0
- package/docs/research.md +157 -0
- package/examples/liveDashboard.js +33 -0
- package/examples/llmSignal.js +33 -0
- package/examples/mcpLiveTrading.js +77 -0
- package/examples/optimize.js +25 -0
- package/package.json +26 -4
- 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 +110 -0
- package/src/engine/optimizeWorker.js +67 -0
- package/src/engine/portfolio.js +4 -1
- package/src/engine/walkForward.js +1 -0
- package/src/index.js +9 -0
- package/src/live/dashboard/server.js +179 -0
- package/src/live/engine/liveEngine.js +2 -2
- package/src/live/engine/paperEngine.js +5 -0
- package/src/live/index.js +3 -0
- package/src/live/session.js +402 -0
- package/src/mcp/liveTools.js +179 -0
- package/src/mcp/schemas.js +167 -0
- package/src/mcp/server.js +35 -0
- package/src/mcp/tools.js +265 -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 +661 -0
- package/types/index.d.ts +179 -0
- package/types/live.d.ts +114 -0
- package/types/mcp.d.ts +17 -0
- package/types/ta.d.ts +45 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/ta/channels.js
|
|
2
|
+
import { ema, atr } from "../utils/indicators.js";
|
|
3
|
+
|
|
4
|
+
function rollingMean(values, period, i) {
|
|
5
|
+
let sum = 0;
|
|
6
|
+
for (let j = i - period + 1; j <= i; j += 1) sum += values[j];
|
|
7
|
+
return sum / period;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Bollinger Bands. `mult` standard deviations around the SMA middle band.
|
|
12
|
+
*/
|
|
13
|
+
export function bollinger(closes, period = 20, mult = 2) {
|
|
14
|
+
const middle = new Array(closes.length).fill(undefined);
|
|
15
|
+
const upper = new Array(closes.length).fill(undefined);
|
|
16
|
+
const lower = new Array(closes.length).fill(undefined);
|
|
17
|
+
for (let i = period - 1; i < closes.length; i += 1) {
|
|
18
|
+
const avg = rollingMean(closes, period, i);
|
|
19
|
+
let variance = 0;
|
|
20
|
+
for (let j = i - period + 1; j <= i; j += 1) variance += (closes[j] - avg) ** 2;
|
|
21
|
+
const sd = Math.sqrt(variance / period);
|
|
22
|
+
middle[i] = avg;
|
|
23
|
+
upper[i] = avg + mult * sd;
|
|
24
|
+
lower[i] = avg - mult * sd;
|
|
25
|
+
}
|
|
26
|
+
return { middle, upper, lower };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Donchian channel: rolling highest-high / lowest-low over `period` bars.
|
|
31
|
+
*/
|
|
32
|
+
export function donchian(bars, period = 20) {
|
|
33
|
+
const upper = new Array(bars.length).fill(undefined);
|
|
34
|
+
const lower = new Array(bars.length).fill(undefined);
|
|
35
|
+
const middle = new Array(bars.length).fill(undefined);
|
|
36
|
+
for (let i = period - 1; i < bars.length; i += 1) {
|
|
37
|
+
let hh = -Infinity;
|
|
38
|
+
let ll = Infinity;
|
|
39
|
+
for (let j = i - period + 1; j <= i; j += 1) {
|
|
40
|
+
if (bars[j].high > hh) hh = bars[j].high;
|
|
41
|
+
if (bars[j].low < ll) ll = bars[j].low;
|
|
42
|
+
}
|
|
43
|
+
upper[i] = hh;
|
|
44
|
+
lower[i] = ll;
|
|
45
|
+
middle[i] = (hh + ll) / 2;
|
|
46
|
+
}
|
|
47
|
+
return { upper, lower, middle };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Keltner channel: EMA middle, +/- mult * ATR width.
|
|
52
|
+
*/
|
|
53
|
+
export function keltner(bars, emaPeriod = 20, atrPeriod = 14, mult = 2) {
|
|
54
|
+
const closes = bars.map((b) => b.close);
|
|
55
|
+
const mid = ema(closes, emaPeriod);
|
|
56
|
+
const range = atr(bars, atrPeriod);
|
|
57
|
+
const upper = new Array(bars.length).fill(undefined);
|
|
58
|
+
const lower = new Array(bars.length).fill(undefined);
|
|
59
|
+
const middle = new Array(bars.length).fill(undefined);
|
|
60
|
+
for (let i = 0; i < bars.length; i += 1) {
|
|
61
|
+
if (range[i] === undefined) continue;
|
|
62
|
+
middle[i] = mid[i];
|
|
63
|
+
upper[i] = mid[i] + mult * range[i];
|
|
64
|
+
lower[i] = mid[i] - mult * range[i];
|
|
65
|
+
}
|
|
66
|
+
return { upper, lower, middle };
|
|
67
|
+
}
|
package/src/ta/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/ta/index.js
|
|
2
|
+
export {
|
|
3
|
+
ema,
|
|
4
|
+
atr,
|
|
5
|
+
swingHigh,
|
|
6
|
+
swingLow,
|
|
7
|
+
detectFVG,
|
|
8
|
+
lastSwing,
|
|
9
|
+
structureState,
|
|
10
|
+
} from "../utils/indicators.js";
|
|
11
|
+
|
|
12
|
+
export { rsi, macd, stochastic } from "./oscillators.js";
|
|
13
|
+
|
|
14
|
+
export { bollinger, donchian, keltner } from "./channels.js";
|
|
15
|
+
|
|
16
|
+
export { supertrend, vwap } from "./trend.js";
|
|
@@ -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
|
+
}
|