forked from koala73/worldmonitor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstock-backtest.ts
More file actions
89 lines (80 loc) · 2.8 KB
/
stock-backtest.ts
File metadata and controls
89 lines (80 loc) · 2.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import {
MarketServiceClient,
type BacktestStockResponse,
} from '@/generated/client/worldmonitor/market/v1/service_client';
const client = new MarketServiceClient('', {
fetch: (...args: Parameters<typeof fetch>) => globalThis.fetch(...args),
});
export type StockBacktestResult = BacktestStockResponse;
const DEFAULT_LIMIT = 4;
const DEFAULT_EVAL_WINDOW_DAYS = 10;
export const STOCK_BACKTEST_FRESH_MS = 24 * 60 * 60 * 1000;
async function getTargets(limit: number) {
const { getStockAnalysisTargets } = await import('./stock-analysis');
return getStockAnalysisTargets(limit);
}
export async function fetchStockBacktestsForTargets(
targets: Array<{ symbol: string; name: string }>,
evalWindowDays = DEFAULT_EVAL_WINDOW_DAYS,
): Promise<StockBacktestResult[]> {
const results: StockBacktestResult[] = [];
for (let i = 0; i < targets.length; i++) {
if (i > 0) await new Promise((resolve) => setTimeout(resolve, 200));
try {
const result = await client.backtestStock({
symbol: targets[i]!.symbol,
name: targets[i]!.name,
evalWindowDays,
});
if (result.available) results.push(result);
} catch {
// Skip failed individual backtest
}
}
return results;
}
export async function fetchStockBacktests(
limit = DEFAULT_LIMIT,
evalWindowDays = DEFAULT_EVAL_WINDOW_DAYS,
): Promise<StockBacktestResult[]> {
return fetchStockBacktestsForTargets(await getTargets(limit), evalWindowDays);
}
export async function fetchStoredStockBacktests(
limit = DEFAULT_LIMIT,
evalWindowDays = DEFAULT_EVAL_WINDOW_DAYS,
): Promise<StockBacktestResult[]> {
const targets = await getTargets(limit);
const symbols = targets.map((target) => target.symbol);
const response = await client.listStoredStockBacktests({
symbols,
evalWindowDays,
});
return response.items.filter((result) => result.available);
}
export function hasFreshStoredStockBacktests(
items: StockBacktestResult[],
symbols: string[],
maxAgeMs = STOCK_BACKTEST_FRESH_MS,
): boolean {
if (symbols.length === 0) return false;
const bySymbol = new Map(items.map((item) => [item.symbol, item]));
const now = Date.now();
return symbols.every((symbol) => {
const item = bySymbol.get(symbol);
const ts = Date.parse(item?.generatedAt || '');
return !!item?.available && Number.isFinite(ts) && (now - ts) <= maxAgeMs;
});
}
export function getMissingOrStaleStoredStockBacktests(
items: StockBacktestResult[],
symbols: string[],
maxAgeMs = STOCK_BACKTEST_FRESH_MS,
): string[] {
const bySymbol = new Map(items.map((item) => [item.symbol, item]));
const now = Date.now();
return symbols.filter((symbol) => {
const item = bySymbol.get(symbol);
const ts = Date.parse(item?.generatedAt || '');
return !(item?.available && Number.isFinite(ts) && (now - ts) <= maxAgeMs);
});
}