评分改为±100分制;库存图y轴倒置;月差序列过滤远月前12月幽灵数据
This commit is contained in:
+43
-8
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user