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.
@@ -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[];