缠论引擎提速 2.6x,瓶颈是逐行 Series 查找而非指标计算

原以为浪费在 add_indicators 算了太多用不到的指标,实测它只占全量构建的
1.3%——talib 是向量化 C 代码,便宜。真正的两处:

cal_kl_data 占 96%:每根 K 线 df.iloc[i] 新建一个 40 列 Series,再在其上做
几十次逐键查找。改为预取 ndarray 后 2 万根 1946ms → 824ms。

ChanKLC.cal_all_ema_status 占 25%:每次合并 KLU 都立即重算,而它产出的
ema_status / ema52_pos / ema52_status 全仓无任何读取方(含前端)。改为惰性
求值,保留属性形式以防将来有人读。顺带删掉 get_klc_list 里累加一整轮后直接
丢弃的 ema_up_list / ema_down_list。

另加 TF_DF(lean=True):只构建到中枢,跳过线段/走势中枢/MACD 状态机——这些
只服务 bsp_list 与 web 展示,笔和中枢不依赖。研究与实盘走这条快 3.6x。

结果 2 万根 5m:full 1946 → 754ms,lean → 543ms。

step46_engine_parity.py 是配套的安全网,改引擎前先跑一次 --save。它对 KLC
端点与分型、笔起止价与 is_sure、中枢 zg/zd/available_ts/阶梯、信号全部输出列,
以及 26 个被下游消费的 dataframe 列取哈希。本次三处改动逐步验证,另用
git stash 切回改动前代码在 20 万根 × 5 用例上做了跨版本逐位对拍,全部一致;
增量路径与 web API 也各验一遍。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-28 04:04:44 +08:00
co-authored by Cursor
parent 3b14bba247
commit 0b4d7693b8
7 changed files with 1300 additions and 80 deletions
+487
View File
@@ -0,0 +1,487 @@
{
"BTC_1m": {
"n_rows": 20000,
"n_klu": 20000,
"n_klc": 12496,
"n_bi": 913,
"n_seg": 131,
"n_bi_zs": 114,
"n_zs": 16,
"n_bsp": 0,
"klc_high": "986038d3e673336e",
"klc_low": "ee349f689ebfe387",
"klc_fx": "b323365a81e5e666",
"bi_start": "f829c2ce6845f569",
"bi_end": "f829c2ce6845f569",
"bi_sure": "64458b0f3100544e",
"n_zones": 114,
"zone_zg": "00c54276171344e0",
"zone_zd": "e0dcb978a1105305",
"zone_avail": "06d947c90bbab2f7",
"zone_ladder": "c6312a30d149c5b8",
"n_sig": 31,
"sig_entry_idx": "117daf6e8c55678c",
"sig_direction": "82ee301abc35d11b",
"sig_bo_idx": "37ffa946fad579ce",
"sig_pb_idx": "8552ade1f84eb3d3",
"sig_lag": "da1bcee041ab9685",
"sig_depth": "e0e60c3d319358e3",
"sig_zone_i": "434c25ef783c3d1d",
"col_open": "3197bbad7b435c9f",
"col_high": "41e4ad11f6f6d786",
"col_low": "c309926c23dfa574",
"col_close": "c48f76a626b629c6",
"col_volume": "07f9f254f17399a0",
"col_atr": "074464dfb5648113",
"col_macd": "135a69f5aa1df7dc",
"col_macdsignal": "62ce36e9da9f37ec",
"col_macdhist": "61ddcf3bb139bb81",
"col_ema5": "e0ea01077a59b84b",
"col_ema13": "ab9a31bbf5de8b57",
"col_ema24": "44e8e295560b85cf",
"col_ema26": "a710c87eeec607ea",
"col_ema52": "578454b2d9147d96",
"col_ema104": "36649911c3df0136",
"col_ema156": "bba5874bd409fa90",
"col_ema208": "a0bc2fa3e690681c",
"col_ema7": "e1e76d337b9139e4",
"col_rsi": "cc09f5884225b37f",
"col_volume_ratio": "2382df013004571f",
"col_bb2633upper": "85839e5a7bb36dc7",
"col_bb2633lower": "13861ca562994024",
"col_bb2633middle": "bf3c9bd564c2e8d7",
"col_bbp30": "d00623b2d18c6653",
"col_bbp120": "36dd361b5c618dcc",
"col_bbp365": "5cd3e7d640c7d5dd",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
},
"BTC_5m": {
"n_rows": 20000,
"n_klu": 20000,
"n_klc": 14323,
"n_bi": 974,
"n_seg": 149,
"n_bi_zs": 115,
"n_zs": 15,
"n_bsp": 0,
"klc_high": "0d3d5dcb6be9d8cf",
"klc_low": "2ae0ce7158f99ee5",
"klc_fx": "db8c606c09803b39",
"bi_start": "eb36252ef42df30d",
"bi_end": "eb36252ef42df30d",
"bi_sure": "a61c15678945f9e1",
"n_zones": 115,
"zone_zg": "5eaa2f8f738c8566",
"zone_zd": "d48ee940dac1fdba",
"zone_avail": "bfc273ac2c80a856",
"zone_ladder": "822ab80c13164fcf",
"n_sig": 48,
"sig_entry_idx": "4ca6e0b3ad9650db",
"sig_direction": "74de66c775f3159f",
"sig_bo_idx": "020d83285f111211",
"sig_pb_idx": "c9d29e0e1cf23857",
"sig_lag": "630718fda8c18f8b",
"sig_depth": "cbe22a059a935417",
"sig_zone_i": "499d9a4ffc4e4fac",
"col_open": "da5240f8ff94b7d3",
"col_high": "c06a481db2faee01",
"col_low": "07108f56862e97f8",
"col_close": "878c50482e134764",
"col_volume": "e57b8dbfb3589a2a",
"col_atr": "2e57310f4bb4f259",
"col_macd": "807220fb465a46f1",
"col_macdsignal": "079fd0757f9a6040",
"col_macdhist": "2372736ddaa73786",
"col_ema5": "5f7cc824e469e468",
"col_ema13": "be17db658716e40f",
"col_ema24": "dae2098cf4b4a029",
"col_ema26": "1d6e667de928a8b4",
"col_ema52": "a9521436966fff79",
"col_ema104": "1d1373a8c3e48cd9",
"col_ema156": "679d2e502207f085",
"col_ema208": "8790e0e5c50bfbc3",
"col_ema7": "b631eeb99b06b174",
"col_rsi": "f1b3f6541bcefdc9",
"col_volume_ratio": "3db5f52fe7051017",
"col_bb2633upper": "cbd0d954047104f5",
"col_bb2633lower": "e1a83624de00b90c",
"col_bb2633middle": "a2a24306ccdc04c4",
"col_bbp30": "453235723fa5bc76",
"col_bbp120": "f67bad6c333f35b7",
"col_bbp365": "d68b613efed41cd6",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
},
"ETH_5m": {
"n_rows": 20000,
"n_klu": 20000,
"n_klc": 14342,
"n_bi": 927,
"n_seg": 142,
"n_bi_zs": 100,
"n_zs": 15,
"n_bsp": 0,
"klc_high": "131ed2d2cc245008",
"klc_low": "d53c72dc569bbd4f",
"klc_fx": "ec3177b225143a7a",
"bi_start": "fb708274964722c2",
"bi_end": "fb708274964722c2",
"bi_sure": "b7b4cb502f2a0d70",
"n_zones": 100,
"zone_zg": "dca84f6a299fd1aa",
"zone_zd": "d462f78cc4b7538b",
"zone_avail": "b92e97fc130e310d",
"zone_ladder": "cfc5c84c6a6d90f2",
"n_sig": 42,
"sig_entry_idx": "82e50dda58170b06",
"sig_direction": "c7e63a0158b14ef2",
"sig_bo_idx": "d9c826c620ff6bf2",
"sig_pb_idx": "25340fc752a8143c",
"sig_lag": "a6fa56f9f5b5f367",
"sig_depth": "71d0776582fad395",
"sig_zone_i": "f87a3a29b6b26adb",
"col_open": "4875825277eb22d2",
"col_high": "efc10b5252481a18",
"col_low": "c2978c8eb3cdf78c",
"col_close": "e0bc2b374fa5b33d",
"col_volume": "f97c30ec4bb0ecf8",
"col_atr": "8d6f2061d285ad98",
"col_macd": "c8fc7f49467985ee",
"col_macdsignal": "b6af44d80ea485c1",
"col_macdhist": "fa854fe5a2055930",
"col_ema5": "c6f2736ca8f67a11",
"col_ema13": "2103d16e3db9dff7",
"col_ema24": "e72b6c1ac9118ebd",
"col_ema26": "a83023c8930a807d",
"col_ema52": "ce2971fa029ee7b7",
"col_ema104": "3cbe3efc7b6d8457",
"col_ema156": "84e140907ef54b4c",
"col_ema208": "e28316d21c6d29e7",
"col_ema7": "5bbf9ce6610e1574",
"col_rsi": "c4c6ff1145c88981",
"col_volume_ratio": "bfb7636b8d92fca1",
"col_bb2633upper": "f0068bec633e28d4",
"col_bb2633lower": "78936103ef228327",
"col_bb2633middle": "3b4a8fd306318760",
"col_bbp30": "225773d67ca05c7e",
"col_bbp120": "1084331d91660254",
"col_bbp365": "ea71e0285f2801ba",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
},
"SOL_15m": {
"n_rows": 20000,
"n_klu": 20000,
"n_klc": 14051,
"n_bi": 894,
"n_seg": 131,
"n_bi_zs": 115,
"n_zs": 7,
"n_bsp": 0,
"klc_high": "6514126fb15a2495",
"klc_low": "1a95961cd8510b61",
"klc_fx": "39a6f1e8cbe15296",
"bi_start": "93d17db2c0726ad2",
"bi_end": "93d17db2c0726ad2",
"bi_sure": "72a7eeed9f203249",
"n_zones": 115,
"zone_zg": "bdddd8960357e2c2",
"zone_zd": "a72a80f2ec747fa9",
"zone_avail": "d6bec0bead4ca986",
"zone_ladder": "4bbd1a9e4080a3d4",
"n_sig": 33,
"sig_entry_idx": "57154349f7256bd5",
"sig_direction": "3456806b7f3f915a",
"sig_bo_idx": "d3a1e9433c85a1d0",
"sig_pb_idx": "7584cbbe94a65ea5",
"sig_lag": "0d9429413d5a72ba",
"sig_depth": "5fefacd49949cd9f",
"sig_zone_i": "99844be679dbc613",
"col_open": "0c38967f4d88ca2f",
"col_high": "9d506e4c74c1d73c",
"col_low": "416ac69aa8a7a5c4",
"col_close": "27579fe4a877d720",
"col_volume": "a8c30f4f5a732e91",
"col_atr": "366c4ec27cd52632",
"col_macd": "449587b2c37e8df2",
"col_macdsignal": "83de7bfbe9224c61",
"col_macdhist": "e94458f1558bd910",
"col_ema5": "cd13382ef7069033",
"col_ema13": "6a9429bffb033b9a",
"col_ema24": "41a037d614e701ba",
"col_ema26": "c46e3a74f471bbb0",
"col_ema52": "bf8d0b1208647291",
"col_ema104": "4f229c578d7998e4",
"col_ema156": "1fd8a8c38d11f081",
"col_ema208": "3e2704d5c53ee9f8",
"col_ema7": "21281407046b4322",
"col_rsi": "a800c451254f433e",
"col_volume_ratio": "ec05ddc04f54b31b",
"col_bb2633upper": "9c0bb9a91102bcd9",
"col_bb2633lower": "b55705f39cdb0c63",
"col_bb2633middle": "365c7763ddad555c",
"col_bbp30": "8ab505323d0cb0a9",
"col_bbp120": "8a16100ed6fe37b6",
"col_bbp365": "437b91a2cdde91eb",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
},
"XRP_30m": {
"n_rows": 20000,
"n_klu": 20000,
"n_klc": 14763,
"n_bi": 849,
"n_seg": 125,
"n_bi_zs": 108,
"n_zs": 11,
"n_bsp": 0,
"klc_high": "567e7651c968c00c",
"klc_low": "c1b8189a9d447452",
"klc_fx": "67b72deec2a5593b",
"bi_start": "04f30102940f974d",
"bi_end": "04f30102940f974d",
"bi_sure": "da571f5278b770e1",
"n_zones": 108,
"zone_zg": "1eefb80dd6c76a53",
"zone_zd": "8fa0d96149ddc204",
"zone_avail": "7e652fb397da32da",
"zone_ladder": "4177c4b5a40b6247",
"n_sig": 40,
"sig_entry_idx": "d550aee46e46c917",
"sig_direction": "81f88af0167a498c",
"sig_bo_idx": "f704a273ed48013f",
"sig_pb_idx": "2e030812f5243076",
"sig_lag": "1c1eebf161a0ff83",
"sig_depth": "c671af2668c0cc78",
"sig_zone_i": "49971affbaf93434",
"col_open": "e064238e9f5e7baa",
"col_high": "a28e55a860c236ce",
"col_low": "7585fe470cd2ecfd",
"col_close": "3470d49402aa5a5e",
"col_volume": "0b473a543b78cd89",
"col_atr": "967be1c16f118183",
"col_macd": "cf3b1cc2d9f3ca98",
"col_macdsignal": "993858809b2f3a2e",
"col_macdhist": "469650fc16672829",
"col_ema5": "0c7e735c6abfc0b5",
"col_ema13": "bc9799632b2232f5",
"col_ema24": "9397472a7982e2e4",
"col_ema26": "59ec2e87acb64341",
"col_ema52": "f61f6de2169678b8",
"col_ema104": "fe786b2dc9436f25",
"col_ema156": "1291ef40a77682d5",
"col_ema208": "7e63bf51454a5dfb",
"col_ema7": "25983717dea0f463",
"col_rsi": "b992c428c2643962",
"col_volume_ratio": "4d70f107c08380cc",
"col_bb2633upper": "cbe0b203046d7377",
"col_bb2633lower": "166c968182153faf",
"col_bb2633middle": "7ba495bfe685d0eb",
"col_bbp30": "598150e822aac882",
"col_bbp120": "36778d36238896c4",
"col_bbp365": "a1f88436a790dc2a",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
}
}
+487
View File
@@ -0,0 +1,487 @@
{
"BTC_1m": {
"n_rows": 200000,
"n_klu": 200000,
"n_klc": 131701,
"n_bi": 9504,
"n_seg": 1406,
"n_bi_zs": 1073,
"n_zs": 155,
"n_bsp": 0,
"klc_high": "992af879db2e8e43",
"klc_low": "8ba4f68b829b552c",
"klc_fx": "03870041e7b334e7",
"bi_start": "3d8fbede022d751d",
"bi_end": "3d8fbede022d751d",
"bi_sure": "bd74890c6552b345",
"n_zones": 1073,
"zone_zg": "0436269233f63984",
"zone_zd": "5b3cc3b0b0eafd20",
"zone_avail": "b45174f22f908d5b",
"zone_ladder": "1342f5e104d399fa",
"n_sig": 396,
"sig_entry_idx": "c9728ba1d4a739c5",
"sig_direction": "8bc66cd2f6c0ffa8",
"sig_bo_idx": "2981b0d3134b99cc",
"sig_pb_idx": "21ec7c5b1f85cca4",
"sig_lag": "2f5640376a99c4ae",
"sig_depth": "cf3f0bf3d239c2f9",
"sig_zone_i": "8654e213e0faaa5f",
"col_open": "32e16cc827550cce",
"col_high": "9c9cb56b72814b2b",
"col_low": "4db855614f0f3254",
"col_close": "3707a170acb64306",
"col_volume": "4e3eb344a25eeec2",
"col_atr": "c6e1a3b27bfaec47",
"col_macd": "94a485845cf30bea",
"col_macdsignal": "87ec0074d9bc8cbc",
"col_macdhist": "5127835e6b590aff",
"col_ema5": "5f4851ec918152d5",
"col_ema13": "5ede020d68cde473",
"col_ema24": "dc45445e03565a7d",
"col_ema26": "7ffc3a0799531172",
"col_ema52": "dafc5dbfdac7ec7d",
"col_ema104": "6a5911c3a9daead3",
"col_ema156": "f7aa34a1c04ecd66",
"col_ema208": "773341a5170f61ee",
"col_ema7": "25b47f9884bc7eff",
"col_rsi": "d07bc07917c51b98",
"col_volume_ratio": "2b3cbd8c2fd4a0c7",
"col_bb2633upper": "8abeb464d97ed502",
"col_bb2633lower": "54f96c202ecd17c2",
"col_bb2633middle": "bf7a068c7592c379",
"col_bbp30": "e6b3f087308509bd",
"col_bbp120": "eb53451f0dadd779",
"col_bbp365": "c32489b1e8216c96",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
},
"BTC_5m": {
"n_rows": 200000,
"n_klu": 200000,
"n_klc": 146735,
"n_bi": 9670,
"n_seg": 1485,
"n_bi_zs": 1093,
"n_zs": 160,
"n_bsp": 0,
"klc_high": "dc921e6668a807e9",
"klc_low": "47a5508f3736f23b",
"klc_fx": "4a459666e121fee2",
"bi_start": "12144ccb870d12a1",
"bi_end": "12144ccb870d12a1",
"bi_sure": "0ee81c335c3c858e",
"n_zones": 1093,
"zone_zg": "54918583a2371b0f",
"zone_zd": "cb698e8570e2b3fc",
"zone_avail": "70a78276ce8d81e8",
"zone_ladder": "4eaf9f4810737d9b",
"n_sig": 396,
"sig_entry_idx": "c166fe6bbec08d80",
"sig_direction": "d376c8684119da87",
"sig_bo_idx": "1963ec7608066cf6",
"sig_pb_idx": "c7db54ba1103fe3b",
"sig_lag": "8cfbb30f7e26bcff",
"sig_depth": "fbc0589ca7908cee",
"sig_zone_i": "aa0a7f54921bfdb2",
"col_open": "b0ef7c7ed1122a9b",
"col_high": "c036264627035105",
"col_low": "aad6caaa73079e29",
"col_close": "00ccedeb197fd137",
"col_volume": "11686a1bed642a1f",
"col_atr": "1fa43f86914c4a8e",
"col_macd": "ad66e34357a586c7",
"col_macdsignal": "3b9a127b992897b3",
"col_macdhist": "c564a280ef8eb0f1",
"col_ema5": "74e476c0269798a9",
"col_ema13": "f6968ae7f127ebe2",
"col_ema24": "db3ae3b801d846f5",
"col_ema26": "adda6c2af103a6ed",
"col_ema52": "4d663556702cd4df",
"col_ema104": "c14c8d69fa4be179",
"col_ema156": "9e5df963de200c54",
"col_ema208": "30e0eabc1ef6e4fb",
"col_ema7": "a78d7798f10638fc",
"col_rsi": "c4dc1ca7d74b64f9",
"col_volume_ratio": "62694ace333f324a",
"col_bb2633upper": "35149f0ee32941d5",
"col_bb2633lower": "ce61849352496a88",
"col_bb2633middle": "62cf973be0c1d775",
"col_bbp30": "d8dc8265680ed17c",
"col_bbp120": "d951e014ae714540",
"col_bbp365": "a4f5700a169eed20",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
},
"ETH_5m": {
"n_rows": 200000,
"n_klu": 200000,
"n_klc": 147071,
"n_bi": 9668,
"n_seg": 1447,
"n_bi_zs": 1064,
"n_zs": 151,
"n_bsp": 0,
"klc_high": "b474c10434b7d1be",
"klc_low": "16c8b368522eff67",
"klc_fx": "454f30149c5437e9",
"bi_start": "e962ff5f90072953",
"bi_end": "e962ff5f90072953",
"bi_sure": "6702ee0ec3ba05b7",
"n_zones": 1064,
"zone_zg": "4f8fdc5ce3699a83",
"zone_zd": "55f8826e3850bbbb",
"zone_avail": "b4af5d59909c20de",
"zone_ladder": "7718718df232fbb6",
"n_sig": 357,
"sig_entry_idx": "6de39296c88fcbb5",
"sig_direction": "62e7be93232d7c6d",
"sig_bo_idx": "6ec163434edaf6e9",
"sig_pb_idx": "ca9b4000a3d8b167",
"sig_lag": "16e524f721f0a3aa",
"sig_depth": "bd78d76d5ac56d18",
"sig_zone_i": "ec18317accf8f34f",
"col_open": "6f693fe747fe72ea",
"col_high": "e5cf5f170c1b74c5",
"col_low": "9a21e97b26b281ee",
"col_close": "c65c480ea3acb4cf",
"col_volume": "deca090b35e34611",
"col_atr": "5c598706876091c4",
"col_macd": "3f38452148fbb5db",
"col_macdsignal": "848e9ea874b125cb",
"col_macdhist": "3e2301728b8c73ae",
"col_ema5": "e66614435995ca8c",
"col_ema13": "5b90f189ecb9dbde",
"col_ema24": "dc98f535eb5b9a14",
"col_ema26": "98ce247c33012910",
"col_ema52": "ce2d49aa246f7d1b",
"col_ema104": "37a07762a9d5bfe1",
"col_ema156": "1040f54fb1f1a28c",
"col_ema208": "2430fa3290e62444",
"col_ema7": "e85e8582b2dd352a",
"col_rsi": "f3d09ac8721406bd",
"col_volume_ratio": "5842f57c75a5c5a8",
"col_bb2633upper": "1e1622aad5a3dc46",
"col_bb2633lower": "58392bfb6e652321",
"col_bb2633middle": "72376e41d5a46393",
"col_bbp30": "1b99ed1cb7132665",
"col_bbp120": "b11c01bd8702106e",
"col_bbp365": "9613ed3ed332a0e2",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
},
"SOL_15m": {
"n_rows": 200000,
"n_klu": 200000,
"n_klc": 147387,
"n_bi": 9221,
"n_seg": 1443,
"n_bi_zs": 1097,
"n_zs": 153,
"n_bsp": 0,
"klc_high": "9873e17761a5f439",
"klc_low": "c26a95692a503700",
"klc_fx": "e9da3d4ecae9b7d6",
"bi_start": "7ae8f7ca13826214",
"bi_end": "7ae8f7ca13826214",
"bi_sure": "4dc74bb88bebe27b",
"n_zones": 1097,
"zone_zg": "4ac63d81a0c7f518",
"zone_zd": "38d5b7afd33e2e15",
"zone_avail": "532fd21eed5584f7",
"zone_ladder": "6139c6b7503d0c44",
"n_sig": 371,
"sig_entry_idx": "d74faae30a9c9d49",
"sig_direction": "038ed1f04dbe7141",
"sig_bo_idx": "9c2251f45e7b31f6",
"sig_pb_idx": "ac324f507086aec8",
"sig_lag": "b429021aa0a4d4da",
"sig_depth": "654d5249825e9903",
"sig_zone_i": "f070e3f31075cb19",
"col_open": "1c9998e987e16a5f",
"col_high": "80ed4bf62274de03",
"col_low": "09b8b6c12e4e3f06",
"col_close": "223ae3e7e1b7d05a",
"col_volume": "f311c91d123a55b5",
"col_atr": "ed2d754bd3b15f77",
"col_macd": "290ea71faef859e4",
"col_macdsignal": "70008a039281deb8",
"col_macdhist": "16535492810b17e8",
"col_ema5": "c2b75b157fee520a",
"col_ema13": "a38553a7296f8c14",
"col_ema24": "aa12c0c0fc8b839f",
"col_ema26": "2e04f45e2f2067fb",
"col_ema52": "361bd4886eaf510d",
"col_ema104": "beca8cd07bb88835",
"col_ema156": "941a4949be08fcbd",
"col_ema208": "82f3b64ae8e6c97c",
"col_ema7": "466b0319cd174bb2",
"col_rsi": "cd3eb3fa8896a0e6",
"col_volume_ratio": "c4d3a5bf0a821829",
"col_bb2633upper": "3599736cc0c8ed31",
"col_bb2633lower": "4afcdb806d790258",
"col_bb2633middle": "fea10757ba7fdeb6",
"col_bbp30": "64222d075acd186e",
"col_bbp120": "d17f37169b2a5966",
"col_bbp365": "943089e90d9da3ba",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
},
"XRP_30m": {
"n_rows": 116374,
"n_klu": 116374,
"n_klc": 82461,
"n_bi": 5109,
"n_seg": 784,
"n_bi_zs": 572,
"n_zs": 74,
"n_bsp": 0,
"klc_high": "2f214dccb66c2acb",
"klc_low": "11763813065965dc",
"klc_fx": "16a8abb38cc08276",
"bi_start": "ddb3716c5603fc9f",
"bi_end": "ddb3716c5603fc9f",
"bi_sure": "45d38e2edf89a78e",
"n_zones": 572,
"zone_zg": "6301d2ba0529348a",
"zone_zd": "02798be778a12b3c",
"zone_avail": "f7627b9e187434f8",
"zone_ladder": "79b4eba53b7f16b4",
"n_sig": 185,
"sig_entry_idx": "3fd118bcca9c0318",
"sig_direction": "4e5c8a66c981612e",
"sig_bo_idx": "359749625e049f17",
"sig_pb_idx": "30d938988a47152e",
"sig_lag": "6ecaeb56a6377c02",
"sig_depth": "b472e1cc5387f001",
"sig_zone_i": "beb04af56f37bf08",
"col_open": "d8eb9bfb0cb6375a",
"col_high": "c8305b04a6736acf",
"col_low": "b00124fab8245af3",
"col_close": "69e3cdd2e55f4809",
"col_volume": "839836338744c27c",
"col_atr": "5900706833e9edba",
"col_macd": "88f65443d79c489f",
"col_macdsignal": "f1fd2dcea6c73575",
"col_macdhist": "fce95fc9ccb6b57e",
"col_ema5": "5c6b418babd70b93",
"col_ema13": "4775865a25779b16",
"col_ema24": "f24136710438fa4e",
"col_ema26": "c8a14eab58bc38dc",
"col_ema52": "602f9cd13a369f59",
"col_ema104": "aefabaf6c26d4977",
"col_ema156": "a09b1eb8b3435f96",
"col_ema208": "861be94bc86d83b8",
"col_ema7": "7e605fb6cd8fa03f",
"col_rsi": "ab9617b826fb1cf1",
"col_volume_ratio": "adeab54de59ff72c",
"col_bb2633upper": "a4e7709df8bebb48",
"col_bb2633lower": "e62b8f228e730261",
"col_bb2633middle": "5799755b2c60e9fe",
"col_bbp30": "a404273b177e495e",
"col_bbp120": "46c63a30348ad94e",
"col_bbp365": "f974d0696160d78d",
"_all_columns": [
"atr",
"bb2633lower",
"bb2633middle",
"bb2633upper",
"bblow120",
"bblow30",
"bblow302",
"bblow365",
"bbmiddle30",
"bbp120",
"bbp2633",
"bbp30",
"bbp302",
"bbp365",
"bbup120",
"bbup30",
"bbup302",
"bbup365",
"close",
"date",
"ema10",
"ema104",
"ema13",
"ema156",
"ema208",
"ema24",
"ema26",
"ema5",
"ema52",
"ema7",
"high",
"low",
"macd",
"macdhist",
"macdsignal",
"open",
"rsi",
"timestamp",
"volume",
"volume_ratio"
]
}
}
+200
View File
@@ -0,0 +1,200 @@
"""Step 46:缠论引擎的逐位对拍基线。
重构引擎前先固化一份指纹,改完再对一次。没有它,任何"精简"都无法证明
没有改变行为——而 HANDOFF 里所有回测数字都绑定当前实现,**行为变化是静默的**:
不报错、不崩溃,只是信号悄悄变了一批。
指纹覆盖三条链路各自依赖的东西:
结构 klu / klc / bi / bi_zs / seg 的数量与关键端点
信号 中枢表(zg/zd/available_ts) 与 fast_bsp3 的全部输出列
数值 dataframe 上被下游真正消费的列(逐位比较)
用法:
python step46_engine_parity.py --save # 改动前,存基线
python step46_engine_parity.py --check # 改动后,对比
"""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
import warnings
from pathlib import Path
import numpy as np
import pandas as pd
warnings.filterwarnings("ignore")
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
sys.path.insert(0, str(HERE.parent))
pd.set_option("display.width", 300)
BASELINE = HERE / "out" / "step46_baseline.json"
# 覆盖面:多币多级别,1m 用较短窗口以免跑太久
CASES = [
("BTC/USDT:USDT", "1m", 20_000),
("BTC/USDT:USDT", "5m", 20_000),
("ETH/USDT:USDT", "5m", 20_000),
("SOL/USDT:USDT", "15m", 20_000),
("XRP/USDT:USDT", "30m", 20_000),
]
# 下游真正消费的列(见 §5.5 的列使用扫描)。精简若动到这些,必须体现在指纹里。
CONSUMED = ["open", "high", "low", "close", "volume", "atr",
"macd", "macdsignal", "macdhist",
"ema5", "ema13", "ema24", "ema26", "ema52", "ema104", "ema156", "ema208", "ema7",
"rsi", "volume_ratio",
"bb2633upper", "bb2633lower", "bb2633middle",
"bbp30", "bbp120", "bbp365"]
def _h(arr) -> str:
a = np.asarray(arr, dtype=np.float64)
a = np.nan_to_num(a, nan=-9.87654321e30, posinf=1e300, neginf=-1e300)
return hashlib.sha256(a.tobytes()).hexdigest()[:16]
def fingerprint(pair: str, tf: str, rows: int) -> dict:
from chanlun import TF_DF
from chanlun.analysis.fast_bsp import (
add_zone_ladder, build_htf_zones, find_fast_bsp3,
)
from lib.data import fetch_ohlcv
df = fetch_ohlcv(pair, tf, rows)
chan = TF_DF(df, 1, tf)
cdf = chan.dataframe
fp: dict = {"n_rows": int(len(cdf))}
# --- 结构 ---
fp["n_klu"] = len(getattr(chan, "klu_list", []) or [])
fp["n_klc"] = len(getattr(chan, "klc_list", []) or [])
fp["n_bi"] = len(getattr(chan, "bi_list", []) or [])
fp["n_seg"] = len(getattr(chan, "seg_list", []) or [])
fp["n_bi_zs"] = len(getattr(chan, "bi_zs_list", []) or [])
fp["n_zs"] = len(getattr(chan, "zs_list", []) or [])
fp["n_bsp"] = len(getattr(chan, "bsp_list", []) or [])
# KLC 端点(包含关系的结果,最容易被指标改动影响)
klc = getattr(chan, "klc_list", []) or []
fp["klc_high"] = _h([k.high for k in klc])
fp["klc_low"] = _h([k.low for k in klc])
fp["klc_fx"] = _h([float(getattr(k.fx, "value", 0) or 0) for k in klc])
# 笔端点
bi = getattr(chan, "bi_list", []) or []
fp["bi_start"] = _h([float(getattr(b, "start_price", 0) or 0) for b in bi])
fp["bi_end"] = _h([float(getattr(b, "end_price", 0) or 0) for b in bi])
fp["bi_sure"] = _h([1.0 if getattr(b, "is_sure", False) else 0.0 for b in bi])
# --- 信号 ---
zones = build_htf_zones(cdf, tf, chan=chan)
fp["n_zones"] = int(len(zones))
if len(zones):
zl = add_zone_ladder(zones.reset_index(drop=True))
fp["zone_zg"] = _h(zl["zg"])
fp["zone_zd"] = _h(zl["zd"])
fp["zone_avail"] = _h(zl["available_ts"])
fp["zone_ladder"] = _h(zl["z_above"].astype(float) * 2 + zl["z_below"].astype(float))
sig = find_fast_bsp3(cdf, zl)
fp["n_sig"] = int(len(sig))
for c in ("entry_idx", "direction", "bo_idx", "pb_idx", "lag", "depth", "zone_i"):
if c in sig.columns:
fp[f"sig_{c}"] = _h(sig[c])
else:
fp["n_sig"] = 0
# --- 数值列(只对下游消费的列逐位比较)---
for c in CONSUMED:
fp[f"col_{c}"] = _h(cdf[c]) if c in cdf.columns else "MISSING"
fp["_all_columns"] = sorted(map(str, cdf.columns))
return fp
def collect() -> dict:
out = {}
for pair, tf, rows in CASES:
key = f"{pair.split('/')[0]}_{tf}"
print(f" 计算 {key} ...", flush=True)
try:
out[key] = fingerprint(pair, tf, rows)
except Exception as e:
out[key] = {"error": repr(e)[:200]}
print(f" 失败: {e!r}", flush=True)
return out
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--save", action="store_true", help="存为基线")
ap.add_argument("--check", action="store_true", help="与基线对比")
ap.add_argument("--rows", type=int, default=0, help="覆盖各用例根数(验证大样本时用)")
ap.add_argument("--out", default="", help="基线文件名,便于大小样本各存一份")
args = ap.parse_args()
global BASELINE, CASES
if args.out:
BASELINE = HERE / "out" / args.out
if args.rows:
CASES = [(p, tf, args.rows) for p, tf, _ in CASES]
if not (args.save or args.check):
ap.error("需要 --save 或 --check")
BASELINE.parent.mkdir(exist_ok=True)
print(f"[引擎对拍] {len(CASES)} 个用例\n")
cur = collect()
if args.save:
BASELINE.write_text(json.dumps(cur, ensure_ascii=False, indent=1))
print(f"\n基线已存:{BASELINE}")
for k, v in cur.items():
if "error" in v:
continue
print(f" {k}: klu {v['n_klu']} klc {v['n_klc']} bi {v['n_bi']} "
f"中枢 {v['n_zones']} 信号 {v['n_sig']} 列数 {len(v['_all_columns'])}")
return
if not BASELINE.exists():
print(f"基线不存在:{BASELINE},先跑 --save")
return
old = json.loads(BASELINE.read_text())
print("\n" + "=" * 90)
bad = 0
for key in sorted(set(old) | set(cur)):
o, n = old.get(key), cur.get(key)
if o is None or n is None:
print(f"{key}: 用例缺失")
bad += 1
continue
# 列集合单独看:删列是预期内的,不算行为变化
o_cols, n_cols = set(o.get("_all_columns", [])), set(n.get("_all_columns", []))
diffs = [k for k in o if k != "_all_columns" and o.get(k) != n.get(k)]
dropped, added = sorted(o_cols - n_cols), sorted(n_cols - o_cols)
# 被删列在指纹里会变成 MISSING,若该列本就不被消费则无害
harmful = [d for d in diffs if not (d.startswith("col_") and n.get(d) == "MISSING"
and d[4:] not in CONSUMED)]
if not harmful:
print(f"{key}: 行为一致"
+ (f"(删列 {len(dropped)} 个)" if dropped else ""))
else:
bad += 1
print(f"{key}: {len(harmful)} 项不一致")
for d in harmful[:12]:
print(f" {d}: {o.get(d)}{n.get(d)}")
if dropped:
print(f" 删掉的列: {dropped}")
if added:
print(f" 新增的列: {added}")
print("\n" + ("✅ 全部用例行为一致,可以放心继续" if not bad
else f"{bad} 个用例有行为变化——**回测数字已失效,不要继续**"))
if __name__ == "__main__":
main()