看股功能更新

This commit is contained in:
2026-08-15 15:36:11 +08:00
parent c1c43d2ff7
commit 9cce670b74
26 changed files with 957 additions and 427 deletions

View File

@@ -4,6 +4,7 @@
MVP 在应用层用 pandas resample 即可,逻辑等价、便于切换。
OHLCV 聚合规则:开=周期内首根开、高=最高、低=最低、收=末根收、量=求和。
成交额/换手率为名义量:求和(全缺则保持 None不伪造 0
"""
from __future__ import annotations
@@ -22,6 +23,13 @@ def bars_per_year(timeframe: str) -> int:
return _BARS_PER_YEAR.get(timeframe, 252)
def _sum_or_none(s: pd.Series):
"""求和;全为 NaN 返回 None部分缺失则忽略缺失项求和"""
if s.isna().all():
return None
return float(s.sum())
def resample_bars(bars: list[Bar], timeframe: str) -> list[Bar]:
"""把日线 bars 聚合为目标周期;日线或未知周期原样返回。"""
if not bars or timeframe in ("1d", "d", "day", "", None):
@@ -31,14 +39,18 @@ def resample_bars(bars: list[Bar], timeframe: str) -> list[Bar]:
return bars
df = pd.DataFrame(
[{"ts": b.ts, "open": b.open, "high": b.high, "low": b.low, "close": b.close, "volume": b.volume}
[{"ts": b.ts, "open": b.open, "high": b.high, "low": b.low, "close": b.close,
"volume": b.volume,
"amount": b.amount if b.amount is not None else float("nan"),
"turnover": b.turnover if b.turnover is not None else float("nan")}
for b in bars]
).set_index("ts").sort_index()
agg = (
df.resample(rule)
.agg({"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"})
.dropna()
.agg({"open": "first", "high": "max", "low": "min", "close": "last",
"volume": "sum", "amount": _sum_or_none, "turnover": _sum_or_none})
.dropna(subset=["open"])
)
return [
@@ -49,6 +61,8 @@ def resample_bars(bars: list[Bar], timeframe: str) -> list[Bar]:
low=float(row["low"]),
close=float(row["close"]),
volume=float(row["volume"]),
amount=row["amount"] if row["amount"] == row["amount"] else None, # NaN -> None
turnover=row["turnover"] if row["turnover"] == row["turnover"] else None,
)
for ts, row in agg.iterrows()
]