mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 21:24:16 +08:00
* feat(screener): 选股引擎支持 ETF - 12 个内置策略打 asset_types 白名单 + strategy_supports_asset;涨停类 (连板/断板反包)仅股票,其余 10 个技术类对 ETF 开放 - ScreenerService(repo, asset_type) 分流取数,ETF 复用 kline_etf_enriched, 跳过股票专用历史缓存与涨停信号;进程级 _history_cache key 含 asset_type - API /run、/run_preset 透传 asset_type;/strategies 按资产过滤; 股票专有策略在 ETF 下返回空 - 新增 enriched_dirname(asset_type) 共享 helper;get_enriched_latest_asset 增 refresh 参数(供轮询线程避免冷缓存同步重算) - 前端「策略」页加 股票/ETF 切换,ETF 走实时单跑(空日期→用 ETF 自身最新日); QK.screenerStrategies 按 asset_type keyed - 测试:test_screener_etf.py Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(backtest): 回测支持 ETF(个股/因子/策略组合) - 三条回测路径 + 共用 BacktestEngine 面板加载按 asset_type 路由到 kline_etf_enriched(复用 enriched_dirname);PanelCache key 隔离资产; ETF 跳过股票专用 get_enriched_range 缓存 - 面板 compute_all/名称 JOIN 按 asset_type 取维表(get_instruments_asset), 修复 ETF 策略回测用错股票维表致名称为空/涨停信号算错 - BacktestConfig/FactorConfig/StrategyBacktestConfig 增 asset_type - 三个回测 API + SSE stream 透传 asset_type;_make_job_key 纳入 asset_type (修复 stream 与 cancel job_key 不对齐致取消失效的回归) - 前端策略组合页/因子页加 股票/ETF 切换,标的搜索与策略列表跟随资产; assetType 持久化 - 测试:test_backtest_etf.py(含 job_key 一致性回归);既有回测测试替身同步 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(monitor): 监控规则支持 ETF - engine.evaluate(df, asset_type) 按规则 asset_type 分轮评估;quote_service 增开 ETF 评估轮(用 ETF enriched 快照),股票轮不受影响、不重置其策略结果 - ETF 评估轮独立 try(异常不丢弃已算出的股票告警)+ refresh=False(不在轮询 线程触发 ETF 冷缓存同步重算) - ETF 版历史加载器(main.py 注入)+ 按规则 asset_type 选加载器 - _strategy_pools 按 (sid, asset_type) 键,避免同策略股票/ETF 规则互相覆盖 - name_map 仅在有 ETF 规则时补 ETF 维表, setdefault 保股票名优先 - RuleModel/normalize 增 asset_type(默认 stock,持久化往返) - 前端 RuleEditor 加 股票/ETF 选择,策略列表与标的搜索跟随资产 - 测试:test_monitor_etf.py Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(etf): 前端 API 绑定透传 asset_type + 文档 - api.ts: screener/backtest 绑定加 assetType 参数,MonitorRule 类型加 asset_type - docs/features.md: 标注选股/回测/监控的 ETF 支持范围与前提 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(reliability): 管道并发/原子写/能力探测/监控告警多处加固 后端可靠性专项修复(均带回归测试, backend 全套 64 passed): 并发与数据完整性: - 盘后管道单飞: JobStore.create() 去重纳入 pending∨running, 关闭"两次快速点击" 并发双跑窗口; 新增 _heavy_run_lock 执行槽挡住 reap 后僵尸线程并发写 parquet - adj_factor/minute 全部改走原子写(tmp+replace), 消除 kill/断电致 all.parquet 损坏 - 分块拉取失败聚合 WARNING 可见化(不再静默当成功); 复权失败标的会保持旧价已提示 能力探测: - 周期重探(60min)热更新 app.state.capabilities, 付费 Key 过期/续费无需重启即可见 - 瞬时探测失败(超时/连接/5xx, 按 _is_transient 判定)不降级、保留旧付费档; 真 401/无权限仍正常降级回落 free-api 监控告警: - 评估仅在连续竞价(9:30-11:30/13:00-15:00)+ 快照当日新鲜度下进行, 避开集合竞价/ 收盘后陈旧价与节假日误告警 - scope=sector fail-closed(validate 拒绝新建 + _apply_scope 返回空), 修复板块规则 对全市场刷屏 - 飞书 webhook 加退避重试并移到独立线程池 fire-and-forget, 不再阻塞行情轮询线程 单标的新鲜度: 新增 repo.symbols_lagging() 检测掉队标的并 WARNING + 计入 job 结果 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
/**
|
|
* 集中管理所有 localStorage 持久化。
|
|
*
|
|
* - key 在此注册,各页面只通过 storage.xxx.get/set 调用。
|
|
* - 类型安全,不再散落 try/catch。
|
|
*/
|
|
|
|
function kv<T>(key: string) {
|
|
return {
|
|
get(fallback: T): T {
|
|
try {
|
|
const raw = localStorage.getItem(key)
|
|
if (raw !== null) return JSON.parse(raw) as T
|
|
} catch { /* ignore */ }
|
|
return fallback
|
|
},
|
|
set(val: T) {
|
|
try { localStorage.setItem(key, JSON.stringify(val)) } catch { /* ignore */ }
|
|
},
|
|
}
|
|
}
|
|
|
|
export const storage = {
|
|
/** 查询轮询 / SSE 配置 */
|
|
queryConfig: kv<unknown>('tf-stocks-query-config'),
|
|
|
|
/** 策略池 (screener) */
|
|
strategyPool: kv<string[]>('strategy-pool'),
|
|
|
|
/** 自选列表列配置 */
|
|
watchlistColumns: kv<unknown[]>('watchlist_columns'),
|
|
|
|
/** 个股日K信息条指标配置 */
|
|
stockInfoBarFields: kv<unknown[]>('stock_info_bar_fields'),
|
|
|
|
/** 策略结果列表列配置 */
|
|
screenerResultColumns: kv<unknown[]>('screener_result_columns'),
|
|
|
|
/** 自选列表视图模式 table | card */
|
|
watchlistView: kv<string>('watchlist_view'),
|
|
|
|
/** 自选列表日K蜡烛图显示状态 */
|
|
watchlistCandle: kv<boolean>('watchlist_showCandle'),
|
|
|
|
/** 自选列表分时图显示状态 */
|
|
watchlistIntraday: kv<boolean>('watchlist_showIntraday'),
|
|
|
|
/** 策略结果列表日K蜡烛图显示状态 */
|
|
screenerCandle: kv<boolean>('screener_showCandle'),
|
|
|
|
/** 自选列表板块筛选 */
|
|
watchlistBoardFilter: kv<string[]>('watchlist_boardFilter'),
|
|
|
|
/** Screener 卡片尺寸 */
|
|
screenerCardSize: kv<string>('screener-card-size'),
|
|
|
|
/** 连板梯队板块筛选 */
|
|
limitLadderBoard: kv<string[]>('limit-ladder-board-filter'),
|
|
|
|
/** 连板梯队 ext 字段配置 */
|
|
limitLadderExtFields: kv<Record<string, any>>('limit-ladder-ext-fields'),
|
|
|
|
/** 连板梯队 概念/行业 显示开关 */
|
|
limitLadderShowExt: kv<{ concept: boolean; industry: boolean }>('limit-ladder-show-ext'),
|
|
|
|
/** 连板梯队 涨停/跌停 切换方向 */
|
|
limitLadderDirection: kv<'up' | 'down'>('limit-ladder-direction'),
|
|
|
|
/** 连板梯队 封单显示模式: vol=按成交量(手), amount=按金额(元) */
|
|
limitLadderSealMode: kv<'vol' | 'amount'>('limit-ladder-seal-mode'),
|
|
|
|
/** 策略创建草稿(新建专用) */
|
|
strategyDraft: kv<{ name: string; description: string; direction: string; style?: string; rules: string; code: string; step: number; strategyId: string } | null>('strategy-draft'),
|
|
|
|
/** 策略修改草稿(AI修改专用,不影响创建按钮) */
|
|
strategyModify: kv<{ name: string; description: string; direction: string; style?: string; rules: string; code: string; step: number; strategyId: string } | null>('strategy-modify'),
|
|
|
|
/** 策略构建器草稿(旧版兼容,逐渐废弃) */
|
|
strategyBuilderDraft: kv<{ name: string; description: string; direction: string; style?: string; rules: string; code: string; step: number; strategyId: string } | null>('strategy-builder-draft'),
|
|
|
|
/** 已保存策略的原始规则(策略ID → 规则文本) */
|
|
strategyRules: kv<Record<string, string>>('strategy-rules'),
|
|
|
|
/** 策略回测快捷区间按钮配置 */
|
|
strategyBacktestQuickRanges: kv<unknown>('strategy-backtest-quick-ranges'),
|
|
|
|
/** 策略回测最后一次成功结果和参数 */
|
|
strategyBacktestLast: kv<{
|
|
selectedStrategy: string | null
|
|
symbols: string
|
|
assetType?: 'stock' | 'etf'
|
|
start: string
|
|
end: string
|
|
matching: 'close_t' | 'open_t+1'
|
|
entryFill: 'close_t' | 'open_t+1'
|
|
exitFill: 'close_t' | 'open_t+1'
|
|
fees: string
|
|
stampTax?: string
|
|
slippage: string
|
|
maxPositions: string
|
|
maxExposure: string
|
|
initialCapital: string
|
|
positionSizing: 'equal' | 'score_weight'
|
|
mode: 'position' | 'full'
|
|
holdingDays: string
|
|
params?: Record<string, any>
|
|
overrides?: Record<string, any>
|
|
result: any
|
|
} | null>('strategy-backtest-last'),
|
|
|
|
/** 概念分析页面字段配置 */
|
|
conceptAnalysisConfig: kv<Record<string, any>>('concept-analysis-config'),
|
|
|
|
/** 行业分析页面字段配置 */
|
|
industryAnalysisConfig: kv<Record<string, any>>('industry-analysis-config'),
|
|
|
|
/** 数据页画像卡片显隐 (卡片key → 是否显示) */
|
|
dataCardVisible: kv<Record<string, boolean>>('data-card-visible'),
|
|
/** 数据页画像卡片顺序 (卡片key 数组, 长度=卡片总数) */
|
|
dataCardOrder: kv<string[]>('data-card-order'),
|
|
} as const
|