否掉 bis[2]:修右边缘重画会把 alpha 打成零,重画是必付代价
§5.41 发现 available_ts 取「中枢最后一笔」是右边缘重画的根因,改取「第三笔」 能把重画率从 6.5% 压到 1.2%,当时据此判断它是「唯一可能同时改善收益与稳定性」 的改动。那个判断只测了稳定性,过早了。 8 个样本外币 × 30 万根 1m,两组共用同一个 TF_DF,只切 available_ts 的取法。 实盘口径(深色 ∧ ATR≥8bp,955 vs 989 笔): 毛 R 0.933 → -0.000 净均 R 0.798 → -0.147 PF 3.22 → 0.80 滑点余量 15.07 → -2.20 bp 判决依据是毛 R 那一行:扣任何费用之前 edge 就没了,所以不是成本、门控或出场 参数的问题,是信号本身不再有预测力。逐币 8/8 全部变差。滞后确实降了 (2.16 → 2.01),但换来的是另一批交易——两组重合度只有约 30%。 原因是中枢没发育完就下注,支撑/压力还没立住。「等中枢最后一笔」那段等待不是 可以优化掉的延迟,它就是 alpha 本身。由此得一条一般规则:任何以「让信号更早 确定」为目标的改动,先测毛 R,不能只看重画率和滞后。 开关 AVAIL_BI_INDEX 保留只为可复现该 A/B,默认 -1 维持现行口径。环境变量在 调用时解析而非 import 时——fork 启动的子进程会继承已 import 的模块,import 时读会固化成父进程的值。 顺带交叉验证:现行口径本次算出滑点余量 15.07bp,与用优化前代码算的同组同期 15.19bp 吻合。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -44,6 +44,27 @@ import pandas as pd
|
||||
|
||||
from chanlun.core.ChanEnum import Chan_FX_TYPE
|
||||
|
||||
# 中枢的「可用时刻」取第几笔的确认时间。
|
||||
# -1 = 最后一笔(历史默认)。中枢每吸收一根K线,最后一笔就可能后移,
|
||||
# 于是 available_ts 跟着漂——这是右边缘重画的根因(见 HANDOFF §5.41)。
|
||||
# 2 = 第三笔。三笔重叠即中枢成立,此后不再变,理论上更早也更稳。
|
||||
# ⛔ step47 已判定 bis[2] 作废(毛 R 0.933 → −0.000,见 HANDOFF §5.42)。
|
||||
# 开关保留只为可复现那次 A/B,**不要改默认值**。
|
||||
import os as _os
|
||||
|
||||
AVAIL_BI_INDEX = -1
|
||||
|
||||
|
||||
def _resolve_avail_bi(avail_bi: int | None) -> int:
|
||||
"""优先级:显式入参 > 环境变量 CHAN_AVAIL_BI > 模块默认。
|
||||
|
||||
环境变量在调用时读取而非 import 时——ProcessPoolExecutor 在 fork 启动方式下
|
||||
子进程会继承已 import 的模块,import 时读就固化成父进程的值了。
|
||||
"""
|
||||
if avail_bi is not None:
|
||||
return avail_bi
|
||||
return int(_os.environ.get("CHAN_AVAIL_BI", AVAIL_BI_INDEX))
|
||||
|
||||
|
||||
def timestamps_ms(src: pd.DataFrame) -> np.ndarray:
|
||||
"""取毫秒时间戳。研究侧的 df 自带 timestamp,web 侧的不一定,故按 date 回退。
|
||||
@@ -69,7 +90,7 @@ def ensure_timestamp(df: pd.DataFrame) -> pd.DataFrame:
|
||||
return out
|
||||
|
||||
|
||||
def build_htf_zones(df_htf: pd.DataFrame, tf: str, chan=None) -> pd.DataFrame:
|
||||
def build_htf_zones(df_htf: pd.DataFrame, tf: str, chan=None, avail_bi: int | None = None) -> pd.DataFrame:
|
||||
"""算 pure 笔中枢,返回带生效时间的区间表。
|
||||
|
||||
available_ts —— 该中枢最早可被使用的时间戳(其确认时刻)。
|
||||
@@ -82,26 +103,28 @@ def build_htf_zones(df_htf: pd.DataFrame, tf: str, chan=None) -> pd.DataFrame:
|
||||
zs_list = chan.cal_bi_zs_list_pure(chan.bi_list)
|
||||
# 用引擎自己的 dataframe 对齐,避免调用方传入的 df 与引擎内部行数不一致
|
||||
src = chan.dataframe if getattr(chan, "dataframe", None) is not None else df_htf
|
||||
return zones_from_zs_list(zs_list, src)
|
||||
return zones_from_zs_list(zs_list, src, avail_bi=avail_bi)
|
||||
|
||||
|
||||
def zones_from_zs_list(zs_list, src: pd.DataFrame) -> pd.DataFrame:
|
||||
def zones_from_zs_list(zs_list, src: pd.DataFrame, avail_bi: int | None = None) -> pd.DataFrame:
|
||||
"""把已算好的 pure 笔中枢转成区间表。
|
||||
|
||||
调用方手工跑过 cal_bi_zs_list_pure 时走这里,免得再算一遍(web 的 analyze_chan
|
||||
就是这种用法)。
|
||||
"""
|
||||
ts_of = dict(zip(src["date"].dt.strftime("%Y-%m-%d %H:%M:%S"), timestamps_ms(src)))
|
||||
# 中枢可用时刻取第几笔。在循环外解析一次,别让每个中枢都去读一遍环境变量。
|
||||
i = _resolve_avail_bi(avail_bi)
|
||||
|
||||
rows = []
|
||||
for zs in zs_list:
|
||||
bis = getattr(zs, "bi_list", [])
|
||||
if not bis:
|
||||
continue
|
||||
# 中枢可用时刻:构成它的最后一笔被确认之时
|
||||
last_bi = bis[-1]
|
||||
sure_key = str(getattr(last_bi, "sure_time", "") or "")
|
||||
end_key = str(getattr(last_bi, "end_time", "") or "")
|
||||
# 笔数不够时退回最后一笔(i=2 需要至少 3 笔才成立)
|
||||
key_bi = bis[i] if (i == -1 or len(bis) > i) else bis[-1]
|
||||
sure_key = str(getattr(key_bi, "sure_time", "") or "")
|
||||
end_key = str(getattr(key_bi, "end_time", "") or "")
|
||||
avail = ts_of.get(sure_key) or ts_of.get(end_key)
|
||||
if avail is None:
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user