feat(sector_radar): enhance sector radar functionality with active moneyflow and detailed metrics
- Introduced ActiveMoneyflowSource to fetch optional active-order flow, enhancing the sector radar's data capabilities. - Updated StockFactRecord and DailyAggregateRecord to include pct_change and active_buy_net_amount_yuan for improved financial insights. - Modified the build process to incorporate active moneyflow data without invalidating main rankings on failure. - Enhanced the HTTP API to return detailed sector history and metrics, including pct_change and active buy metrics for members. - Updated tests to validate the new functionality and ensure data integrity across various scenarios.
This commit is contained in:
@@ -5,6 +5,9 @@ const requestJson = vi.hoisted(() => vi.fn())
|
||||
vi.mock("@/shared/api/request-json", () => ({ requestJson }))
|
||||
|
||||
import {
|
||||
getSectorRadarHistory,
|
||||
getSectorRadarDetail,
|
||||
parseRadarDetailResponse,
|
||||
getSectorRadarDates,
|
||||
getSectorRadarRankings,
|
||||
getStockSectorMembership,
|
||||
@@ -69,11 +72,106 @@ const rankingPayload = {
|
||||
],
|
||||
}
|
||||
|
||||
const historyMetric = {
|
||||
rank_position: 1,
|
||||
rank_percentile: "100",
|
||||
pool_size: 10,
|
||||
metric_value: "0.125",
|
||||
missing: false,
|
||||
in_top: true,
|
||||
in_bottom: false,
|
||||
}
|
||||
const historyPayload = {
|
||||
status: "success",
|
||||
requested_trade_date: "2026-08-28",
|
||||
trade_date: "2026-08-28",
|
||||
sector_type: "concept",
|
||||
sector_code: "BK01",
|
||||
sector_name: "机器人",
|
||||
publication,
|
||||
window_size: 30,
|
||||
available_days: 1,
|
||||
points: [
|
||||
{
|
||||
trade_date: "2026-08-28",
|
||||
publication_id: "publication-1",
|
||||
swing: historyMetric,
|
||||
ratio: historyMetric,
|
||||
amount: historyMetric,
|
||||
},
|
||||
],
|
||||
}
|
||||
const detailPayload = {
|
||||
...historyPayload,
|
||||
pct_change: "-1.25",
|
||||
leading_stock: { ts_code: "000001.SZ", name: null },
|
||||
summary: {
|
||||
swing: historyMetric,
|
||||
ratio: historyMetric,
|
||||
amount: historyMetric,
|
||||
},
|
||||
history: historyPayload,
|
||||
members: [
|
||||
{
|
||||
ts_code: "000001.SZ",
|
||||
name: "测试成员",
|
||||
pct_change: "-1.5",
|
||||
net_amount_yuan: "120000000",
|
||||
active_buy_net_amount_yuan: null,
|
||||
},
|
||||
],
|
||||
leaders: {
|
||||
pct_change: { top: [], bottom: [] },
|
||||
net_amount_yuan: { top: [], bottom: [] },
|
||||
active_buy_net_amount_yuan: { top: [], bottom: [] },
|
||||
},
|
||||
similar_sectors: [],
|
||||
}
|
||||
|
||||
describe("sector radar API adapters", () => {
|
||||
beforeEach(() => {
|
||||
requestJson.mockReset()
|
||||
})
|
||||
|
||||
it("reads persisted sector resources and preserves nullable independent metrics", async () => {
|
||||
const signal = new AbortController().signal
|
||||
const query = {
|
||||
sectorType: "concept" as const,
|
||||
sectorCode: "BK01",
|
||||
tradeDate: "2026-08-28",
|
||||
}
|
||||
requestJson.mockResolvedValue(historyPayload)
|
||||
expect(
|
||||
(await getSectorRadarHistory(query, signal)).points[0]?.ratio
|
||||
.metric_value,
|
||||
).toBe(0.125)
|
||||
expect(requestJson).toHaveBeenLastCalledWith(
|
||||
"/api/v1/sector-radar/sectors/concept/BK01/history?trade_date=2026-08-28",
|
||||
{ signal },
|
||||
)
|
||||
requestJson.mockResolvedValue(detailPayload)
|
||||
const detail = await getSectorRadarDetail(query, signal)
|
||||
expect(requestJson).toHaveBeenLastCalledWith(
|
||||
"/api/v1/sector-radar/sectors/concept/BK01/detail?trade_date=2026-08-28",
|
||||
{ signal },
|
||||
)
|
||||
expect(detail.pct_change).toBe(-1.25)
|
||||
expect(detail.leading_stock?.name).toBeNull()
|
||||
expect(detail.members[0]).toMatchObject({
|
||||
net_amount_yuan: 120000000,
|
||||
active_buy_net_amount_yuan: null,
|
||||
})
|
||||
})
|
||||
|
||||
it.each(["NaN", "Infinity", "0x10", "", true])(
|
||||
"rejects malformed detail Decimal %s",
|
||||
(value) => {
|
||||
expect(() =>
|
||||
parseRadarDetailResponse({ ...detailPayload, pct_change: value }),
|
||||
).toThrow("detail.pct_change")
|
||||
},
|
||||
)
|
||||
|
||||
it("forwards AbortSignal and normalizes decimal publication fields", async () => {
|
||||
const signal = new AbortController().signal
|
||||
requestJson.mockResolvedValue({
|
||||
@@ -237,6 +335,8 @@ describe("stock sector membership adapter", () => {
|
||||
|
||||
await expect(
|
||||
getStockSectorMembership("000001.SZ", "2026-08-28"),
|
||||
).rejects.toThrow("membership.industries[0].name must be a non-empty string")
|
||||
).rejects.toThrow(
|
||||
"membership.industries[0].name must be a non-empty string",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user