评分改为±100分制;库存图y轴倒置;月差序列过滤远月前12月幽灵数据

This commit is contained in:
2026-06-15 15:25:26 +08:00
parent 94a230e485
commit bc9f2682d1
4 changed files with 93 additions and 51 deletions
+43 -8
View File
@@ -172,6 +172,24 @@ def _extract_year_prefix(contract: str) -> int | None:
return None
def _contract_year(contract: str) -> int | None:
"""从合约代码推断完整年份。
上期所/大商所/中金所 4位数字: RB2610 → 2000+26=2026
郑商所 3位数字: MA609 → 2020+6=2026(当前十年2020s
"""
digits = "".join(c for c in contract if c.isdigit())
if len(digits) < 3:
return None
try:
prefix = int(digits[:-2])
if len(digits) == 3: # 郑商所3位数字,前缀1位
return 2020 + prefix
else: # 4位数字,前缀2位
return 2000 + prefix
except (ValueError, IndexError):
return None
def _year_gap(near: str, far: str) -> int | None:
"""计算近月/远月合约的年份间隔,如 (SC2608, SC2609) → 0, (EG2609, EG2701) → 1。"""
ny = _extract_year_prefix(near)
@@ -254,9 +272,23 @@ def build_historical_spreads(
spread_series = spread_series[spread_series.index >= f"{start_year}-01-01"]
if spread_series.dropna().empty:
continue
# 推断年份:取序列中间日期的年份
mid_idx = len(spread_series) // 2
year_val = int(spread_series.index[mid_idx].year)
# 只保留远月合约到期前约12个月开始的数据
# 远月合约 c2 交割年月 = (Y, far_month),保留 date(Y-1, far_month, 1) 之后的数据
_far_year = _contract_year(c2)
if _far_year is not None:
try:
_far_start = pd.Timestamp(year=_far_year - 1, month=far_month, day=1)
spread_series = spread_series[spread_series.index >= _far_start]
except (ValueError, TypeError):
pass
if spread_series.dropna().empty:
continue
# 推断年份:从近月合约代码提取完整年份
year_val = _contract_year(c1)
if year_val is None:
# 降级方案:使用序列中间日期的年份
mid_idx = len(spread_series) // 2
year_val = int(spread_series.index[mid_idx].year)
hist_series_list.append({
"pair_label": f"{c1} - {c2}",
"series": spread_series,
@@ -336,16 +368,19 @@ def _builtin_band_score(value: float, history: pd.Series, day: int, inverse: boo
low = mid - (mid - q_low) * width_scale
high = mid + (q_high - mid) * width_scale
if high == low:
score = 50.0
elif inverse:
score = 100.0 if value <= low else (0.0 if value >= high else 100.0 - (value - low) / (high - low) * 100)
raw_score = 100.0 if value >= high else -100.0
elif value >= high:
raw_score = 100.0
elif value <= low:
raw_score = -100.0
else:
score = 100.0 if value >= high else (0.0 if value <= low else (value - low) / (high - low) * 100)
raw_score = 200.0 * (value - low) / (high - low) - 100.0
score = -raw_score if inverse else raw_score
below = float((sample < value).sum())
equal = float((sample == value).sum())
raw_pct = 100.0 * (below + 0.5 * equal) / len(sample)
return {
"score": round(max(0.0, min(100.0, score)), 1),
"score": round(max(-100.0, min(100.0, score)), 1),
"raw_percentile": round(raw_pct, 1),
"band_low": round(low, 4),
"band_high": round(high, 4),