From f63eda9e51da31f8c2eec9926a747cb7ea9dc542 Mon Sep 17 00:00:00 2001 From: zhaoli Date: Fri, 28 Aug 2026 12:02:45 +0000 Subject: [PATCH] start experiment 64 (exp/64-scheduled-algo-retrain-on-2026-08-27-tac) --- code/MANIFEST.txt | 28 +-- .../__pycache__/__init__.cpython-312.pyc | Bin 374 -> 374 bytes .../data/__pycache__/__init__.cpython-312.pyc | Bin 216 -> 216 bytes .../data/__pycache__/handler.cpython-312.pyc | Bin 11103 -> 21533 bytes .../tac-qlib/tac_qlib/contrib/data/handler.py | 181 +++++++++++++++++- .../__pycache__/__init__.cpython-312.pyc | Bin 319 -> 319 bytes .../__pycache__/rank_ensemble.cpython-312.pyc | Bin 9461 -> 9461 bytes .../__pycache__/rank_gbdt.cpython-312.pyc | Bin 12288 -> 12288 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 301 -> 301 bytes .../__pycache__/long_short.cpython-312.pyc | Bin 19672 -> 19672 bytes .../__pycache__/optimal_stop.cpython-312.pyc | Bin 10428 -> 10428 bytes .../data/__pycache__/__init__.cpython-312.pyc | Bin 522 -> 522 bytes .../data/__pycache__/config.cpython-312.pyc | Bin 11507 -> 11507 bytes .../__pycache__/providers.cpython-312.pyc | Bin 13340 -> 13340 bytes 14 files changed, 191 insertions(+), 18 deletions(-) diff --git a/code/MANIFEST.txt b/code/MANIFEST.txt index 9bdb016..31bf54b 100644 --- a/code/MANIFEST.txt +++ b/code/MANIFEST.txt @@ -1,34 +1,34 @@ # TradeAC custom-qlib-code snapshot (auto-generated) -# parent repo HEAD : ee5aa8e73c5286860d3ec1dfa6001bf0f8b6690d +# parent repo HEAD : 1142cc9eefde88739f33f34aa96af7b182320d96 # tac-qlib/tac_qlib/contrib # tac-qlib/tac_qlib/data # per-file hashes (git hash-object): 1b6298c4a5652f2e863cbdc385a1014a570fcd59 tac-qlib/tac_qlib/contrib/__init__.py - e21663470e4ab108691392c3f0bcff0a3e7fe459 tac-qlib/tac_qlib/contrib/__pycache__/__init__.cpython-312.pyc + 6c57851807631dfa1a525f87538a1b0a495fd7b2 tac-qlib/tac_qlib/contrib/__pycache__/__init__.cpython-312.pyc 2224424d0ff193be4f55d1b791f8fce89439c5d2 tac-qlib/tac_qlib/contrib/backtest/__init__.py 0bf40dee440ddbded357d7bbb4efc67c62c4b084 tac-qlib/tac_qlib/contrib/backtest/tradeac_exchange.py c76a9f17f680e74eea766eff27f7624359749ed6 tac-qlib/tac_qlib/contrib/data/__init__.py - a5c6952e9c8bab2c83fd8030416999726aea73d9 tac-qlib/tac_qlib/contrib/data/__pycache__/__init__.cpython-312.pyc - 39f148bef96fcce8f548d74c6d338eccdc4145e3 tac-qlib/tac_qlib/contrib/data/__pycache__/handler.cpython-312.pyc - fc3d01d530d4e84b3386614b6ac94324e811920e tac-qlib/tac_qlib/contrib/data/handler.py + 1acd2cb845eac1bcee54450004a4af36484544ed tac-qlib/tac_qlib/contrib/data/__pycache__/__init__.cpython-312.pyc + 2ef18965f77e8955580334d2edc09bd381204355 tac-qlib/tac_qlib/contrib/data/__pycache__/handler.cpython-312.pyc + 3bba0f1696e4ab4b3deebec3f31f269b2e713899 tac-qlib/tac_qlib/contrib/data/handler.py b151d139a0dcde87d74b21e7c4b729176ba5c39b tac-qlib/tac_qlib/contrib/model/__init__.py - f76257db439b030d84ecea2b8ae67079bae4dc6f tac-qlib/tac_qlib/contrib/model/__pycache__/__init__.cpython-312.pyc - 858f5fcbe285642569c214d5803edbae1e5dfa1e tac-qlib/tac_qlib/contrib/model/__pycache__/rank_ensemble.cpython-312.pyc - 7de125f7f263c554b3337138c760d44394b4824a tac-qlib/tac_qlib/contrib/model/__pycache__/rank_gbdt.cpython-312.pyc + c975d2b978f2cc08a388a5d921938704a3dd592d tac-qlib/tac_qlib/contrib/model/__pycache__/__init__.cpython-312.pyc + 009ebd83c5156ca3d7039a112e0d277dd416ca86 tac-qlib/tac_qlib/contrib/model/__pycache__/rank_ensemble.cpython-312.pyc + 1716b680b5623394229f7600ad4c81ad07fa6a2b tac-qlib/tac_qlib/contrib/model/__pycache__/rank_gbdt.cpython-312.pyc d3f051f3a8650c42fedc7b367b966f7c74fb5789 tac-qlib/tac_qlib/contrib/model/rank_ensemble.py d03e6611338918d4aac5eea4adf26f85a3763652 tac-qlib/tac_qlib/contrib/model/rank_gbdt.py 184f80da8edf944bad3c8fb4d4d3d189bf4f082b tac-qlib/tac_qlib/contrib/strategy/__init__.py - 6893c2097f0de110909f3f5076ac1c32ac5b742b tac-qlib/tac_qlib/contrib/strategy/__pycache__/__init__.cpython-312.pyc - 423ec5cd15b273c119ba3b1d7dc7467fc6ba41c4 tac-qlib/tac_qlib/contrib/strategy/__pycache__/long_short.cpython-312.pyc - b6b3238699337d559868cb5f8e0c3e900e5da0cf tac-qlib/tac_qlib/contrib/strategy/__pycache__/optimal_stop.cpython-312.pyc + 9c9f7743970b1a3827bb72768bb6e8be03040759 tac-qlib/tac_qlib/contrib/strategy/__pycache__/__init__.cpython-312.pyc + 6e38a7fa8584b80410ccc88e5feff228a7ece38b tac-qlib/tac_qlib/contrib/strategy/__pycache__/long_short.cpython-312.pyc + f983d5c2472cd16ef9f14a240674ec0a7f41e81c tac-qlib/tac_qlib/contrib/strategy/__pycache__/optimal_stop.cpython-312.pyc 896ef74ae47bcd1ed388e1e5d9c8d70c28097fe9 tac-qlib/tac_qlib/contrib/strategy/kelly_dropout.py 9090fc6dfbd339f2f4df4b0c9b87f400ecb5c9d5 tac-qlib/tac_qlib/contrib/strategy/long_short.py 79aaad9e39fcc740a773f4f63c512ce1086cfde0 tac-qlib/tac_qlib/contrib/strategy/optimal_stop.py 5b9acfb4340111b204249add7760bd53c6ae03f1 tac-qlib/tac_qlib/contrib/strategy/regime_gate.py fe60bacdfedd48617863be31f24b7c7daebfac5a tac-qlib/tac_qlib/contrib/strategy/weekly_rebalance.py 92e6e90eb0cd0a25142034560f27adb6b705b1a8 tac-qlib/tac_qlib/data/__init__.py - fa1090eb6f17b1b7835038e35fac86334294d165 tac-qlib/tac_qlib/data/__pycache__/__init__.cpython-312.pyc - 48629a1f51bd4058036ce78d79ed49454982c051 tac-qlib/tac_qlib/data/__pycache__/config.cpython-312.pyc - ad7c9b726c9c52773fa82031fcd681337be97fa8 tac-qlib/tac_qlib/data/__pycache__/providers.cpython-312.pyc + a0e969bd6504bb8d9220f4641cc01e960c3120e4 tac-qlib/tac_qlib/data/__pycache__/__init__.cpython-312.pyc + 2f8c537d11135155539276bee342d087aad8743e tac-qlib/tac_qlib/data/__pycache__/config.cpython-312.pyc + 53cf7828c425f6b5032b206b93a238607111a6ed tac-qlib/tac_qlib/data/__pycache__/providers.cpython-312.pyc 1953fb2a6371525db7f7b0e1c9dfbf3492d82110 tac-qlib/tac_qlib/data/config.py 8d0644f6f0d1efb94798ed444cc73e63b643459b tac-qlib/tac_qlib/data/providers.py diff --git a/code/tac-qlib/tac_qlib/contrib/__pycache__/__init__.cpython-312.pyc b/code/tac-qlib/tac_qlib/contrib/__pycache__/__init__.cpython-312.pyc index e21663470e4ab108691392c3f0bcff0a3e7fe459..6c57851807631dfa1a525f87538a1b0a495fd7b2 100644 GIT binary patch delta 20 acmeyy^o@!8G%qg~0}w>)>fOkl%?JQMjRmp* delta 20 acmeyy^o@!8G%qg~0}zO;?b^tl%?JQKR0UoD diff --git a/code/tac-qlib/tac_qlib/contrib/data/__pycache__/__init__.cpython-312.pyc b/code/tac-qlib/tac_qlib/contrib/data/__pycache__/__init__.cpython-312.pyc index a5c6952e9c8bab2c83fd8030416999726aea73d9..1acd2cb845eac1bcee54450004a4af36484544ed 100644 GIT binary patch delta 19 Zcmcb?c!QDqG%qg~0}w>)>Yd1a763Mb1+@SG delta 19 Zcmcb?c!QDqG%qg~0}zO;?V8Aa763G!1z`XH diff --git a/code/tac-qlib/tac_qlib/contrib/data/__pycache__/handler.cpython-312.pyc b/code/tac-qlib/tac_qlib/contrib/data/__pycache__/handler.cpython-312.pyc index 39f148bef96fcce8f548d74c6d338eccdc4145e3..2ef18965f77e8955580334d2edc09bd381204355 100644 GIT binary patch delta 10735 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 delta 1643 zcmZWpTWB0r7@jkm-E3}q>25ZgdzuSz8r;N|5PP!?$!?7%n_C<0L>bmSv&p2h+2o%| zW71M0qzaW(>rq5)Er<^-(iazesSk=TqJl-xhtVQ^5lRqL>Z|qtXWOWF9=`dn=Y0Qv z{xesuv7cuGU)R)BO1PpoE-c*Xz7&YF&P(0H13t4VU#-?Ki8Eeq2J%5Q=*oUGln<+6 zlq-0pxho%0Bl)Nr&DW~6`Is8J^l{hWIv(O-gYjKF!lU@DzAVqlYMj@q^*p9F@VZS& zZRE|og~vC&YLhG-lX(56#H;k0g#+UA+Q@zn5t1ae#q~9yuTc+v^w8QmC-J5anA)bd z=rvkPDOQr!JW6Zv7Q5hK>&02`o3gJNJ2$I)l zSQ<9~_KAa`ead07A0WFYm&pU=!yyEVnoaw=>nI-bcBs|+G#ZD*&Cpk@Pn_CQU%VRD zclcLE8zQArD4m*sshBqqZD*&28tq_x#dFa+tZygCsarOU7ZN;UXI!v!JF#LLrUlcO z?RYKI$m;O8cr6wm9!HDfg$Z)VJm>gUjYVEqEk&~IPCM{K3i0F&dp$4=g{48$OlQ&w z)Xx;RVS{+0>UH_m|~#570Xu!!*XC24Qa>2Gi#1~V!Ajo^dakU%5!=K zZ%cEd4K#7*PTPTA~ zutER?s{}6+>~tR1NL@y70uN5sa)xel%OwCwa;HRlimoo1dZdk-3UttVX6VhW6`iR?LA3srM9uawPXEq7Yzvn5S%j8uxw}7)ZjB(Dmi%k#8gsCjU-2hr=d-J zH_~yEhSKq5IEQYOlnOXUsV?af15(}LWO(Bxf=-#G>x+)3uwq*k=*J-|ty0g|(X`$v zJ{yaL{#)1&Qqg|u8@c)}-7FB?Wny%!r>>3kR|w7{I9_{Y32U1X7stANZrNwW&9OH5 z7fIY6yU)_%cDk;b=EPzMIBsu@?~VLUx*uUf`YG6S%ijcjA~`LK3x;TfOdlla9r>zAyz)iHau;#LbZ(<8XxxyOGe3lSB34|2=e|yMx_gVroWx zpTw&KFB7=z>Bm>q|*gPB)83 mr(f;PGB(3rZ@wqF!XKj^scM*Ql>b&OwJNuy(DzcKyVc)qCXs^x diff --git a/code/tac-qlib/tac_qlib/contrib/data/handler.py b/code/tac-qlib/tac_qlib/contrib/data/handler.py index fc3d01d..3bba0f1 100644 --- a/code/tac-qlib/tac_qlib/contrib/data/handler.py +++ b/code/tac-qlib/tac_qlib/contrib/data/handler.py @@ -15,6 +15,9 @@ import os from inspect import getfullargspec from typing import List, Optional, Tuple, Union +import numpy as np +import pandas as pd + from qlib.data.dataset import processor as processor_module from qlib.data.dataset.handler import DataHandlerLP from qlib.utils import get_callable_kwargs @@ -144,6 +147,174 @@ class DropAllNaN(processor_module.Processor): return df +class BenchResidual(processor_module.Processor): + """Subtract a benchmark instrument's forward return from the label, per datetime. + + Turns the training target from an absolute-return rank into a *residual* rank: + ``r_i - r_bench`` is ranked cross-sectionally by the downstream ``CSRankNorm`` / + ``CSZScoreNorm`` processors instead of ``r_i`` alone. Must be inserted BEFORE any + per-date normalization so the ranking itself is computed on residual returns + (ordering flips exactly where the benchmark trends). + + Stateless: ``fit`` is a no-op and the benchmark forward return is recomputed from + the lake parquet on first ``__call__``. Rows whose benchmark value is missing are + left untouched. Accepts ``fit_start_time``/``fit_end_time`` (ignored) so + ``check_transform_proc`` can inject the fit window uniformly. + + NOTE: under any cross-sectional normalization downstream (``CSRankNorm`` / + ``CSZScoreNorm``) this processor is a mathematical no-op: subtracting the same + per-date constant preserves ranks, and z-scoring absorbs constant shifts. Use + ``BenchBetaResidual`` for a target that actually reorders. + """ + + def __init__( + self, + benchmark="SPY", + fields_group="label", + lake_root=None, + market="US", + timeframe=None, + freq="day", + fit_start_time=None, + fit_end_time=None, + ): + self.benchmark = benchmark + self.fields_group = fields_group + self.lake_root = lake_root + self.market = market + self.timeframe = timeframe or timeframe_for_freq(freq) + self.fit_start_time = fit_start_time + self.fit_end_time = fit_end_time + self._bench_label = None + + def _load_bench_label(self): + if self._bench_label is not None: + return self._bench_label + cfg = LakeConfig(self.lake_root, self.market) + p = cfg.bar_path(self.timeframe, self.benchmark) + if not p.exists(): + raise FileNotFoundError(f"BenchResidual: benchmark bar file not found: {p}") + df = pd.read_parquet(p) + s = pd.Series(df["c"].astype(float).values, index=pd.to_datetime(df["t"])).sort_index() + s.index = s.index.normalize() + # mirror Ref($close,-6)/Ref($close,-1)-1 on the benchmark's own calendar + bench_label = s.shift(-6) / s.shift(-1) - 1 + self._bench_label = bench_label[~bench_label.index.duplicated(keep="last")] + return self._bench_label + + def fit(self, df=None): + return self + + def __call__(self, df): + bl = self._load_bench_label() + cols = processor_module.get_group_columns(df, self.fields_group) + dt = df.index.get_level_values("datetime") + aligned = bl.reindex(pd.DatetimeIndex(dt.unique())).reindex(dt) + mask = aligned.notna().values + out = df.copy() + for c in cols: + vals = df[c].values + res = vals.copy() + res[mask] = np.asarray(vals[mask], dtype=float) - aligned[mask].values + out[c] = res + return out + + +class BenchBetaResidual(processor_module.Processor): + """Residualize the label against a beta-scaled benchmark move: ``r_i - b_i * r_bench``. + + Unlike a plain constant subtraction (see ``BenchResidual``), the name-specific rolling + beta ``b_i`` makes this survive cross-sectional normalization: in up-weeks high-beta + names lose rank, in down-weeks they gain — exactly the relative structure an absolute- + return ranking hides. + + Beta is estimated from *past* data only (rolling ``window`` trading days of daily close + returns of each instrument vs the benchmark, both read up to and including ``t``), so + no lookahead enters the target. The benchmark leg uses the same horizon as the label + expression (``Ref($close,-6)/Ref($close,-1)-1`` by default via ``horizon``/``base``, + matching the yaml's 6-day label). Rows with missing beta or benchmark values keep + their raw label. + + Requires ``$close`` to be present in the feature group (it always is for TACHandler). + Stateless; accepts ``fit_start_time``/``fit_end_time`` (ignored) for uniform kwargs + injection. Must be inserted BEFORE any per-date normalization processor. + """ + + def __init__( + self, + benchmark="SPY", + fields_group="label", + lake_root=None, + market="US", + timeframe=None, + freq="day", + window=63, + horizon=6, + base=1, + feature_field="$close", + fit_start_time=None, + fit_end_time=None, + ): + self.benchmark = benchmark + self.fields_group = fields_group + self.lake_root = lake_root + self.market = market + self.timeframe = timeframe or timeframe_for_freq(freq) + self.window = int(window) + self.horizon = int(horizon) + self.base = int(base) + self.feature_field = feature_field + self.fit_start_time = fit_start_time + self.fit_end_time = fit_end_time + self._bench = None + + def _load_bench_close(self): + if self._bench is not None: + return self._bench + cfg = LakeConfig(self.lake_root, self.market) + p = cfg.bar_path(self.timeframe, self.benchmark) + if not p.exists(): + raise FileNotFoundError(f"BenchBetaResidual: benchmark bar file not found: {p}") + df = pd.read_parquet(p) + s = pd.Series(df["c"].astype(float).values, index=pd.to_datetime(df["t"])).sort_index() + s.index = s.index.normalize() + self._bench = s[~s.index.duplicated(keep="last")] + return self._bench + + def fit(self, df=None): + return self + + def __call__(self, df): + bench = self._load_bench_close() + + # benchmark forward return over the same horizon as the label expression + fwd = bench.shift(-(self.base + self.horizon - 1)) / bench.shift(-self.base) - 1 + + px_col = ("feature", self.feature_field) + if px_col not in df.columns: + raise KeyError(f"BenchBetaResidual: {self.feature_field} not found in features") + px = df[px_col].unstack("instrument").sort_index() + rets = px / px.shift(1) - 1 + bret = bench.reindex(px.index).pct_change() + + # rolling beta per instrument using data <= t (no lookahead) + cov = rets.rolling(self.window, min_periods=max(10, self.window // 2)).cov(bret) + var = bret.rolling(self.window, min_periods=max(10, self.window // 2)).var() + beta = cov.div(var, axis=0) + + contrib = beta.mul(fwd.reindex(px.index), axis=0) + cols = list(processor_module.get_group_columns(df, self.fields_group)) + out = df.copy() + for c in cols: + lab = df[c].unstack("instrument").reindex(px.index) + resid = lab - contrib.where(contrib.notna() & lab.notna(), 0.0) + new_vals = resid.stack() + new_vals.index.names = df.index.names + # residual where available, raw label otherwise (e.g. beta warm-up rows) + out[c] = new_vals.reindex(out.index).fillna(df[c]) + return out + + class TACHandler(DataHandlerLP): """DataHandlerLP backed by the TradeAC parquet lake. @@ -246,10 +417,12 @@ class TACHandler(DataHandlerLP): return get_common_feature_fields(lake_root, market, timeframe_for_freq(freq)) -__all__ = ["TACHandler", "DropAllNaN", "get_common_feature_fields"] +__all__ = ["TACHandler", "DropAllNaN", "BenchResidual", "BenchBetaResidual", "get_common_feature_fields"] -# Make `DropAllNaN` resolvable by bare name from processor configs (e.g. the default -# ``infer_processors`` and workflow yamls that reference it without a ``module_path``), -# mirroring how qlib registers its own processors in ``qlib.data.dataset.processor``. +# Make `DropAllNaN`/`BenchResidual`/`BenchBetaResidual` resolvable by bare name from processor +# configs (e.g. the default ``infer_processors`` and workflow yamls that reference them without a +# ``module_path``), mirroring how qlib registers its own processors in ``qlib.data.dataset.processor``. processor_module.DropAllNaN = DropAllNaN +processor_module.BenchResidual = BenchResidual +processor_module.BenchBetaResidual = BenchBetaResidual diff --git a/code/tac-qlib/tac_qlib/contrib/model/__pycache__/__init__.cpython-312.pyc b/code/tac-qlib/tac_qlib/contrib/model/__pycache__/__init__.cpython-312.pyc index f76257db439b030d84ecea2b8ae67079bae4dc6f..c975d2b978f2cc08a388a5d921938704a3dd592d 100644 GIT binary patch delta 20 acmdnbw4aIlG%qg~0}w>)>fOj~!UzC2^#u(8 delta 20 acmdnbw4aIlG%qg~0}zO;?b^s~!UzC0yac%b diff --git a/code/tac-qlib/tac_qlib/contrib/model/__pycache__/rank_ensemble.cpython-312.pyc b/code/tac-qlib/tac_qlib/contrib/model/__pycache__/rank_ensemble.cpython-312.pyc index 858f5fcbe285642569c214d5803edbae1e5dfa1e..009ebd83c5156ca3d7039a112e0d277dd416ca86 100644 GIT binary patch delta 20 acmezB`PGyAG%qg~0}w>)>fOlwN(BH*ga+UM delta 20 acmezB`PGyAG%qg~0}zO;?b^uwN(BH(O9qSp diff --git a/code/tac-qlib/tac_qlib/contrib/model/__pycache__/rank_gbdt.cpython-312.pyc b/code/tac-qlib/tac_qlib/contrib/model/__pycache__/rank_gbdt.cpython-312.pyc index 7de125f7f263c554b3337138c760d44394b4824a..1716b680b5623394229f7600ad4c81ad07fa6a2b 100644 GIT binary patch delta 20 acmZojXh`5b&CAQh00a@cdN*=^)dv7Qg9bVP delta 20 acmZojXh`5b&CAQh00bgyyEbxv)dv7ON(JTs diff --git a/code/tac-qlib/tac_qlib/contrib/strategy/__pycache__/__init__.cpython-312.pyc b/code/tac-qlib/tac_qlib/contrib/strategy/__pycache__/__init__.cpython-312.pyc index 6893c2097f0de110909f3f5076ac1c32ac5b742b..9c9f7743970b1a3827bb72768bb6e8be03040759 100644 GIT binary patch delta 20 acmZ3>w3dncG%qg~0}w>)>fOk##0UU2B?Q+1 delta 20 acmZ3>w3dncG%qg~0}zO;?b^t##0UT~>;!`V diff --git a/code/tac-qlib/tac_qlib/contrib/strategy/__pycache__/long_short.cpython-312.pyc b/code/tac-qlib/tac_qlib/contrib/strategy/__pycache__/long_short.cpython-312.pyc index 423ec5cd15b273c119ba3b1d7dc7467fc6ba41c4..6e38a7fa8584b80410ccc88e5feff228a7ece38b 100644 GIT binary patch delta 22 ccmcaHlkvt(M()$Ryj%=G5V5OwBllS!09M`yQvd(} delta 22 ccmcaHlkvt(M()$Ryj%=GAhNb=BllS!092(0{{R30 diff --git a/code/tac-qlib/tac_qlib/contrib/strategy/__pycache__/optimal_stop.cpython-312.pyc b/code/tac-qlib/tac_qlib/contrib/strategy/__pycache__/optimal_stop.cpython-312.pyc index b6b3238699337d559868cb5f8e0c3e900e5da0cf..f983d5c2472cd16ef9f14a240674ec0a7f41e81c 100644 GIT binary patch delta 20 acmdlJxF?YNG%qg~0}w>)>fOk_Q3C)&zy?VG delta 20 acmdlJxF?YNG%qg~0}zO;?b^t_Q3C)$hXwTj diff --git a/code/tac-qlib/tac_qlib/data/__pycache__/__init__.cpython-312.pyc b/code/tac-qlib/tac_qlib/data/__pycache__/__init__.cpython-312.pyc index fa1090eb6f17b1b7835038e35fac86334294d165..a0e969bd6504bb8d9220f4641cc01e960c3120e4 100644 GIT binary patch delta 20 acmeBT>0;qN&CAQh00a@cdN*?aX9NH-^#yVO delta 20 acmeBT>0;qN&CAQh00bgyyEbzFX9NH*yagTr diff --git a/code/tac-qlib/tac_qlib/data/__pycache__/config.cpython-312.pyc b/code/tac-qlib/tac_qlib/data/__pycache__/config.cpython-312.pyc index 48629a1f51bd4058036ce78d79ed49454982c051..2f8c537d11135155539276bee342d087aad8743e 100644 GIT binary patch delta 20 acmewy`8ksNG%qg~0}w>)>fOlwLI(g%d)>fOjKXaWF12nC}6 delta 20 acmbP}F(-rjG%qg~0}zO;?b^sKXaWE}&jn8a