remove the AI market-outlook feature (delivery-side only; books byte-identical)
Owner decision 2026-09-15. Gone: market_outlook.py, the post-H1-scan Telegram
outlook DM, main.py --outlook, the outlooks table DDL/migration/writer in
signal_log.py, the market_outlook config block + MARKET_OUTLOOK_ENABLED, and
test Module 26. Last commit containing the feature: 2836024.
- app_config: "market_outlook" stays in _KNOWN_TOP as a TOLERATED, ignored
legacy key (oanda.account_id precedent) with one warning, so a live
config.json that still carries the block keeps booting.
- signal_log: existing DBs keep their orphan outlooks table and rows (no
DROP TABLE); fresh DBs never create it.
- tests: Module 26's 4-thread concurrent legacy-DB migration test (the
suite's only one) moved to Module 31; new test for the tolerated block.
519 -> 509 tests, 34 -> 33 modules, 0 failed.
- docs: CLAUDE.md usage/config/file-map lines and a dated state note.
Verification: W2 books (2024-09-05 -> 2026-09-05, $0.65, NumPy CPU, 0 cache
fallbacks) byte-identical before/after — M5 29120a1ee32979431d062f210d41a9d1
(781 / +146.07R / PF 1.52), M15 d185f099482a07f29502cb5a371a9bbf
(931 / +146.14R / PF 1.43).
This commit is contained in:
+84
-405
@@ -3323,6 +3323,36 @@ def _():
|
||||
f"example config.json must be byte-identical to defaults on {tf}"
|
||||
|
||||
|
||||
@test("app_config: a legacy market_outlook block is tolerated and ignored")
|
||||
def _():
|
||||
# The market-outlook feature was removed 2026-09-15; config.json files
|
||||
# on the live box still carry its block and must keep loading (the
|
||||
# oanda.account_id precedent), with one warning so the owner deletes it.
|
||||
import logging
|
||||
records = []
|
||||
|
||||
class _Grab(logging.Handler):
|
||||
def emit(self, record):
|
||||
records.append(record)
|
||||
|
||||
log = logging.getLogger("app_config")
|
||||
handler = _Grab()
|
||||
log.addHandler(handler)
|
||||
path = _write_json({
|
||||
"market_outlook": {"enabled": True, "telegram": True,
|
||||
"max_per_day": 12, "candles_bars": 20},
|
||||
})
|
||||
try:
|
||||
app = load_app_config(path)
|
||||
finally:
|
||||
log.removeHandler(handler)
|
||||
os.remove(path)
|
||||
assert not hasattr(app, "market_outlook_settings")
|
||||
assert any(r.levelno == logging.WARNING
|
||||
and "market_outlook" in r.getMessage() for r in records), \
|
||||
"loading a legacy market_outlook block must log one warning"
|
||||
|
||||
|
||||
@test("providers: api key resolves env → config.json (no code default)")
|
||||
def _():
|
||||
from providers import TwelveDataProvider
|
||||
@@ -8528,409 +8558,6 @@ def _():
|
||||
os.remove(path + suffix)
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# MODULE 26: market_outlook.py (advisory hourly market brief)
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("\n═══ Module 26: AI market outlook ═══")
|
||||
|
||||
import market_outlook as mo
|
||||
|
||||
|
||||
class _OutlookStub:
|
||||
available = True
|
||||
|
||||
def __init__(self, reply=None):
|
||||
self.reply = reply or {
|
||||
"bias": "bullish", "setup_forming": "buy", "confidence": 68,
|
||||
"watch": "Look for the price to hold above its recent low.",
|
||||
"note": "The short-term chart is improving while the bigger trend remains supportive.",
|
||||
}
|
||||
self.calls = []
|
||||
|
||||
def chat_json(self, system_prompt, user_payload):
|
||||
self.calls.append((system_prompt, user_payload))
|
||||
if isinstance(self.reply, Exception):
|
||||
raise self.reply
|
||||
return self.reply
|
||||
|
||||
|
||||
class _OutlookNews:
|
||||
def __init__(self, config):
|
||||
pass
|
||||
|
||||
def get_fundamental_context(self):
|
||||
now = datetime.now(timezone.utc)
|
||||
return FundamentalContext(
|
||||
news_headlines=[NewsSentiment(
|
||||
headline="Gold firms as the dollar weakens", source="test",
|
||||
timestamp=now, currencies_mentioned=["XAU", "USD"],
|
||||
sentiment_score=0.3, keywords=[])],
|
||||
events_today=[EconomicEvent(
|
||||
time=now - timedelta(minutes=5), currency="USD", impact="high",
|
||||
event="Inflation", actual="0.2%", forecast="0.3%")],
|
||||
)
|
||||
|
||||
def get_pair_sentiment(self, symbol, context):
|
||||
return 0.25, "slightly bullish"
|
||||
|
||||
def get_week_events(self):
|
||||
return []
|
||||
|
||||
|
||||
_OUTLOOK_ON = {
|
||||
"enabled": True,
|
||||
"telegram": True,
|
||||
"max_per_day": 12,
|
||||
"candles_bars": 20,
|
||||
}
|
||||
|
||||
|
||||
@test("app_config: market_outlook defaults, env override, and validation")
|
||||
def _():
|
||||
defaults = load_app_config(_write_json({})).market_outlook_settings
|
||||
assert defaults == {
|
||||
"enabled": False, "telegram": False,
|
||||
"max_per_day": 12, "candles_bars": 20,
|
||||
}
|
||||
app = load_app_config(_write_json({"market_outlook": _OUTLOOK_ON}))
|
||||
assert app.market_outlook_settings == _OUTLOOK_ON
|
||||
old = os.environ.get("MARKET_OUTLOOK_ENABLED")
|
||||
try:
|
||||
os.environ["MARKET_OUTLOOK_ENABLED"] = "false"
|
||||
assert app_config.market_outlook_enabled(app) is False
|
||||
os.environ["MARKET_OUTLOOK_ENABLED"] = "true"
|
||||
assert app_config.market_outlook_enabled(app) is True
|
||||
finally:
|
||||
if old is None:
|
||||
os.environ.pop("MARKET_OUTLOOK_ENABLED", None)
|
||||
else:
|
||||
os.environ["MARKET_OUTLOOK_ENABLED"] = old
|
||||
for bad in (
|
||||
{"market_outlook": {"enabld": True}},
|
||||
{"market_outlook": {"enabled": "yes"}},
|
||||
{"market_outlook": {"telegram": 1}},
|
||||
{"market_outlook": {"max_per_day": 0}},
|
||||
{"market_outlook": {"max_per_day": True}},
|
||||
{"market_outlook": {"candles_bars": 2.5}},
|
||||
):
|
||||
try:
|
||||
load_app_config(_write_json(bad))
|
||||
assert False, f"should have rejected {bad}"
|
||||
except AppConfigError:
|
||||
pass
|
||||
|
||||
|
||||
@test("signal_log: market outlook table roundtrip and old-db migration")
|
||||
def _():
|
||||
import sqlite3
|
||||
import signal_log as slog
|
||||
fd, path = tempfile.mkstemp(suffix=".db")
|
||||
os.close(fd)
|
||||
try:
|
||||
con = sqlite3.connect(path)
|
||||
# Legacy pre-panel schema: it has a signals table but no outlooks
|
||||
# table. Keep the index columns from the original schema present.
|
||||
con.execute(
|
||||
"CREATE TABLE signals (id INTEGER PRIMARY KEY, logged_at TEXT,"
|
||||
" symbol TEXT, timeframe TEXT)")
|
||||
con.execute(
|
||||
"CREATE TABLE outlooks (id INTEGER PRIMARY KEY, logged_at TEXT,"
|
||||
" surface TEXT, bias TEXT, setup_forming TEXT, confidence REAL,"
|
||||
" watch TEXT, note TEXT)")
|
||||
con.commit()
|
||||
con.close()
|
||||
barrier = threading.Barrier(4)
|
||||
loggers = []
|
||||
errors = []
|
||||
|
||||
def open_logger():
|
||||
try:
|
||||
barrier.wait()
|
||||
loggers.append(slog.SignalLogger(path))
|
||||
except Exception as exc:
|
||||
errors.append(exc)
|
||||
|
||||
threads = [threading.Thread(target=open_logger) for _ in range(4)]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join(timeout=5)
|
||||
assert not errors, f"concurrent additive migration failed: {errors}"
|
||||
assert len(loggers) == 4
|
||||
logger = loggers[0]
|
||||
columns = {
|
||||
row[1] for row in logger._conn.execute("PRAGMA table_info(signals)")
|
||||
}
|
||||
assert {
|
||||
"indicators_json", "candles_json", "panel_created_at",
|
||||
"panel_cache_status", "panel_payload_hash",
|
||||
"advisory_outcome_status", "advisory_r_multiple",
|
||||
"advisory_outcome_json",
|
||||
} <= columns
|
||||
outlook_columns = {
|
||||
row[1] for row in logger._conn.execute("PRAGMA table_info(outlooks)")
|
||||
}
|
||||
assert {"news_json", "indicators_json", "candles_json"} <= \
|
||||
outlook_columns
|
||||
result = mo.OutlookResult(
|
||||
bias="bearish", setup_forming="sell", confidence=71.5,
|
||||
watch="Wait for price to fail at resistance.",
|
||||
note="Momentum is fading after a sharp rise.", created_ts=time.time())
|
||||
payload = {
|
||||
"news": {"headlines": ["Gold firms"]},
|
||||
"indicators": {"1h": {"rsi": {"value": 55.0}}},
|
||||
"market_regime": {"1h": "trending_up"},
|
||||
"volatility_regime": {"1h": "normal"},
|
||||
"key_levels": {"1h": {"support": 4400.0}},
|
||||
"candles": {"1h": {"bars": [[-0.2, 0.1, -0.3, 0.0]]}},
|
||||
}
|
||||
rowid = logger.log_outlook(result, surface="test", payload=payload)
|
||||
for open_instance in loggers:
|
||||
open_instance.close()
|
||||
con = sqlite3.connect(path)
|
||||
row = con.execute(
|
||||
"SELECT surface, bias, setup_forming, confidence, watch, note, "
|
||||
"news_json, indicators_json, candles_json "
|
||||
"FROM outlooks WHERE id=?", (rowid,)).fetchone()
|
||||
con.close()
|
||||
assert row[:6] == ("test", "bearish", "sell", 71.5,
|
||||
"Wait for price to fail at resistance.",
|
||||
"Momentum is fading after a sharp rise.")
|
||||
assert json.loads(row[6])["headlines"] == ["Gold firms"]
|
||||
assert json.loads(row[7])["market_regime"]["1h"] == "trending_up"
|
||||
assert len(json.loads(row[8])["1h"]["bars"]) == 1
|
||||
finally:
|
||||
slog.reset_loggers_for_tests()
|
||||
os.remove(path)
|
||||
|
||||
|
||||
@test("market_outlook: payload is finite, normalized, and news-safe")
|
||||
def _():
|
||||
try:
|
||||
mo._dp = _FakeDP()
|
||||
with patch("news_engine.NewsEngine", _OutlookNews):
|
||||
payload = mo._collect_payload(_OUTLOOK_ON)
|
||||
assert mo._normalize_candles is ap._normalize_candles
|
||||
assert set(payload["candles"]) == {"15min", "1h", "4h"}
|
||||
assert all(len(block["bars"]) == 20
|
||||
for block in payload["candles"].values())
|
||||
assert payload["indicators"]["1h"]
|
||||
assert payload["news"]["headlines"] == ["Gold firms as the dollar weakens"]
|
||||
json.dumps(payload, sort_keys=True, allow_nan=False)
|
||||
|
||||
with patch("news_engine.NewsEngine", side_effect=RuntimeError("down")):
|
||||
no_news = mo._collect_payload(_OUTLOOK_ON)["news"]
|
||||
assert no_news == mo._empty_news_payload()
|
||||
finally:
|
||||
mo._dp = None
|
||||
|
||||
|
||||
@test("market_outlook: reply validation clamps and rejects malformed JSON")
|
||||
def _():
|
||||
validate = mo._validate_outlook
|
||||
assert validate(None) is None
|
||||
assert validate({"bias": "up", "setup_forming": "buy", "confidence": 50}) is None
|
||||
assert validate({"bias": "bullish", "setup_forming": "wait", "confidence": 50}) is None
|
||||
assert validate({"bias": "bullish", "setup_forming": "buy"}) is None
|
||||
assert validate({"bias": "bullish", "setup_forming": "buy", "confidence": "hot"}) is None
|
||||
result = validate({
|
||||
"bias": "neutral", "setup_forming": "none", "confidence": 150,
|
||||
"watch": "word " * 30, "note": "note " * 30,
|
||||
})
|
||||
assert result.confidence == 100.0
|
||||
assert len(result.watch.split()) == 25 and len(result.note.split()) == 25
|
||||
|
||||
|
||||
@test("market_outlook: disabled default spends no LLM request")
|
||||
def _():
|
||||
original_app = app_config.get_app_config()
|
||||
old = os.environ.pop("MARKET_OUTLOOK_ENABLED", None)
|
||||
try:
|
||||
app_config.set_app_config(AppConfig())
|
||||
stub = _OutlookStub()
|
||||
lc._client = stub
|
||||
mo.reset_outlook_state()
|
||||
assert mo.maybe_run_outlook("telegram") is None
|
||||
assert stub.calls == []
|
||||
finally:
|
||||
if old is not None:
|
||||
os.environ["MARKET_OUTLOOK_ENABLED"] = old
|
||||
lc._client = None
|
||||
mo.reset_outlook_state()
|
||||
app_config.set_app_config(original_app)
|
||||
|
||||
|
||||
@test("market_outlook: hourly dedup prevents a second request")
|
||||
def _():
|
||||
original_app = app_config.get_app_config()
|
||||
old = os.environ.pop("MARKET_OUTLOOK_ENABLED", None)
|
||||
try:
|
||||
app_config.set_app_config(AppConfig(market_outlook_settings=dict(_OUTLOOK_ON)))
|
||||
stub = _OutlookStub()
|
||||
lc._client = stub
|
||||
mo.reset_outlook_state()
|
||||
with patch("providers.base._is_forex_weekend", return_value=False), \
|
||||
patch.object(mo, "_collect_payload", return_value={}), \
|
||||
patch("signal_log.log_outlook"):
|
||||
assert mo.maybe_run_outlook("telegram") is not None
|
||||
assert mo.maybe_run_outlook("telegram") is None
|
||||
assert len(stub.calls) == 1
|
||||
finally:
|
||||
if old is not None:
|
||||
os.environ["MARKET_OUTLOOK_ENABLED"] = old
|
||||
lc._client = None
|
||||
mo.reset_outlook_state()
|
||||
app_config.set_app_config(original_app)
|
||||
|
||||
|
||||
@test("market_outlook: daily budget blocks force calls after the cap")
|
||||
def _():
|
||||
original_app = app_config.get_app_config()
|
||||
try:
|
||||
app_config.set_app_config(AppConfig(market_outlook_settings={
|
||||
**_OUTLOOK_ON, "max_per_day": 1}))
|
||||
stub = _OutlookStub()
|
||||
lc._client = stub
|
||||
mo.reset_outlook_state()
|
||||
with patch("providers.base._is_forex_weekend", return_value=False), \
|
||||
patch.object(mo, "_collect_payload", return_value={}), \
|
||||
patch("signal_log.log_outlook"):
|
||||
assert mo.maybe_run_outlook("cli", force=True) is not None
|
||||
assert mo.maybe_run_outlook("cli", force=True) is None
|
||||
assert len(stub.calls) == 1
|
||||
finally:
|
||||
lc._client = None
|
||||
mo.reset_outlook_state()
|
||||
app_config.set_app_config(original_app)
|
||||
|
||||
|
||||
@test("market_outlook: LLM and persistence failures remain fail-safe")
|
||||
def _():
|
||||
original_app = app_config.get_app_config()
|
||||
try:
|
||||
app_config.set_app_config(AppConfig(market_outlook_settings=dict(_OUTLOOK_ON)))
|
||||
lc._client = _OutlookStub(RuntimeError("LLM down"))
|
||||
mo.reset_outlook_state()
|
||||
with patch("providers.base._is_forex_weekend", return_value=False), \
|
||||
patch.object(mo, "_collect_payload", return_value={}):
|
||||
assert mo.maybe_run_outlook("telegram") is None
|
||||
|
||||
lc._client = _OutlookStub()
|
||||
mo.reset_outlook_state()
|
||||
with patch("providers.base._is_forex_weekend", return_value=False), \
|
||||
patch.object(mo, "_collect_payload", return_value={}), \
|
||||
patch("signal_log.log_outlook", side_effect=RuntimeError("db locked")):
|
||||
assert mo.maybe_run_outlook("telegram") is not None
|
||||
finally:
|
||||
lc._client = None
|
||||
mo.reset_outlook_state()
|
||||
app_config.set_app_config(original_app)
|
||||
|
||||
|
||||
@test("market_outlook: weekend gate spends nothing")
|
||||
def _():
|
||||
original_app = app_config.get_app_config()
|
||||
try:
|
||||
app_config.set_app_config(AppConfig(market_outlook_settings=dict(_OUTLOOK_ON)))
|
||||
stub = _OutlookStub()
|
||||
lc._client = stub
|
||||
mo.reset_outlook_state()
|
||||
with patch("providers.base._is_forex_weekend", return_value=True):
|
||||
assert mo.maybe_run_outlook("telegram") is None
|
||||
assert stub.calls == []
|
||||
finally:
|
||||
lc._client = None
|
||||
mo.reset_outlook_state()
|
||||
app_config.set_app_config(original_app)
|
||||
|
||||
|
||||
@test("market_outlook: hour-bucket dedup runs consecutive hours 3,590 s apart")
|
||||
def _():
|
||||
original_app = app_config.get_app_config()
|
||||
old = os.environ.pop("MARKET_OUTLOOK_ENABLED", None)
|
||||
try:
|
||||
app_config.set_app_config(AppConfig(market_outlook_settings=dict(_OUTLOOK_ON)))
|
||||
stub = _OutlookStub()
|
||||
lc._client = stub
|
||||
mo.reset_outlook_state()
|
||||
base = 1_800_000_000 - (1_800_000_000 % 3600) # an exact hour
|
||||
clock = {"t": base + 25.0} # 10:00:25
|
||||
with patch("providers.base._is_forex_weekend", return_value=False), \
|
||||
patch.object(mo, "_collect_payload", return_value={}), \
|
||||
patch("signal_log.log_outlook"), \
|
||||
patch.object(mo.time, "time", lambda: clock["t"]):
|
||||
assert mo.maybe_run_outlook("telegram") is not None
|
||||
clock["t"] = base + 1800.0 # 10:30 same bucket
|
||||
assert mo.maybe_run_outlook("telegram") is None
|
||||
clock["t"] = base + 3600.0 + 15.0 # 11:00:15 — 3,590 s later
|
||||
assert mo.maybe_run_outlook("telegram") is not None, \
|
||||
"the old `now - last < 3600` rule dropped every other hour"
|
||||
assert len(stub.calls) == 2
|
||||
finally:
|
||||
if old is not None:
|
||||
os.environ["MARKET_OUTLOOK_ENABLED"] = old
|
||||
lc._client = None
|
||||
mo.reset_outlook_state()
|
||||
app_config.set_app_config(original_app)
|
||||
|
||||
|
||||
@test("market_outlook: every silent early return leaves a heartbeat log line")
|
||||
def _():
|
||||
original_app = app_config.get_app_config()
|
||||
old = os.environ.pop("MARKET_OUTLOOK_ENABLED", None)
|
||||
records = []
|
||||
|
||||
class _Grab(logging.Handler):
|
||||
def emit(self, record):
|
||||
records.append(record.getMessage())
|
||||
|
||||
handler = _Grab()
|
||||
mo.logger.addHandler(handler)
|
||||
prev_level = mo.logger.level
|
||||
mo.logger.setLevel(logging.INFO)
|
||||
try:
|
||||
app_config.set_app_config(AppConfig(market_outlook_settings={
|
||||
**_OUTLOOK_ON, "enabled": False}))
|
||||
mo.reset_outlook_state()
|
||||
assert mo.maybe_run_outlook("telegram") is None
|
||||
assert any("skipped (disabled)" in m for m in records), records
|
||||
records.clear()
|
||||
app_config.set_app_config(AppConfig(market_outlook_settings=dict(_OUTLOOK_ON)))
|
||||
with patch("providers.base._is_forex_weekend", return_value=True):
|
||||
assert mo.maybe_run_outlook("telegram") is None
|
||||
assert any("skipped (weekend)" in m for m in records), records
|
||||
records.clear()
|
||||
# LLM returns nothing → warning, nothing persisted
|
||||
stub = _OutlookStub(reply=None)
|
||||
stub.reply = None
|
||||
lc._client = stub
|
||||
persisted = []
|
||||
with patch("providers.base._is_forex_weekend", return_value=False), \
|
||||
patch.object(mo, "_collect_payload", return_value={}), \
|
||||
patch("signal_log.log_outlook", persisted.append):
|
||||
assert mo.maybe_run_outlook("telegram") is None
|
||||
assert any("no outlook produced" in m for m in records), records
|
||||
assert persisted == []
|
||||
finally:
|
||||
mo.logger.removeHandler(handler)
|
||||
mo.logger.setLevel(prev_level)
|
||||
if old is not None:
|
||||
os.environ["MARKET_OUTLOOK_ENABLED"] = old
|
||||
lc._client = None
|
||||
mo.reset_outlook_state()
|
||||
app_config.set_app_config(original_app)
|
||||
|
||||
|
||||
@test("market_outlook isolation: signal path never imports the advisory module")
|
||||
def _():
|
||||
root = os.path.dirname(__file__)
|
||||
for name in ("engine.py", "signal_aggregator.py"):
|
||||
with open(os.path.join(root, name), encoding="utf-8") as fh:
|
||||
assert "market_outlook" not in fh.read(), name
|
||||
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# MODULE 27: signal-log observability (2026-09 audit)
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
@@ -10536,6 +10163,59 @@ def _():
|
||||
assert weighted_agg.gate_config_fingerprint() == m5.gate_config_fingerprint()
|
||||
|
||||
|
||||
@test("signal_log: concurrent legacy-DB migration adds the panel/advisory columns")
|
||||
def _():
|
||||
# Four loggers open the same pre-panel database at once: the additive
|
||||
# ALTER TABLE migration must survive the race (_with_lock_retry / WAL).
|
||||
# Moved from the removed market-outlook module (2026-09-15); it was the
|
||||
# suite's only concurrent-migration regression.
|
||||
import sqlite3
|
||||
import signal_log as slog
|
||||
fd, path = tempfile.mkstemp(suffix=".db")
|
||||
os.close(fd)
|
||||
try:
|
||||
con = sqlite3.connect(path)
|
||||
# Legacy pre-panel schema. Keep the index columns from the original
|
||||
# schema present.
|
||||
con.execute(
|
||||
"CREATE TABLE signals (id INTEGER PRIMARY KEY, logged_at TEXT,"
|
||||
" symbol TEXT, timeframe TEXT)")
|
||||
con.commit()
|
||||
con.close()
|
||||
barrier = threading.Barrier(4)
|
||||
loggers = []
|
||||
errors = []
|
||||
|
||||
def open_logger():
|
||||
try:
|
||||
barrier.wait()
|
||||
loggers.append(slog.SignalLogger(path))
|
||||
except Exception as exc:
|
||||
errors.append(exc)
|
||||
|
||||
threads = [threading.Thread(target=open_logger) for _ in range(4)]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join(timeout=5)
|
||||
assert not errors, f"concurrent additive migration failed: {errors}"
|
||||
assert len(loggers) == 4
|
||||
columns = {
|
||||
row[1] for row in loggers[0]._conn.execute("PRAGMA table_info(signals)")
|
||||
}
|
||||
assert {
|
||||
"indicators_json", "candles_json", "panel_created_at",
|
||||
"panel_cache_status", "panel_payload_hash",
|
||||
"advisory_outcome_status", "advisory_r_multiple",
|
||||
"advisory_outcome_json",
|
||||
} <= columns
|
||||
for open_instance in loggers:
|
||||
open_instance.close()
|
||||
finally:
|
||||
slog.reset_loggers_for_tests()
|
||||
os.remove(path)
|
||||
|
||||
|
||||
@test("signal_log: migration adds the research columns, scan_outcomes and indexes")
|
||||
def _():
|
||||
import sqlite3
|
||||
@@ -11906,8 +11586,7 @@ def _():
|
||||
# the backtester.
|
||||
banned = {"signal_log", "news_engine", "providers", "telegram_bot",
|
||||
"news_archive", "agent_panel", "violent_watch",
|
||||
"market_outlook", "gpu_backtest", "sklearn", "scipy",
|
||||
"lightgbm", "torch"}
|
||||
"gpu_backtest", "sklearn", "scipy", "lightgbm", "torch"}
|
||||
path = Path("backtest/replay_record.py")
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), str(path))
|
||||
seen, offenders = set(), []
|
||||
|
||||
Reference in New Issue
Block a user