Files
tick-stock-panel/backend/tests/test_repository_index.py
T
shy3130 8c00f7a3fd feat(v0.2): 双击价格创建点位监控 + 分钟K补齐修复 + 超时设置独立
点位监控(双击图表):
- 日K/单日分时/多日分时价格主图支持双击, 自动预填目标价并按最新价判断上穿/下穿
- 成交量区与指标子图不触发; 自动生成通知文案, 用户手改后不再覆盖
- 已启用点位监控以横虚线显示 (上穿红/下穿绿), 并纳入三图 Y 轴范围
- 复用现有 PriceAlertDialog, 未改变后端 close>=/<= 规则语义与冷却机制

分钟K数据:
- 个股补齐强制回溯请求天数 (force_full_days), 修复切换日期范围只显示3天
- 单股并发补齐复用仓库写锁, 修复 Windows Parquet 临时文件替换失败
- DuckDB 读路径改用短生命周期 cursor, 释放对 Parquet 文件的占用

设置页:
- 数据任务超时从 DataSources 抽出为独立"超时设置"页签
- 设置侧栏垂直居中

验证: pytest 38 passed, pnpm build ok, git diff --check ok, 浏览器端到端 GUI 验证通过
2026-08-10 23:32:44 +08:00

131 lines
4.7 KiB
Python

"""指数资产路由 — repository 层测试。"""
import os
import polars as pl
import pytest
from app.tickflow.repository import DataStore, KlineRepository
@pytest.fixture()
def repo(tmp_path):
return KlineRepository(DataStore(tmp_path))
def _write_index_instruments(repo, rows):
pl.DataFrame(rows).write_parquet(
repo.store.data_dir / "instruments_index" / "part.parquet"
)
repo._refresh_index_instruments()
def test_name_map_includes_index(repo):
_write_index_instruments(repo, {
"symbol": ["000001.SH"], "name": ["上证指数"],
"code": ["000001"], "asset_type": ["index"],
})
names = repo.get_name_map(["000001.SH", "600000.SH"])
assert names.get("000001.SH") == "上证指数"
assert "600000.SH" not in names # 未收录不造名
def test_name_map_stock_beats_index(repo):
"""同名 symbol 同时出现在股票/指数维表时, 股票名称优先。"""
_write_index_instruments(repo, {
"symbol": ["600000.SH"], "name": ["某指数"],
"code": ["600000"], "asset_type": ["index"],
})
pl.DataFrame({
"symbol": ["600000.SH"], "name": ["浦发银行"], "code": ["600000"],
"exchange": ["SH"], "region": ["CN"], "type": ["stock"],
"listing_date": [None], "total_shares": [None], "float_shares": [None],
"tick_size": [None], "limit_up": [None], "limit_down": [None],
"as_of": ["2026-07-25"],
}).write_parquet(repo.store.data_dir / "instruments" / "instruments.parquet")
repo._refresh_instruments()
assert repo.get_name_map(["600000.SH"]).get("600000.SH") == "浦发银行"
import datetime as _dt
def test_execute_one_releases_parquet_file(repo):
minute_dir = repo.store.data_dir / "kline_minute" / "date=2026-07-23"
minute_dir.mkdir(parents=True, exist_ok=True)
part = minute_dir / "part.parquet"
replacement = minute_dir / "part.parquet.tmp"
minute = pl.DataFrame({
"symbol": ["600000.SH"],
"datetime": [_dt.datetime(2026, 7, 23, 9, 30)],
"close": [10.0],
})
minute.write_parquet(part)
repo.rebuild_views()
assert repo.execute_one("SELECT max(datetime) FROM kline_minute")[0] == _dt.datetime(2026, 7, 23, 9, 30)
minute.write_parquet(replacement)
os.replace(replacement, part)
def _write_index_enriched(repo, dates_rows):
for ds, rows in dates_rows.items():
d = repo.store.data_dir / "kline_index_enriched" / f"date={ds}"
d.mkdir(parents=True, exist_ok=True)
pl.DataFrame(rows).write_parquet(d / "part.parquet")
def _index_rows(ds, close=3000.0):
return [{
"symbol": "000001.SH", "date": _dt.date.fromisoformat(ds),
"open": close - 10, "high": close + 20, "low": close - 20, "close": close,
"volume": 1_000_000, "amount": 1e9,
}]
def test_get_enriched_latest_asset_index(repo):
_write_index_enriched(repo, {
"2026-07-23": _index_rows("2026-07-23", 2990.0),
"2026-07-24": _index_rows("2026-07-24", 3000.0),
})
df, dt = repo.get_enriched_latest_asset("index")
assert str(dt) == "2026-07-24"
assert df["symbol"].to_list() == ["000001.SH"]
assert "ma5" in df.columns or "rsi_14" in df.columns # 重算产出指标列
def test_get_enriched_latest_asset_index_cold_no_refresh(repo):
df, dt = repo.get_enriched_latest_asset("index", refresh=False)
assert df.is_empty() and dt is None
def test_flush_live_enriched_asset_index_updates_cache(repo):
df = pl.DataFrame([{
"symbol": "000001.SH", "date": _dt.date(2026, 7, 25),
"open": 3000.0, "high": 3010.0, "low": 2990.0, "close": 3005.0,
"volume": 1_000_000, "amount": 1e9, "ma5": 3001.0, "rsi_14": 55.0,
}])
repo.flush_live_enriched_asset("index", df)
cached, dt = repo.get_enriched_latest_asset("index", refresh=False)
assert str(dt) == "2026-07-25"
assert cached["close"].to_list() == [3005.0]
assert (repo.store.data_dir / "kline_index_enriched" / "date=2026-07-25" / "part.parquet").exists()
def _merge_row(symbol, close):
return {
"symbol": symbol, "date": _dt.date(2026, 7, 25),
"open": close - 5, "high": close + 5, "low": close - 6, "close": close,
"volume": 1_000, "amount": 1e6,
}
def test_merge_live_enriched_asset_index_merges_cache(repo):
"""merge 路径: 两次合并缓存取并集 (不 NameError, 不丢已有缓存)。"""
repo.merge_live_enriched_asset("index", pl.DataFrame([_merge_row("000001.SH", 3000.0)]))
repo.merge_live_enriched_asset("index", pl.DataFrame([_merge_row("000300.SH", 4000.0)]))
cached, dt = repo.get_enriched_latest_asset("index", refresh=False)
assert str(dt) == "2026-07-25"
assert set(cached["symbol"].to_list()) == {"000001.SH", "000300.SH"}