Adds a stock data cache layer and indicator helpers (MA/RSI/MACD), expands the MarketDataProvider interface with candles, fundamentals, related-data, and quant analysis, and threads tags + source attribution through the shared types. Updates affected agent tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
117 lines
3.1 KiB
TypeScript
117 lines
3.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { generateDailyReport } from "../src/agent/report";
|
|
import type { MarketDataProvider, WatchStock } from "../src/shared/types";
|
|
|
|
const watchlist: WatchStock[] = [
|
|
{
|
|
id: "AAPL",
|
|
symbol: "AAPL",
|
|
display: "AAPL",
|
|
market: "US",
|
|
name: "Apple",
|
|
tags: ["core"],
|
|
addedAt: "2026-05-11T00:00:00.000Z"
|
|
},
|
|
{
|
|
id: "00700.HK",
|
|
symbol: "00700",
|
|
display: "00700.HK",
|
|
market: "HK",
|
|
name: "Tencent",
|
|
tags: ["internet"],
|
|
addedAt: "2026-05-11T00:00:00.000Z"
|
|
}
|
|
];
|
|
|
|
const provider: MarketDataProvider = {
|
|
async snapshot(stock) {
|
|
if (stock.display === "AAPL") {
|
|
return {
|
|
price: 100,
|
|
currency: "USD",
|
|
pe: 12,
|
|
forwardPe: 11,
|
|
revenueGrowth: 0.08,
|
|
profitGrowth: 0.05,
|
|
nextEarningsDate: "2026-05-13",
|
|
peers: [
|
|
{ display: "MSFT", pe: 28 },
|
|
{ display: "GOOGL", pe: 24 }
|
|
],
|
|
news: [{ title: "Apple supplier raises shipment guidance", url: "https://example.test/aapl" }]
|
|
};
|
|
}
|
|
|
|
return {
|
|
price: 380,
|
|
currency: "HKD",
|
|
pe: 22,
|
|
forwardPe: 20,
|
|
revenueGrowth: 0.02,
|
|
profitGrowth: -0.01,
|
|
nextEarningsDate: "2026-06-30",
|
|
peers: [{ display: "9988.HK", pe: 18 }],
|
|
news: []
|
|
};
|
|
},
|
|
async candles() {
|
|
return [];
|
|
},
|
|
async fundamentals() {
|
|
return [];
|
|
},
|
|
async relatedData() {
|
|
return {
|
|
sector: "Technology",
|
|
avgVolume20: 1_000_000,
|
|
volumeRatio: 1,
|
|
volatility20: 0.02,
|
|
drawdownFromHigh: 0.1,
|
|
relativeStrength60: 0.04,
|
|
support: 90,
|
|
resistance: 120,
|
|
capitalFlow: 0.1,
|
|
sentimentScore: 0.6,
|
|
newsHeat: 0.5,
|
|
riskFlags: [],
|
|
sources: []
|
|
};
|
|
},
|
|
async quantAnalysis() {
|
|
return {
|
|
action: "hold",
|
|
score: 55,
|
|
confidence: 0.5,
|
|
horizon: "1-3 months",
|
|
summary: "Hold",
|
|
factors: [
|
|
{ name: "trend", score: 55, weight: 1, evidence: "neutral" },
|
|
{ name: "valuation", score: 55, weight: 0, evidence: "neutral" },
|
|
{ name: "risk", score: 55, weight: 0, evidence: "neutral" }
|
|
],
|
|
riskNotes: []
|
|
};
|
|
}
|
|
};
|
|
|
|
describe("generateDailyReport", () => {
|
|
it("ranks earnings, related news, valuation mismatch, and buy advice across markets", async () => {
|
|
const report = await generateDailyReport(watchlist, provider, new Date("2026-05-11T00:00:00.000Z"));
|
|
|
|
expect(report.generatedAt).toBe("2026-05-11T00:00:00.000Z");
|
|
expect(report.items).toHaveLength(2);
|
|
expect(report.summary).toContain("US: 1");
|
|
expect(report.summary).toContain("HK: 1");
|
|
|
|
const apple = report.items.find((item) => item.stock.display === "AAPL");
|
|
expect(apple?.earnings.status).toBe("soon");
|
|
expect(apple?.valuation.label).toBe("discount");
|
|
expect(apple?.recommendation.action).toBe("buy_watch");
|
|
expect(apple?.news[0]?.title).toContain("supplier");
|
|
|
|
const tencent = report.items.find((item) => item.stock.display === "00700.HK");
|
|
expect(tencent?.valuation.label).toBe("premium");
|
|
expect(tencent?.recommendation.action).toBe("hold");
|
|
});
|
|
});
|