mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 16:44:15 +08:00
fix(realtime): unify turnover rate units (#93)
This commit is contained in:
@@ -818,7 +818,12 @@ class QuoteService:
|
||||
] if c in df.columns]
|
||||
if not keep or "symbol" not in keep:
|
||||
return pl.DataFrame()
|
||||
return df.select(keep)
|
||||
out = df.select(keep)
|
||||
# 实时 API 的 turnover_rate 入口契约为小数制(0.05 = 5%).
|
||||
# enriched 内部统一存百分数值(5 = 5%), 后续页面/筛选直接展示和比较。
|
||||
if "turnover_rate" in out.columns:
|
||||
out = out.with_columns((pl.col("turnover_rate").cast(pl.Float64, strict=False) * 100).alias("turnover_rate"))
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def _build_index_quotes(records: list[dict]) -> pl.DataFrame:
|
||||
|
||||
@@ -30,7 +30,7 @@ ALERTS = []
|
||||
|
||||
|
||||
def filter(df: pl.DataFrame, params: dict) -> pl.Expr:
|
||||
min_to = params.get("min_turnover", 5.0) / 100.0
|
||||
min_to = params.get("min_turnover", 5.0)
|
||||
min_chg = params.get("min_change", 3.0) / 100.0
|
||||
expr = pl.col("symbol").is_not_null() | pl.col("symbol").is_null()
|
||||
if params.get("use_turnover_filter", True):
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import polars as pl
|
||||
|
||||
from app.strategy.builtin import high_turnover_surge
|
||||
|
||||
|
||||
def test_high_turnover_surge_uses_percent_value_turnover_rate():
|
||||
df = pl.DataFrame({
|
||||
"symbol": ["low", "hit"],
|
||||
"turnover_rate": [4.9, 5.1],
|
||||
"change_pct": [0.04, 0.04],
|
||||
})
|
||||
|
||||
expr = high_turnover_surge.filter(df, {"min_turnover": 5.0, "min_change": 3.0})
|
||||
out = df.filter(expr)
|
||||
|
||||
assert out["symbol"].to_list() == ["hit"]
|
||||
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import polars as pl
|
||||
import pytest
|
||||
|
||||
from app.indicators import pipeline
|
||||
from app.services.quote_service import QuoteService
|
||||
|
||||
|
||||
def _today_rows(turnover_rate: float | None = None) -> pl.DataFrame:
|
||||
row = {
|
||||
"symbol": "600000.SH",
|
||||
"open": 10.0,
|
||||
"high": 10.0,
|
||||
"low": 10.0,
|
||||
"close": 10.0,
|
||||
"raw_close": 10.0,
|
||||
"raw_high": 10.0,
|
||||
"volume": 8000.0,
|
||||
}
|
||||
if turnover_rate is not None:
|
||||
row["turnover_rate"] = turnover_rate
|
||||
return pl.DataFrame([row])
|
||||
|
||||
|
||||
def _instruments() -> pl.DataFrame:
|
||||
return pl.DataFrame({
|
||||
"symbol": ["600000.SH"],
|
||||
"name": ["Test"],
|
||||
"float_shares": [100_000_000.0],
|
||||
})
|
||||
|
||||
|
||||
def test_quote_extra_normalizes_realtime_turnover_fraction_to_percent_value():
|
||||
out = QuoteService._build_quote_extra([{"symbol": "600000.SH", "turnover_rate": 0.008}])
|
||||
|
||||
assert out["turnover_rate"][0] == pytest.approx(0.8)
|
||||
|
||||
|
||||
def test_realtime_turnover_rate_uses_api_value_directly_after_entry_normalization():
|
||||
out = pipeline._compute_limit_signals_today(_today_rows(0.8), _instruments())
|
||||
|
||||
assert out["turnover_rate"][0] == pytest.approx(0.8)
|
||||
|
||||
|
||||
def test_realtime_turnover_rate_falls_back_to_float_shares_when_missing():
|
||||
out = pipeline._compute_limit_signals_today(_today_rows(), _instruments())
|
||||
|
||||
assert out["turnover_rate"][0] == pytest.approx(0.8)
|
||||
@@ -253,7 +253,7 @@ cp docs/examples/custom-data-source/mock_source.yaml data/data_sources/mock_sour
|
||||
change_pct = 涨跌幅 (小数, 0.0366 = 3.66%)
|
||||
change_amount = 涨跌额
|
||||
amplitude = 振幅
|
||||
turnover_rate = 换手率
|
||||
turnover_rate = 换手率 (小数, 0.05 = 5%; 若上游返回 5 表示 5%, 配置 transforms: turnover_rate: "value / 100")
|
||||
|
||||
分钟K (minute):
|
||||
symbol = 股票代码
|
||||
|
||||
@@ -415,8 +415,7 @@ function StockList({ title, rows, mode, onStockClick }: {
|
||||
</>
|
||||
) : mode === 'active' ? (
|
||||
<>
|
||||
{/* overview 的 turnover_rate 为小数制, 需 ×100 转百分数显示 */}
|
||||
<div className="font-mono text-[11px] text-accent">{fmtPrice(r.turnover_rate != null ? r.turnover_rate * 100 : null, 1)}%</div>
|
||||
<div className="font-mono text-[11px] text-accent">{fmtPrice(r.turnover_rate, 1)}%</div>
|
||||
<div className={`font-mono text-[9px] ${pctClass(r.change_pct)}`}>{fmtStockPct(r.change_pct)}</div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -45,7 +45,7 @@ const FIELD_LABELS: Record<string, string> = {
|
||||
change_pct: '涨跌幅 (小数 0.0366=3.66%)',
|
||||
change_amount: '涨跌额',
|
||||
amplitude: '振幅 (小数)',
|
||||
turnover_rate: '换手率 (小数)',
|
||||
turnover_rate: '换手率 (小数 0.05=5%)',
|
||||
timestamp: '时间戳',
|
||||
session: '交易时段',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user