tradelab 1.2.1 → 1.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/CHANGELOG.md +42 -0
- package/README.md +17 -5
- package/bin/tradelab.js +36 -0
- package/dist/cjs/index.cjs +90 -0
- package/dist/cjs/live.cjs +242 -51
- package/docs/live-trading.md +130 -0
- package/docs/mcp.md +89 -20
- package/examples/agentResearchLoop.js +188 -0
- package/examples/multiSymbolPortfolio.js +122 -0
- package/package.json +1 -1
- package/src/cli/runPreset.js +42 -0
- package/src/index.js +2 -0
- package/src/live/engine/riskManager.js +38 -0
- package/src/live/index.js +1 -0
- package/src/live/notify.js +42 -0
- package/src/live/session.js +166 -52
- package/src/mcp/liveTools.js +28 -24
- package/src/mcp/researchSession.js +24 -0
- package/src/mcp/schemas.js +5 -1
- package/src/mcp/tools.js +27 -2
- package/src/reporting/summarize.js +43 -0
- package/src/research/store.js +67 -0
- package/types/index.d.ts +30 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/research/store.js
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_DIR = ".tradelab/research";
|
|
6
|
+
|
|
7
|
+
function fileFor(dir, id) {
|
|
8
|
+
if (!/^[\w.-]+$/.test(String(id))) throw new Error(`invalid research id: ${id}`);
|
|
9
|
+
return join(dir, `${id}.json`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function load(dir, id) {
|
|
13
|
+
try {
|
|
14
|
+
const raw = await readFile(fileFor(dir, id), "utf8");
|
|
15
|
+
return JSON.parse(raw);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function save(dir, record) {
|
|
22
|
+
await mkdir(dir, { recursive: true });
|
|
23
|
+
await writeFile(fileFor(dir, record.id), JSON.stringify(record, null, 2));
|
|
24
|
+
return record;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function bestSharpe(entries) {
|
|
28
|
+
let best = null;
|
|
29
|
+
for (const e of entries) {
|
|
30
|
+
const s = e.metrics?.sharpe;
|
|
31
|
+
if (Number.isFinite(s) && (best === null || s > best.sharpe)) best = { sharpe: s, params: e.params };
|
|
32
|
+
}
|
|
33
|
+
return best;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createResearchStore({ dir = DEFAULT_DIR } = {}) {
|
|
37
|
+
return {
|
|
38
|
+
async open(id, goal = "") {
|
|
39
|
+
const existing = await load(dir, id);
|
|
40
|
+
if (existing) return existing;
|
|
41
|
+
const record = { id, goal, createdAt: new Date().toISOString(), closedAt: null, entries: [] };
|
|
42
|
+
return save(dir, record);
|
|
43
|
+
},
|
|
44
|
+
async log(id, { hypothesis = "", params = {}, metrics = {}, verdict = null } = {}) {
|
|
45
|
+
const record = (await load(dir, id)) || { id, goal: "", createdAt: new Date().toISOString(), closedAt: null, entries: [] };
|
|
46
|
+
const entry = { at: new Date().toISOString(), hypothesis, params, metrics, verdict };
|
|
47
|
+
record.entries.push(entry);
|
|
48
|
+
await save(dir, record);
|
|
49
|
+
return entry;
|
|
50
|
+
},
|
|
51
|
+
async recall(id, limit = 10) {
|
|
52
|
+
const record = (await load(dir, id)) || { goal: "", entries: [] };
|
|
53
|
+
const entries = record.entries.slice(-limit);
|
|
54
|
+
const best = bestSharpe(record.entries);
|
|
55
|
+
const flagged = record.entries.filter((e) => e.verdict?.overfit).length;
|
|
56
|
+
const summary = record.entries.length
|
|
57
|
+
? `Best Sharpe so far: ${best ? best.sharpe.toFixed(2) : "n/a"}${best ? ` via ${JSON.stringify(best.params)}` : ""}. ${flagged} of ${record.entries.length} flagged overfit.`
|
|
58
|
+
: "No entries logged yet.";
|
|
59
|
+
return { goal: record.goal, entries, summary };
|
|
60
|
+
},
|
|
61
|
+
async close(id) {
|
|
62
|
+
const record = (await load(dir, id)) || { id, goal: "", createdAt: new Date().toISOString(), entries: [] };
|
|
63
|
+
record.closedAt = new Date().toISOString();
|
|
64
|
+
return save(dir, record);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -724,6 +724,36 @@ export function registerStrategy(name: string, def: StrategyDefinition): void;
|
|
|
724
724
|
export function listStrategies(): StrategySummary[];
|
|
725
725
|
export function getStrategy(name: string): StrategyDefinition["factory"];
|
|
726
726
|
|
|
727
|
+
export interface ResearchEntry {
|
|
728
|
+
at: string;
|
|
729
|
+
hypothesis?: string;
|
|
730
|
+
params?: Record<string, unknown>;
|
|
731
|
+
metrics?: Record<string, unknown>;
|
|
732
|
+
verdict?: Record<string, unknown> | null;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
export interface ResearchRecord {
|
|
736
|
+
id: string;
|
|
737
|
+
goal: string;
|
|
738
|
+
createdAt: string;
|
|
739
|
+
closedAt: string | null;
|
|
740
|
+
entries: ResearchEntry[];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export interface ResearchStore {
|
|
744
|
+
open(id: string, goal?: string): Promise<ResearchRecord>;
|
|
745
|
+
log(id: string, options?: {
|
|
746
|
+
hypothesis?: string;
|
|
747
|
+
params?: Record<string, unknown>;
|
|
748
|
+
metrics?: Record<string, unknown>;
|
|
749
|
+
verdict?: Record<string, unknown> | null;
|
|
750
|
+
}): Promise<ResearchEntry>;
|
|
751
|
+
recall(id: string, limit?: number): Promise<{ goal: string; entries: ResearchEntry[]; summary: string }>;
|
|
752
|
+
close(id: string): Promise<ResearchRecord>;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
export function createResearchStore(options?: { dir?: string }): ResearchStore;
|
|
756
|
+
|
|
727
757
|
export namespace research {
|
|
728
758
|
function monteCarlo(options: {
|
|
729
759
|
tradePnls: number[];
|