phase-2: extract shared utils
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
const STORE_PATH = './market-calls.json';
|
||||
|
||||
// MarketCallStore — persists quarterly market thesis entries to market-calls.json.
|
||||
//
|
||||
// A market call captures:
|
||||
// - A written thesis (the reasoning behind the call)
|
||||
// - Tickers to watch
|
||||
// - A snapshot of each ticker's price + signal at the time of the call
|
||||
// - Performance tracking (current vs snapshot price) computed on read
|
||||
//
|
||||
// Format:
|
||||
// {
|
||||
// "calls": [
|
||||
// {
|
||||
// "id": "uuid",
|
||||
// "title": "Q3 2025 — Rate pivot & tech rotation",
|
||||
// "quarter": "Q3 2025",
|
||||
// "date": "2025-07-01",
|
||||
// "thesis": "The Fed is expected to begin cutting...",
|
||||
// "tickers": ["AAPL", "MSFT", "TLT"],
|
||||
// "snapshot": {
|
||||
// "AAPL": { "price": 195.00, "signal": "✅ Strong Buy", "verdict": "BUY (High Conviction)" }
|
||||
// },
|
||||
// "createdAt": "2025-07-01T14:22:00.000Z"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
export class MarketCallStore {
|
||||
_load() {
|
||||
if (!existsSync(STORE_PATH)) return { calls: [] };
|
||||
try {
|
||||
return JSON.parse(readFileSync(STORE_PATH, 'utf8'));
|
||||
} catch {
|
||||
return { calls: [] };
|
||||
}
|
||||
}
|
||||
|
||||
_save(data) {
|
||||
writeFileSync(STORE_PATH, JSON.stringify(data, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
list() {
|
||||
return this._load().calls.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
}
|
||||
|
||||
get(id) {
|
||||
return this._load().calls.find((c) => c.id === id) ?? null;
|
||||
}
|
||||
|
||||
// Create a new call. snapshot is an object keyed by ticker with { price, signal, verdict }.
|
||||
create({ title, quarter, date, thesis, tickers, snapshot }) {
|
||||
const data = this._load();
|
||||
const call = {
|
||||
id: randomUUID(),
|
||||
title,
|
||||
quarter,
|
||||
date: date ?? new Date().toISOString().slice(0, 10),
|
||||
thesis,
|
||||
tickers,
|
||||
snapshot: snapshot ?? {},
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
data.calls.push(call);
|
||||
this._save(data);
|
||||
return call;
|
||||
}
|
||||
|
||||
delete(id) {
|
||||
const data = this._load();
|
||||
const before = data.calls.length;
|
||||
data.calls = data.calls.filter((c) => c.id !== id);
|
||||
if (data.calls.length === before) return false;
|
||||
this._save(data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user