desic-okx-agent 0.2.1 → 0.3.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/README.en.md +90 -10
- package/README.md +76 -10
- package/dist/account/private-websocket.js +4 -4
- package/dist/account/private-websocket.js.map +1 -1
- package/dist/account/service.d.ts +12 -1
- package/dist/account/service.js +18 -0
- package/dist/account/service.js.map +1 -1
- package/dist/bars/rate-limiter.d.ts +18 -0
- package/dist/bars/rate-limiter.js +84 -0
- package/dist/bars/rate-limiter.js.map +1 -0
- package/dist/bars/schema.d.ts +36 -0
- package/dist/bars/schema.js +134 -0
- package/dist/bars/schema.js.map +1 -0
- package/dist/bars/service.d.ts +60 -0
- package/dist/bars/service.js +120 -0
- package/dist/bars/service.js.map +1 -0
- package/dist/bars/store.d.ts +105 -0
- package/dist/bars/store.js +415 -0
- package/dist/bars/store.js.map +1 -0
- package/dist/bars/timeframe.d.ts +40 -0
- package/dist/bars/timeframe.js +146 -0
- package/dist/bars/timeframe.js.map +1 -0
- package/dist/bars/types.d.ts +68 -0
- package/dist/bars/types.js +13 -0
- package/dist/bars/types.js.map +1 -0
- package/dist/cli/data-render.d.ts +37 -0
- package/dist/cli/data-render.js +143 -0
- package/dist/cli/data-render.js.map +1 -0
- package/dist/cli/doctor.js +7 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +757 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/live-render.d.ts +24 -0
- package/dist/cli/live-render.js +85 -0
- package/dist/cli/live-render.js.map +1 -0
- package/dist/cli/range.d.ts +28 -0
- package/dist/cli/range.js +63 -0
- package/dist/cli/range.js.map +1 -0
- package/dist/cli/render.js +3 -0
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/strategy-render.d.ts +36 -0
- package/dist/cli/strategy-render.js +391 -0
- package/dist/cli/strategy-render.js.map +1 -0
- package/dist/cli/width.d.ts +18 -0
- package/dist/cli/width.js +71 -0
- package/dist/cli/width.js.map +1 -0
- package/dist/config/loader.js +1 -1
- package/dist/config/schema.d.ts +7 -0
- package/dist/config/schema.js +24 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/core/okx-client.d.ts +9 -1
- package/dist/core/okx-client.js +14 -5
- package/dist/core/okx-client.js.map +1 -1
- package/dist/i18n/locale.d.ts +24 -0
- package/dist/i18n/locale.js +65 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/messages.d.ts +333 -0
- package/dist/i18n/messages.js +660 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/live/account-snapshot.d.ts +30 -0
- package/dist/live/account-snapshot.js +130 -0
- package/dist/live/account-snapshot.js.map +1 -0
- package/dist/live/cutoff-queue.d.ts +42 -0
- package/dist/live/cutoff-queue.js +69 -0
- package/dist/live/cutoff-queue.js.map +1 -0
- package/dist/live/execution-key.d.ts +23 -0
- package/dist/live/execution-key.js +31 -0
- package/dist/live/execution-key.js.map +1 -0
- package/dist/live/failures.d.ts +37 -0
- package/dist/live/failures.js +57 -0
- package/dist/live/failures.js.map +1 -0
- package/dist/live/gates.d.ts +65 -0
- package/dist/live/gates.js +136 -0
- package/dist/live/gates.js.map +1 -0
- package/dist/live/loop.d.ts +56 -0
- package/dist/live/loop.js +197 -0
- package/dist/live/loop.js.map +1 -0
- package/dist/live/preconditions.d.ts +48 -0
- package/dist/live/preconditions.js +69 -0
- package/dist/live/preconditions.js.map +1 -0
- package/dist/live/reconcile.d.ts +46 -0
- package/dist/live/reconcile.js +104 -0
- package/dist/live/reconcile.js.map +1 -0
- package/dist/live/runner.d.ts +57 -0
- package/dist/live/runner.js +160 -0
- package/dist/live/runner.js.map +1 -0
- package/dist/live/schema.d.ts +18 -0
- package/dist/live/schema.js +91 -0
- package/dist/live/schema.js.map +1 -0
- package/dist/live/service.d.ts +144 -0
- package/dist/live/service.js +303 -0
- package/dist/live/service.js.map +1 -0
- package/dist/live/session.d.ts +85 -0
- package/dist/live/session.js +234 -0
- package/dist/live/session.js.map +1 -0
- package/dist/live/sizing.d.ts +62 -0
- package/dist/live/sizing.js +79 -0
- package/dist/live/sizing.js.map +1 -0
- package/dist/live/store.d.ts +123 -0
- package/dist/live/store.js +350 -0
- package/dist/live/store.js.map +1 -0
- package/dist/live/types.d.ts +82 -0
- package/dist/live/types.js +2 -0
- package/dist/live/types.js.map +1 -0
- package/dist/market/websocket.d.ts +16 -1
- package/dist/market/websocket.js +60 -5
- package/dist/market/websocket.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +15 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/network/connectivity.d.ts +9 -1
- package/dist/network/connectivity.js +28 -1
- package/dist/network/connectivity.js.map +1 -1
- package/dist/report/chart-script.d.ts +12 -0
- package/dist/report/chart-script.js +146 -0
- package/dist/report/chart-script.js.map +1 -0
- package/dist/report/compare-html.d.ts +8 -0
- package/dist/report/compare-html.js +254 -0
- package/dist/report/compare-html.js.map +1 -0
- package/dist/report/compare-script.d.ts +12 -0
- package/dist/report/compare-script.js +109 -0
- package/dist/report/compare-script.js.map +1 -0
- package/dist/report/compare.d.ts +61 -0
- package/dist/report/compare.js +205 -0
- package/dist/report/compare.js.map +1 -0
- package/dist/report/fetch.d.ts +20 -0
- package/dist/report/fetch.js +56 -0
- package/dist/report/fetch.js.map +1 -0
- package/dist/report/html.d.ts +54 -0
- package/dist/report/html.js +641 -0
- package/dist/report/html.js.map +1 -0
- package/dist/report/open.d.ts +42 -0
- package/dist/report/open.js +114 -0
- package/dist/report/open.js.map +1 -0
- package/dist/runtime/server.d.ts +16 -1
- package/dist/runtime/server.js +112 -9
- package/dist/runtime/server.js.map +1 -1
- package/dist/setup/installer.d.ts +1 -0
- package/dist/setup/installer.js +8 -0
- package/dist/setup/installer.js.map +1 -1
- package/dist/setup/wizard.js +19 -22
- package/dist/setup/wizard.js.map +1 -1
- package/dist/strategy/constants.d.ts +23 -0
- package/dist/strategy/constants.js +24 -0
- package/dist/strategy/constants.js.map +1 -0
- package/dist/strategy/environment.d.ts +52 -0
- package/dist/strategy/environment.js +187 -0
- package/dist/strategy/environment.js.map +1 -0
- package/dist/strategy/instrument.d.ts +29 -0
- package/dist/strategy/instrument.js +39 -0
- package/dist/strategy/instrument.js.map +1 -0
- package/dist/strategy/optimize.d.ts +73 -0
- package/dist/strategy/optimize.js +113 -0
- package/dist/strategy/optimize.js.map +1 -0
- package/dist/strategy/parameter-space.d.ts +59 -0
- package/dist/strategy/parameter-space.js +221 -0
- package/dist/strategy/parameter-space.js.map +1 -0
- package/dist/strategy/python-bridge.d.ts +24 -0
- package/dist/strategy/python-bridge.js +114 -0
- package/dist/strategy/python-bridge.js.map +1 -0
- package/dist/strategy/schema.d.ts +9 -0
- package/dist/strategy/schema.js +91 -0
- package/dist/strategy/schema.js.map +1 -0
- package/dist/strategy/service.d.ts +138 -0
- package/dist/strategy/service.js +745 -0
- package/dist/strategy/service.js.map +1 -0
- package/dist/strategy/settings.d.ts +162 -0
- package/dist/strategy/settings.js +243 -0
- package/dist/strategy/settings.js.map +1 -0
- package/dist/strategy/store.d.ts +96 -0
- package/dist/strategy/store.js +367 -0
- package/dist/strategy/store.js.map +1 -0
- package/dist/strategy/templates.d.ts +11 -0
- package/dist/strategy/templates.js +134 -0
- package/dist/strategy/templates.js.map +1 -0
- package/dist/strategy/types.d.ts +111 -0
- package/dist/strategy/types.js +2 -0
- package/dist/strategy/types.js.map +1 -0
- package/dist/tools/catalog.d.ts +16 -0
- package/dist/tools/catalog.js +118 -17
- package/dist/tools/catalog.js.map +1 -1
- package/dist/trade/service.d.ts +12 -0
- package/dist/trade/service.js +24 -7
- package/dist/trade/service.js.map +1 -1
- package/dist/tui/app.d.ts +23 -0
- package/dist/tui/app.js +322 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/commands.d.ts +70 -0
- package/dist/tui/commands.js +313 -0
- package/dist/tui/commands.js.map +1 -0
- package/dist/tui/entries.d.ts +17 -0
- package/dist/tui/entries.js +24 -0
- package/dist/tui/entries.js.map +1 -0
- package/dist/tui/execute.d.ts +26 -0
- package/dist/tui/execute.js +664 -0
- package/dist/tui/execute.js.map +1 -0
- package/dist/tui/history.d.ts +17 -0
- package/dist/tui/history.js +48 -0
- package/dist/tui/history.js.map +1 -0
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.js +48 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/line-editor.d.ts +44 -0
- package/dist/tui/line-editor.js +98 -0
- package/dist/tui/line-editor.js.map +1 -0
- package/dist/tui/progress.d.ts +23 -0
- package/dist/tui/progress.js +46 -0
- package/dist/tui/progress.js.map +1 -0
- package/dist/tui/settings-editor.d.ts +18 -0
- package/dist/tui/settings-editor.js +115 -0
- package/dist/tui/settings-editor.js.map +1 -0
- package/docs/live-trading.md +455 -0
- package/docs/strategy-research.md +597 -0
- package/package.json +10 -1
- package/python/desic_strategy/__init__.py +34 -0
- package/python/desic_strategy/actions.py +158 -0
- package/python/desic_strategy/context.py +164 -0
- package/python/desic_strategy/engine.py +614 -0
- package/python/desic_strategy/indicators.py +159 -0
- package/python/desic_strategy/live.py +253 -0
- package/python/desic_strategy/policy.py +193 -0
- package/python/desic_strategy/portfolio.py +152 -0
- package/python/desic_strategy/report.py +319 -0
- package/python/desic_strategy/runner.py +574 -0
- package/python/desic_strategy/timeframe.py +150 -0
- package/python/main.py +18 -0
- package/skills/okx-live-trading/SKILL.md +117 -0
- package/skills/okx-live-trading/agents/openai.yaml +9 -0
- package/skills/okx-live-trading/references/lifecycle.md +128 -0
- package/skills/okx-strategy-research/SKILL.md +113 -0
- package/skills/okx-strategy-research/agents/openai.yaml +9 -0
- package/skills/okx-strategy-research/references/execution-semantics.md +107 -0
- package/skills/okx-strategy-research/references/field-traps.md +142 -0
- package/skills/okx-strategy-research/references/python-api.md +121 -0
- package/skills/okx-strategy-research/references/tools-and-data.md +192 -0
- package/skills/okx-trading/SKILL.md +11 -10
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { RuntimeError } from "../core/errors.js";
|
|
3
|
+
import { MAX_PAGE_ROWS } from "./constants.js";
|
|
4
|
+
import { STRATEGY_SCHEMA_SQL } from "./schema.js";
|
|
5
|
+
/** Points per stored blob. Four float64 columns per point. */
|
|
6
|
+
const CHUNK_POINTS = 8_192;
|
|
7
|
+
const BYTES_PER_POINT = 32;
|
|
8
|
+
export class StrategyStore {
|
|
9
|
+
db;
|
|
10
|
+
now;
|
|
11
|
+
constructor(db, now = Date.now) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
this.now = now;
|
|
14
|
+
this.db.exec(STRATEGY_SCHEMA_SQL);
|
|
15
|
+
this.migrate();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Adds columns introduced after a database was first created.
|
|
19
|
+
*
|
|
20
|
+
* `CREATE TABLE IF NOT EXISTS` does nothing to an existing table, so a new
|
|
21
|
+
* column is invisible to anyone upgrading — every read then fails with "no
|
|
22
|
+
* such column" until the file is deleted.
|
|
23
|
+
*/
|
|
24
|
+
migrate() {
|
|
25
|
+
const columns = new Set(this.db.prepare("PRAGMA table_info(strategy_runs)").all().map((row) => row.name));
|
|
26
|
+
// Additive only: each entry must be nullable so existing rows stay valid.
|
|
27
|
+
const additions = [["assumptions_json", "TEXT"]];
|
|
28
|
+
for (const [name, type] of additions) {
|
|
29
|
+
if (!columns.has(name))
|
|
30
|
+
this.db.exec(`ALTER TABLE strategy_runs ADD COLUMN ${name} ${type}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Marks runs left behind by a previous process as failed.
|
|
35
|
+
*
|
|
36
|
+
* A queued or running row cannot survive a restart: its worker is gone, so
|
|
37
|
+
* leaving it in place would show a run that will never progress.
|
|
38
|
+
*/
|
|
39
|
+
failInterruptedRuns() {
|
|
40
|
+
const result = this.db
|
|
41
|
+
.prepare(`UPDATE strategy_runs SET status='failed', error=?, finished_at=?
|
|
42
|
+
WHERE status IN ('queued','running')`)
|
|
43
|
+
.run("The runtime restarted before this run could finish", this.now());
|
|
44
|
+
return result.changes;
|
|
45
|
+
}
|
|
46
|
+
createRun(input) {
|
|
47
|
+
const id = `${input.kind === "backtest" ? "bt" : "opt"}_${crypto.randomBytes(6).toString("hex")}`;
|
|
48
|
+
this.db
|
|
49
|
+
.prepare(`INSERT INTO strategy_runs(
|
|
50
|
+
id,kind,status,strategy_name,strategy_hash,strategy_source,inst_id,request_json,created_at
|
|
51
|
+
) VALUES(?,?,'queued',?,?,?,?,?,?)`)
|
|
52
|
+
.run(id, input.kind, input.strategyName, sourceHash(input.source), input.source, input.instId, JSON.stringify(input.request), this.now());
|
|
53
|
+
return id;
|
|
54
|
+
}
|
|
55
|
+
markRunning(id, barCount, preloadBars, dataSnapshotId, assumptions) {
|
|
56
|
+
this.db
|
|
57
|
+
.prepare(`UPDATE strategy_runs SET status='running', started_at=?, bar_count=?, preload_bars=?,
|
|
58
|
+
data_snapshot_id=?, assumptions_json=?
|
|
59
|
+
WHERE id=? AND status='queued'`)
|
|
60
|
+
.run(this.now(), barCount, preloadBars, dataSnapshotId, assumptions === undefined ? null : JSON.stringify(assumptions), id);
|
|
61
|
+
}
|
|
62
|
+
updateProgress(id, progressPct, etaMs) {
|
|
63
|
+
this.db
|
|
64
|
+
.prepare("UPDATE strategy_runs SET progress_pct=?, eta_ms=? WHERE id=? AND status='running'")
|
|
65
|
+
.run(Math.max(0, Math.min(100, progressPct)), etaMs, id);
|
|
66
|
+
}
|
|
67
|
+
completeRun(id, metrics, equity, trades, actions) {
|
|
68
|
+
const transaction = this.db.transaction(() => {
|
|
69
|
+
this.db
|
|
70
|
+
.prepare("UPDATE strategy_runs SET status='completed', progress_pct=100, eta_ms=0, metrics_json=?, finished_at=? WHERE id=?")
|
|
71
|
+
.run(JSON.stringify(metrics), this.now(), id);
|
|
72
|
+
this.writeSeries(id, equity);
|
|
73
|
+
this.writeRows("strategy_run_trades", "trade_json", id, trades);
|
|
74
|
+
this.writeRows("strategy_run_actions", "action_json", id, actions);
|
|
75
|
+
});
|
|
76
|
+
transaction();
|
|
77
|
+
}
|
|
78
|
+
failRun(id, error) {
|
|
79
|
+
this.db
|
|
80
|
+
.prepare("UPDATE strategy_runs SET status='failed', error=?, finished_at=? WHERE id=?")
|
|
81
|
+
.run(error.slice(0, 2_000), this.now(), id);
|
|
82
|
+
}
|
|
83
|
+
cancelRun(id) {
|
|
84
|
+
const result = this.db
|
|
85
|
+
.prepare(`UPDATE strategy_runs SET status='cancelled', error=?, finished_at=?
|
|
86
|
+
WHERE id=? AND status IN ('queued','running')`)
|
|
87
|
+
.run("Cancelled by request", this.now(), id);
|
|
88
|
+
return result.changes > 0;
|
|
89
|
+
}
|
|
90
|
+
run(id) {
|
|
91
|
+
const row = this.db
|
|
92
|
+
.prepare(`SELECT id,kind,status,strategy_name,strategy_hash,inst_id,data_snapshot_id,bar_count,preload_bars,
|
|
93
|
+
progress_pct,eta_ms,metrics_json,assumptions_json,error,created_at,started_at,finished_at
|
|
94
|
+
FROM strategy_runs WHERE id=?`)
|
|
95
|
+
.get(id);
|
|
96
|
+
return row ? toSummary(row) : undefined;
|
|
97
|
+
}
|
|
98
|
+
runSource(id) {
|
|
99
|
+
const row = this.db.prepare("SELECT strategy_source FROM strategy_runs WHERE id=?").get(id);
|
|
100
|
+
return row?.strategy_source;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The request a run was queued with.
|
|
104
|
+
*
|
|
105
|
+
* Read back rather than recomputed: an optimize report needs the space and
|
|
106
|
+
* budget exactly as they were resolved, and re-deriving them from current
|
|
107
|
+
* settings would describe a different search than the one that ran.
|
|
108
|
+
*/
|
|
109
|
+
runRequest(id) {
|
|
110
|
+
const row = this.db.prepare("SELECT request_json FROM strategy_runs WHERE id=?").get(id);
|
|
111
|
+
if (!row)
|
|
112
|
+
return undefined;
|
|
113
|
+
try {
|
|
114
|
+
return JSON.parse(row.request_json);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
listRuns(options = {}) {
|
|
121
|
+
const limit = Math.max(1, Math.min(options.limit ?? 20, 200));
|
|
122
|
+
const clauses = [];
|
|
123
|
+
const values = [];
|
|
124
|
+
if (options.status) {
|
|
125
|
+
clauses.push("status=?");
|
|
126
|
+
values.push(options.status);
|
|
127
|
+
}
|
|
128
|
+
if (options.instId) {
|
|
129
|
+
clauses.push("inst_id=?");
|
|
130
|
+
values.push(options.instId);
|
|
131
|
+
}
|
|
132
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
133
|
+
const rows = this.db
|
|
134
|
+
.prepare(`SELECT id,kind,status,strategy_name,strategy_hash,inst_id,data_snapshot_id,bar_count,preload_bars,
|
|
135
|
+
progress_pct,eta_ms,metrics_json,assumptions_json,error,created_at,started_at,finished_at
|
|
136
|
+
FROM strategy_runs ${where} ORDER BY created_at DESC LIMIT ?`)
|
|
137
|
+
.all(...values, limit);
|
|
138
|
+
return rows.map(toSummary);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The most recent completed backtest of this exact source on this instrument.
|
|
142
|
+
*
|
|
143
|
+
* Matched on the source hash rather than a name or a file path: enabling a profile
|
|
144
|
+
* requires evidence that *this* code was run against *this* contract, and a hash is
|
|
145
|
+
* the only thing that cannot drift when a file is edited.
|
|
146
|
+
*
|
|
147
|
+
* A failed or cancelled run does not count. It says nothing about whether the
|
|
148
|
+
* strategy works, which is the entire reason the evidence is required.
|
|
149
|
+
*/
|
|
150
|
+
latestCompletedRun(strategyHash, instId) {
|
|
151
|
+
const row = this.db
|
|
152
|
+
.prepare(`SELECT id,kind,status,strategy_name,strategy_hash,inst_id,data_snapshot_id,bar_count,preload_bars,
|
|
153
|
+
progress_pct,eta_ms,metrics_json,assumptions_json,error,created_at,started_at,finished_at
|
|
154
|
+
FROM strategy_runs
|
|
155
|
+
WHERE strategy_hash=? AND inst_id=? AND kind='backtest' AND status='completed'
|
|
156
|
+
ORDER BY created_at DESC LIMIT 1`)
|
|
157
|
+
.get(strategyHash, instId);
|
|
158
|
+
return row ? toSummary(row) : undefined;
|
|
159
|
+
}
|
|
160
|
+
deleteRun(id) {
|
|
161
|
+
const transaction = this.db.transaction(() => {
|
|
162
|
+
this.db.prepare("DELETE FROM strategy_run_series WHERE run_id=?").run(id);
|
|
163
|
+
this.db.prepare("DELETE FROM strategy_run_trades WHERE run_id=?").run(id);
|
|
164
|
+
this.db.prepare("DELETE FROM strategy_run_actions WHERE run_id=?").run(id);
|
|
165
|
+
this.db.prepare("DELETE FROM strategy_optimization_candidates WHERE run_id=?").run(id);
|
|
166
|
+
return this.db.prepare("DELETE FROM strategy_runs WHERE id=?").run(id).changes > 0;
|
|
167
|
+
});
|
|
168
|
+
return transaction();
|
|
169
|
+
}
|
|
170
|
+
equity(id, offset = 0, limit = 1_000) {
|
|
171
|
+
const total = this.equityLength(id);
|
|
172
|
+
const bounded = Math.max(1, Math.min(limit, MAX_PAGE_ROWS));
|
|
173
|
+
const start = Math.max(0, offset);
|
|
174
|
+
const rows = [];
|
|
175
|
+
if (start >= total)
|
|
176
|
+
return { rows, offset: start, limit: bounded, total };
|
|
177
|
+
const end = Math.min(total, start + bounded);
|
|
178
|
+
const chunks = this.db
|
|
179
|
+
.prepare(`SELECT chunk_index,from_point,to_point,start_ms,step_ms,payload FROM strategy_run_series
|
|
180
|
+
WHERE run_id=? AND to_point>=? AND from_point<? ORDER BY chunk_index ASC`)
|
|
181
|
+
.all(id, start, end);
|
|
182
|
+
for (const chunk of chunks) {
|
|
183
|
+
const count = chunk.to_point - chunk.from_point + 1;
|
|
184
|
+
for (let index = 0; index < count; index += 1) {
|
|
185
|
+
const point = chunk.from_point + index;
|
|
186
|
+
if (point < start || point >= end)
|
|
187
|
+
continue;
|
|
188
|
+
const base = index * BYTES_PER_POINT;
|
|
189
|
+
rows.push({
|
|
190
|
+
timeMs: chunk.payload.readDoubleLE(base),
|
|
191
|
+
equityUsdt: chunk.payload.readDoubleLE(base + 8),
|
|
192
|
+
realizedCashUsdt: chunk.payload.readDoubleLE(base + 16),
|
|
193
|
+
unrealizedPnlUsdt: chunk.payload.readDoubleLE(base + 24)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return { rows, offset: start, limit: bounded, total };
|
|
198
|
+
}
|
|
199
|
+
equityLength(id) {
|
|
200
|
+
const row = this.db
|
|
201
|
+
.prepare("SELECT COALESCE(MAX(to_point) + 1, 0) AS total FROM strategy_run_series WHERE run_id=?")
|
|
202
|
+
.get(id);
|
|
203
|
+
return Number(row.total);
|
|
204
|
+
}
|
|
205
|
+
trades(id, offset = 0, limit = 200) {
|
|
206
|
+
return this.readRows("strategy_run_trades", "trade_json", id, offset, limit);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Records the candidate set up front, before any of it has run.
|
|
210
|
+
*
|
|
211
|
+
* Written in one transaction so a reader never sees a partial set and mistakes
|
|
212
|
+
* it for a smaller search than the one under way.
|
|
213
|
+
*/
|
|
214
|
+
seedCandidates(runId, candidates) {
|
|
215
|
+
const statement = this.db.prepare(`INSERT INTO strategy_optimization_candidates(run_id,candidate_index,params_json,status)
|
|
216
|
+
VALUES(?,?,?,'queued')`);
|
|
217
|
+
this.db.transaction(() => {
|
|
218
|
+
candidates.forEach((params, index) => statement.run(runId, index, JSON.stringify(params)));
|
|
219
|
+
})();
|
|
220
|
+
}
|
|
221
|
+
markCandidateRunning(runId, index) {
|
|
222
|
+
this.db
|
|
223
|
+
.prepare(`UPDATE strategy_optimization_candidates SET status='running'
|
|
224
|
+
WHERE run_id=? AND candidate_index=? AND status='queued'`)
|
|
225
|
+
.run(runId, index);
|
|
226
|
+
}
|
|
227
|
+
completeCandidate(runId, index, result) {
|
|
228
|
+
this.db
|
|
229
|
+
.prepare(`UPDATE strategy_optimization_candidates
|
|
230
|
+
SET status='completed', train_metrics_json=?, validation_metrics_json=?,
|
|
231
|
+
rank_score=?, rank_basis=?, verdict=?, error=NULL
|
|
232
|
+
WHERE run_id=? AND candidate_index=?`)
|
|
233
|
+
.run(JSON.stringify(result.train), JSON.stringify(result.validation),
|
|
234
|
+
// A non-finite score would sort unpredictably in SQLite; it is stored as
|
|
235
|
+
// null so a failed measurement ranks last instead of first.
|
|
236
|
+
Number.isFinite(result.rankScore) ? result.rankScore : null, result.rankBasis, result.verdict, runId, index);
|
|
237
|
+
}
|
|
238
|
+
failCandidate(runId, index, error) {
|
|
239
|
+
this.db
|
|
240
|
+
.prepare(`UPDATE strategy_optimization_candidates SET status='failed', error=?, rank_score=NULL
|
|
241
|
+
WHERE run_id=? AND candidate_index=?`)
|
|
242
|
+
.run(error.slice(0, 2_000), runId, index);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Candidates in rank order, best first.
|
|
246
|
+
*
|
|
247
|
+
* `rank_score IS NULL` sorts last explicitly: SQLite orders nulls first under
|
|
248
|
+
* `DESC`, which would put every failure above the winner.
|
|
249
|
+
*/
|
|
250
|
+
candidates(runId, limit = 200) {
|
|
251
|
+
const bounded = Math.max(1, Math.min(limit, MAX_PAGE_ROWS));
|
|
252
|
+
const rows = this.db
|
|
253
|
+
.prepare(`SELECT candidate_index,params_json,status,train_metrics_json,validation_metrics_json,
|
|
254
|
+
rank_score,rank_basis,verdict,error
|
|
255
|
+
FROM strategy_optimization_candidates WHERE run_id=?
|
|
256
|
+
ORDER BY rank_score IS NULL ASC, rank_score DESC, candidate_index ASC
|
|
257
|
+
LIMIT ?`)
|
|
258
|
+
.all(runId, bounded);
|
|
259
|
+
return rows.map(toCandidate);
|
|
260
|
+
}
|
|
261
|
+
candidateCounts(runId) {
|
|
262
|
+
const rows = this.db
|
|
263
|
+
.prepare("SELECT status, COUNT(*) AS count FROM strategy_optimization_candidates WHERE run_id=? GROUP BY status")
|
|
264
|
+
.all(runId);
|
|
265
|
+
const counts = { queued: 0, running: 0, completed: 0, failed: 0, total: 0 };
|
|
266
|
+
for (const row of rows) {
|
|
267
|
+
counts[row.status] = Number(row.count);
|
|
268
|
+
counts.total += Number(row.count);
|
|
269
|
+
}
|
|
270
|
+
return counts;
|
|
271
|
+
}
|
|
272
|
+
actions(id, offset = 0, limit = 200) {
|
|
273
|
+
return this.readRows("strategy_run_actions", "action_json", id, offset, limit);
|
|
274
|
+
}
|
|
275
|
+
writeSeries(id, equity) {
|
|
276
|
+
if (equity.length === 0)
|
|
277
|
+
return;
|
|
278
|
+
const statement = this.db.prepare(`INSERT INTO strategy_run_series(run_id,chunk_index,from_point,to_point,start_ms,step_ms,payload)
|
|
279
|
+
VALUES(?,?,?,?,?,?,?)`);
|
|
280
|
+
let chunkIndex = 0;
|
|
281
|
+
for (let start = 0; start < equity.length; start += CHUNK_POINTS) {
|
|
282
|
+
const slice = equity.slice(start, start + CHUNK_POINTS);
|
|
283
|
+
const payload = Buffer.allocUnsafe(slice.length * BYTES_PER_POINT);
|
|
284
|
+
slice.forEach((point, index) => {
|
|
285
|
+
const base = index * BYTES_PER_POINT;
|
|
286
|
+
payload.writeDoubleLE(point.timeMs, base);
|
|
287
|
+
payload.writeDoubleLE(point.equityUsdt, base + 8);
|
|
288
|
+
payload.writeDoubleLE(point.realizedCashUsdt, base + 16);
|
|
289
|
+
payload.writeDoubleLE(point.unrealizedPnlUsdt, base + 24);
|
|
290
|
+
});
|
|
291
|
+
const first = slice[0];
|
|
292
|
+
const step = slice.length > 1 ? slice[1].timeMs - first.timeMs : 0;
|
|
293
|
+
statement.run(id, chunkIndex, start, start + slice.length - 1, first.timeMs, step, payload);
|
|
294
|
+
chunkIndex += 1;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
writeRows(table, column, id, rows) {
|
|
298
|
+
if (rows.length === 0)
|
|
299
|
+
return;
|
|
300
|
+
const statement = this.db.prepare(`INSERT INTO ${table}(run_id,seq,${column}) VALUES(?,?,?)`);
|
|
301
|
+
rows.forEach((row, index) => statement.run(id, index, JSON.stringify(row)));
|
|
302
|
+
}
|
|
303
|
+
readRows(table, column, id, offset, limit) {
|
|
304
|
+
const bounded = Math.max(1, Math.min(limit, MAX_PAGE_ROWS));
|
|
305
|
+
const start = Math.max(0, offset);
|
|
306
|
+
const totalRow = this.db.prepare(`SELECT COUNT(*) AS total FROM ${table} WHERE run_id=?`).get(id);
|
|
307
|
+
const rows = this.db
|
|
308
|
+
.prepare(`SELECT ${column} AS value FROM ${table} WHERE run_id=? AND seq>=? ORDER BY seq ASC LIMIT ?`)
|
|
309
|
+
.all(id, start, bounded);
|
|
310
|
+
return {
|
|
311
|
+
rows: rows.map((row) => JSON.parse(row.value)),
|
|
312
|
+
offset: start,
|
|
313
|
+
limit: bounded,
|
|
314
|
+
total: Number(totalRow.total)
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
export function sourceHash(source) {
|
|
319
|
+
return crypto.createHash("sha256").update(source, "utf8").digest("hex");
|
|
320
|
+
}
|
|
321
|
+
function toSummary(row) {
|
|
322
|
+
const metrics = row.metrics_json ? JSON.parse(String(row.metrics_json)) : null;
|
|
323
|
+
const assumptions = row.assumptions_json
|
|
324
|
+
? JSON.parse(String(row.assumptions_json))
|
|
325
|
+
: null;
|
|
326
|
+
return {
|
|
327
|
+
id: String(row.id),
|
|
328
|
+
kind: String(row.kind),
|
|
329
|
+
status: String(row.status),
|
|
330
|
+
strategyName: String(row.strategy_name),
|
|
331
|
+
strategyHash: String(row.strategy_hash),
|
|
332
|
+
instId: String(row.inst_id),
|
|
333
|
+
dataSnapshotId: row.data_snapshot_id === null ? null : String(row.data_snapshot_id),
|
|
334
|
+
barCount: Number(row.bar_count),
|
|
335
|
+
preloadBars: Number(row.preload_bars),
|
|
336
|
+
progressPct: Number(row.progress_pct),
|
|
337
|
+
etaMs: row.eta_ms === null ? null : Number(row.eta_ms),
|
|
338
|
+
metrics,
|
|
339
|
+
assumptions,
|
|
340
|
+
error: row.error === null ? null : String(row.error),
|
|
341
|
+
createdAt: Number(row.created_at),
|
|
342
|
+
startedAt: row.started_at === null ? null : Number(row.started_at),
|
|
343
|
+
finishedAt: row.finished_at === null ? null : Number(row.finished_at)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function toCandidate(row) {
|
|
347
|
+
return {
|
|
348
|
+
candidateIndex: Number(row.candidate_index),
|
|
349
|
+
params: JSON.parse(String(row.params_json)),
|
|
350
|
+
status: String(row.status),
|
|
351
|
+
train: row.train_metrics_json ? JSON.parse(String(row.train_metrics_json)) : null,
|
|
352
|
+
validation: row.validation_metrics_json
|
|
353
|
+
? JSON.parse(String(row.validation_metrics_json))
|
|
354
|
+
: null,
|
|
355
|
+
rankScore: row.rank_score === null || row.rank_score === undefined ? null : Number(row.rank_score),
|
|
356
|
+
rankBasis: row.rank_basis === null || row.rank_basis === undefined ? null : String(row.rank_basis),
|
|
357
|
+
verdict: row.verdict === null || row.verdict === undefined ? null : String(row.verdict),
|
|
358
|
+
error: row.error === null || row.error === undefined ? null : String(row.error)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
export function requireRun(store, id) {
|
|
362
|
+
const run = store.run(id);
|
|
363
|
+
if (!run)
|
|
364
|
+
throw new RuntimeError("NOT_FOUND", `Unknown strategy run '${id}'`);
|
|
365
|
+
return run;
|
|
366
|
+
}
|
|
367
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/strategy/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starting points for a new strategy.
|
|
3
|
+
*
|
|
4
|
+
* Each one is valid under the source policy and demonstrates the contract that
|
|
5
|
+
* is easiest to get wrong: read documented fields directly, return exactly one
|
|
6
|
+
* decision per bar, and let the host size the position.
|
|
7
|
+
*/
|
|
8
|
+
export declare const TEMPLATE_NAMES: readonly ["blank", "ema-trend", "atr-breakout", "bollinger-reversion"];
|
|
9
|
+
export type TemplateName = (typeof TEMPLATE_NAMES)[number];
|
|
10
|
+
export declare function isTemplateName(value: string): value is TemplateName;
|
|
11
|
+
export declare function template(name: TemplateName): string;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starting points for a new strategy.
|
|
3
|
+
*
|
|
4
|
+
* Each one is valid under the source policy and demonstrates the contract that
|
|
5
|
+
* is easiest to get wrong: read documented fields directly, return exactly one
|
|
6
|
+
* decision per bar, and let the host size the position.
|
|
7
|
+
*/
|
|
8
|
+
export const TEMPLATE_NAMES = ["blank", "ema-trend", "atr-breakout", "bollinger-reversion"];
|
|
9
|
+
const BLANK = `"""Blank strategy skeleton.
|
|
10
|
+
|
|
11
|
+
on_bar runs after every confirmed one-minute close. Return exactly one decision.
|
|
12
|
+
A market decision fills at the next bar's open; the host derives the size.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def on_bar(ctx):
|
|
17
|
+
# ctx.market.bars(ctx.instrument_id, "1m", lookback=N) -> confirmed bars
|
|
18
|
+
# ctx.indicators.ema(ctx.instrument_id, "1m", period) -> None until warm
|
|
19
|
+
# ctx.portfolio.position(ctx.instrument_id, "long") -> Position or None
|
|
20
|
+
return ctx.no_action("no rule implemented yet")
|
|
21
|
+
`;
|
|
22
|
+
const EMA_TREND = `"""Dual EMA trend following.
|
|
23
|
+
|
|
24
|
+
Enters long when the fast EMA leads the slow one and exits on the reverse cross.
|
|
25
|
+
Both values are None until enough bars exist, so the warm-up is explicit.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def on_bar(ctx):
|
|
30
|
+
fast_period = int(ctx.params.get("fastPeriod", 20))
|
|
31
|
+
slow_period = int(ctx.params.get("slowPeriod", 60))
|
|
32
|
+
|
|
33
|
+
fast = ctx.indicators.ema(ctx.instrument_id, "1m", fast_period)
|
|
34
|
+
slow = ctx.indicators.ema(ctx.instrument_id, "1m", slow_period)
|
|
35
|
+
if fast is None or slow is None:
|
|
36
|
+
return ctx.no_action("indicators warming up")
|
|
37
|
+
|
|
38
|
+
position = ctx.portfolio.position(ctx.instrument_id, "long")
|
|
39
|
+
if position is None and fast > slow:
|
|
40
|
+
return ctx.open_long("fast EMA above slow EMA")
|
|
41
|
+
if position is not None and fast < slow:
|
|
42
|
+
return ctx.close_long("fast EMA below slow EMA")
|
|
43
|
+
return ctx.no_action("holding current state")
|
|
44
|
+
`;
|
|
45
|
+
const ATR_BREAKOUT = `"""Breakout with an ATR-scaled protective stop.
|
|
46
|
+
|
|
47
|
+
The stop is attached to the opening decision as an absolute price, so the first
|
|
48
|
+
minute of the position is already covered.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def on_bar(ctx):
|
|
53
|
+
lookback = int(ctx.params.get("lookback", 60))
|
|
54
|
+
atr_period = int(ctx.params.get("atrPeriod", 14))
|
|
55
|
+
stop_multiple = float(ctx.params.get("stopMultiple", 3.0))
|
|
56
|
+
|
|
57
|
+
bars = ctx.market.bars(ctx.instrument_id, "1m", lookback=lookback)
|
|
58
|
+
atr = ctx.indicators.atr(ctx.instrument_id, "1m", atr_period)
|
|
59
|
+
if len(bars) < lookback or atr is None:
|
|
60
|
+
return ctx.no_action("waiting for a full lookback window")
|
|
61
|
+
|
|
62
|
+
# The current bar is the last item, so compare against the prior window.
|
|
63
|
+
prior = bars[:-1]
|
|
64
|
+
highest = max(bar.high for bar in prior)
|
|
65
|
+
lowest = min(bar.low for bar in prior)
|
|
66
|
+
close = bars[-1].close
|
|
67
|
+
|
|
68
|
+
position = ctx.portfolio.position(ctx.instrument_id, "long")
|
|
69
|
+
if position is None and close > highest:
|
|
70
|
+
return ctx.open_long(
|
|
71
|
+
"closed above the prior range high",
|
|
72
|
+
protection={"stopLossPrice": close - atr * stop_multiple},
|
|
73
|
+
)
|
|
74
|
+
if position is not None and close < lowest:
|
|
75
|
+
return ctx.close_long("closed below the prior range low")
|
|
76
|
+
return ctx.no_action("inside the prior range")
|
|
77
|
+
`;
|
|
78
|
+
const BOLLINGER_REVERSION = `"""Mean reversion around a moving average band.
|
|
79
|
+
|
|
80
|
+
Bands are computed from the visible confirmed window, so nothing here can read a
|
|
81
|
+
bar later than the current cutoff.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
import math
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _mean(values):
|
|
88
|
+
return sum(values) / len(values)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _stdev(values, mean):
|
|
92
|
+
if len(values) < 2:
|
|
93
|
+
return 0.0
|
|
94
|
+
variance = sum((value - mean) ** 2 for value in values) / (len(values) - 1)
|
|
95
|
+
return math.sqrt(variance)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def on_bar(ctx):
|
|
99
|
+
period = int(ctx.params.get("period", 60))
|
|
100
|
+
width = float(ctx.params.get("width", 2.0))
|
|
101
|
+
|
|
102
|
+
bars = ctx.market.bars(ctx.instrument_id, "1m", lookback=period)
|
|
103
|
+
if len(bars) < period:
|
|
104
|
+
return ctx.no_action("waiting for a full window")
|
|
105
|
+
|
|
106
|
+
closes = [bar.close for bar in bars]
|
|
107
|
+
mean = _mean(closes)
|
|
108
|
+
deviation = _stdev(closes, mean)
|
|
109
|
+
if deviation <= 0:
|
|
110
|
+
return ctx.no_action("no dispersion to trade")
|
|
111
|
+
|
|
112
|
+
close = closes[-1]
|
|
113
|
+
lower = mean - deviation * width
|
|
114
|
+
|
|
115
|
+
position = ctx.portfolio.position(ctx.instrument_id, "long")
|
|
116
|
+
if position is None and close < lower:
|
|
117
|
+
return ctx.open_long("closed below the lower band")
|
|
118
|
+
if position is not None and close >= mean:
|
|
119
|
+
return ctx.close_long("reverted to the mean")
|
|
120
|
+
return ctx.no_action("inside the bands")
|
|
121
|
+
`;
|
|
122
|
+
const TEMPLATES = {
|
|
123
|
+
blank: BLANK,
|
|
124
|
+
"ema-trend": EMA_TREND,
|
|
125
|
+
"atr-breakout": ATR_BREAKOUT,
|
|
126
|
+
"bollinger-reversion": BOLLINGER_REVERSION
|
|
127
|
+
};
|
|
128
|
+
export function isTemplateName(value) {
|
|
129
|
+
return Object.hasOwn(TEMPLATES, value);
|
|
130
|
+
}
|
|
131
|
+
export function template(name) {
|
|
132
|
+
return TEMPLATES[name];
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/strategy/templates.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,qBAAqB,CAAU,CAAC;AAIrG,MAAM,KAAK,GAAG;;;;;;;;;;;;CAYb,CAAC;AAEF,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;CAsBjB,CAAC;AAEF,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCpB,CAAC;AAEF,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2C3B,CAAC;AAEF,MAAM,SAAS,GAAiC;IAC9C,KAAK,EAAE,KAAK;IACZ,WAAW,EAAE,SAAS;IACtB,cAAc,EAAE,YAAY;IAC5B,qBAAqB,EAAE,mBAAmB;CAC3C,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAkB;IACzC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export type RunKind = "backtest" | "optimize";
|
|
2
|
+
export type RunStatus = "queued" | "running" | "completed" | "failed" | "cancelled";
|
|
3
|
+
export interface BacktestRequest {
|
|
4
|
+
instId: string;
|
|
5
|
+
source: string;
|
|
6
|
+
strategyName?: string;
|
|
7
|
+
params?: Record<string, unknown>;
|
|
8
|
+
intervals?: string[];
|
|
9
|
+
fromMs?: number;
|
|
10
|
+
toMs?: number;
|
|
11
|
+
days?: number;
|
|
12
|
+
preloadBars?: number;
|
|
13
|
+
initialEquityUsdt?: number;
|
|
14
|
+
leverage?: number;
|
|
15
|
+
marginSafetyMultiplier?: number;
|
|
16
|
+
entryBudgetUsdt?: number;
|
|
17
|
+
entryBudgetPct?: number;
|
|
18
|
+
takerFeeRate?: number;
|
|
19
|
+
makerFeeRate?: number;
|
|
20
|
+
entrySlippageBps?: number;
|
|
21
|
+
exitSlippageBps?: number;
|
|
22
|
+
closeAtEnd?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface RunSummary {
|
|
25
|
+
id: string;
|
|
26
|
+
kind: RunKind;
|
|
27
|
+
status: RunStatus;
|
|
28
|
+
strategyName: string;
|
|
29
|
+
strategyHash: string;
|
|
30
|
+
instId: string;
|
|
31
|
+
dataSnapshotId: string | null;
|
|
32
|
+
barCount: number;
|
|
33
|
+
preloadBars: number;
|
|
34
|
+
progressPct: number;
|
|
35
|
+
etaMs: number | null;
|
|
36
|
+
metrics: Record<string, unknown> | null;
|
|
37
|
+
/** Assumptions this run used, as sent to the engine. */
|
|
38
|
+
assumptions: Record<string, unknown> | null;
|
|
39
|
+
error: string | null;
|
|
40
|
+
createdAt: number;
|
|
41
|
+
startedAt: number | null;
|
|
42
|
+
finishedAt: number | null;
|
|
43
|
+
}
|
|
44
|
+
export interface EquityPoint {
|
|
45
|
+
timeMs: number;
|
|
46
|
+
equityUsdt: number;
|
|
47
|
+
realizedCashUsdt: number;
|
|
48
|
+
unrealizedPnlUsdt: number;
|
|
49
|
+
}
|
|
50
|
+
export interface PolicyViolation {
|
|
51
|
+
line: number;
|
|
52
|
+
code: string;
|
|
53
|
+
message: string;
|
|
54
|
+
}
|
|
55
|
+
export interface ValidationResult {
|
|
56
|
+
valid: boolean;
|
|
57
|
+
violations: PolicyViolation[];
|
|
58
|
+
}
|
|
59
|
+
export interface Page<T> {
|
|
60
|
+
rows: T[];
|
|
61
|
+
offset: number;
|
|
62
|
+
limit: number;
|
|
63
|
+
total: number;
|
|
64
|
+
}
|
|
65
|
+
/** An optimize request: a backtest, plus what to vary and how hard to look. */
|
|
66
|
+
export interface OptimizeRequest extends BacktestRequest {
|
|
67
|
+
/** Per-parameter { min, max, step }. Validated against the strategy's own params. */
|
|
68
|
+
space: Record<string, unknown>;
|
|
69
|
+
/** Candidates to evaluate. Defaults to DEFAULT_BUDGET. */
|
|
70
|
+
budget?: number;
|
|
71
|
+
}
|
|
72
|
+
export type CandidateStatus = "queued" | "running" | "completed" | "failed";
|
|
73
|
+
export interface CandidateRow {
|
|
74
|
+
candidateIndex: number;
|
|
75
|
+
params: Record<string, number>;
|
|
76
|
+
status: CandidateStatus;
|
|
77
|
+
/** Metrics over the searched segment. */
|
|
78
|
+
train: Record<string, unknown> | null;
|
|
79
|
+
/** Metrics over the held-back segment. These are the reportable ones. */
|
|
80
|
+
validation: Record<string, unknown> | null;
|
|
81
|
+
rankScore: number | null;
|
|
82
|
+
rankBasis: string | null;
|
|
83
|
+
verdict: string | null;
|
|
84
|
+
error: string | null;
|
|
85
|
+
}
|
|
86
|
+
/** Everything needed to render an optimize report. */
|
|
87
|
+
export interface OptimizationReport {
|
|
88
|
+
run: RunSummary;
|
|
89
|
+
space: Record<string, {
|
|
90
|
+
min: number;
|
|
91
|
+
max: number;
|
|
92
|
+
step: number;
|
|
93
|
+
}>;
|
|
94
|
+
budget: number;
|
|
95
|
+
combinations: number;
|
|
96
|
+
/** Bar boundaries of the two segments, so the split is auditable. */
|
|
97
|
+
split: {
|
|
98
|
+
trainStartMs: number;
|
|
99
|
+
trainEndMs: number;
|
|
100
|
+
validationStartMs: number;
|
|
101
|
+
validationEndMs: number;
|
|
102
|
+
trainBars: number;
|
|
103
|
+
validationBars: number;
|
|
104
|
+
} | null;
|
|
105
|
+
/** Candidates in rank order, best first. Failures sort last. */
|
|
106
|
+
candidates: CandidateRow[];
|
|
107
|
+
completed: number;
|
|
108
|
+
failed: number;
|
|
109
|
+
/** Which ranking formula the leader used. */
|
|
110
|
+
rankBasis: string | null;
|
|
111
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/strategy/types.ts"],"names":[],"mappings":""}
|