Repository navigation
Conversation
…alier incrémental (phase 1) - src/pullback/universe.py : T212 -> symbole Yahoo (shortName, BRK.B -> BRK-B), filtre de liquidité (cours >= 5 $, 20 M$/jour, 252 séances) sans regard vers le futur. - src/pullback/store.py : Parquet par symbole, seules les barres manquantes sont téléchargées, séries périmées (> 3 jours ouvrés) refusées, aucune donnée comblée. - src/pullback/cli.py : universe / update / stats. - 10 tests ; suite complète 442 passed, 3 skipped. N'affecte ni t212_executor.py ni schedule.py. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Screener momentum (+30 %, rang >= 75), détection de consolidation (creux ascendants, ATR5/ATR20, EMA 9/21), niveaux de support (EMA, AVWAP ancré, anciens plus hauts, gaps non comblés), jour de setup (déclencheur = plus haut, stop = plus bas, distance <= 3 %), taille (0,5 % de risque, plafonds 25 % / 100 % / 3 %), cibles 3R/5R et sortie sous EMA 9. Aucune lecture après `asof` (testé). Corrige aussi cli.py : le symbole « NAN » était lu comme NaN par pandas et plantait le téléchargement. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
| close = b["Close"] | ||
| e9, e21, e50 = ema(close, 9), ema(close, 21), ema(close, 50) | ||
| window = b.iloc[-p.consolidation_max_bars :] | ||
| anchor = len(b) - len(window) + int(np.argmax(window["High"].to_numpy())) |
There was a problem hiding this comment.
WARNING: Anchor calculation uses highest high within consolidation window
The anchor for AVWAP should be the peak before consolidation starts. np.argmax(window["High"]) finds the highest high anywhere in the last 30 bars (the consolidation window), which could be at the end of the window if there's upward drift during consolidation. This misplaces the AVWAP anchor.
Consider finding the peak before the consolidation window, or using the first bar of the window as the anchor (since the window is defined as the consolidation period).
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| if bars_since < p.consolidation_min_bars: | ||
| reasons.append("plus haut trop récent (pas de consolidation)") | ||
| lows = [i for i in pivot_lows(b) if i > anchor] | ||
| if len(lows) < 2 or not b["Low"].iloc[lows[-1]] > b["Low"].iloc[lows[-2]]: |
There was a problem hiding this comment.
WARNING: Only checks last two pivot lows for ascending pattern
The comment says "creux ascendants" (ascending lows, plural) but the code only verifies the last two pivot lows: b["Low"].iloc[lows[-1]] > b["Low"].iloc[lows[-2]]. If there are 3+ pivot lows where early ones descend but the last two ascend, this passes incorrectly.
Should verify all consecutive pivot lows after anchor are ascending: all(b["Low"].iloc[lows[i]] > b["Low"].iloc[lows[i-1]] for i in range(1, len(lows))).
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| from dotenv import load_dotenv | ||
|
|
||
| load_dotenv(".env.t212") | ||
| from src.t212_executor import _get_t212_base_url, get_auth_header |
There was a problem hiding this comment.
WARNING: Imports private functions from t212_executor
from src.t212_executor import _get_t212_base_url, get_auth_header imports _get_t212_base_url (prefixed with _ indicating private). This creates tight coupling to internal implementation details. If t212_executor refactors these, the CLI breaks.
Consider exposing a public helper in t212_executor or duplicating the minimal logic needed here.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
|
|
||
| def is_stale(last_bar: pd.Timestamp, asof: pd.Timestamp, max_days: int = MAX_STALE_BUSINESS_DAYS) -> bool: | ||
| """Périmé si plus de `max_days` jours ouvrés séparent la dernière barre de `asof`.""" | ||
| return int(np.busday_count(last_bar.date(), asof.date())) > max_days |
There was a problem hiding this comment.
WARNING: is_stale ignores market holidays
np.busday_count only excludes weekends, not market holidays (e.g., Thanksgiving, Christmas). Around holidays, this can incorrectly mark fresh data as stale (or vice versa) by 1-2 days.
The project's AGENTS.md notes this pattern elsewhere (GO-gate 5: "stale-at-source price data is refused"). Consider using a market calendar (e.g., pandas_market_calendars) or maintaining a holiday list.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| for s in chunk: | ||
| _merge(s, got.get(s), "new") | ||
| if todo_inc: | ||
| start = (min(todo_inc.values()) + pd.Timedelta(days=1)).strftime("%Y-%m-%d") |
There was a problem hiding this comment.
SUGGESTION: Start date uses calendar days, not business days
start = (min(todo_inc.values()) + pd.Timedelta(days=1)).strftime("%Y-%m-%d") adds 1 calendar day. If the last bar was Friday, this requests from Saturday. yfinance handles this gracefully but it's imprecise. Use pd.offsets.BDay(1) for 1 business day.
| start = (min(todo_inc.values()) + pd.Timedelta(days=1)).strftime("%Y-%m-%d") | |
| start = (min(todo_inc.values()) + pd.offsets.BDay(1)).strftime("%Y-%m-%d") |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| rows = [] | ||
| for ins in instruments: | ||
| ticker = ins.get("ticker", "") | ||
| if ins.get("type") != "STOCK" or not ticker.endswith(US_SUFFIX) or ins.get("currencyCode") != "USD": |
There was a problem hiding this comment.
WARNING: Case-sensitive comparisons for API fields
ins.get("type") != "STOCK" and ins.get("currencyCode") != "USD" assume exact casing. The T212 API could return "stock", "Stock", "usd", etc. Use case-insensitive comparison: ins.get("type", "").upper() != "STOCK" and ins.get("currencyCode", "").upper() != "USD".
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
|
|
||
|
|
||
| def cmd_update(limit: int | None) -> int: | ||
| symbols = list(pd.read_csv(UNIVERSE_CSV, keep_default_na=False)["symbol"])[:limit] |
There was a problem hiding this comment.
WARNING: No error handling for missing universe CSV
pd.read_csv(UNIVERSE_CSV, keep_default_na=False) raises FileNotFoundError if the file doesn't exist (e.g., user runs update before universe). Add a try/except or check UNIVERSE_CSV.exists() first.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
|
|
||
|
|
||
| def cmd_stats() -> int: | ||
| symbols = list(pd.read_csv(UNIVERSE_CSV, keep_default_na=False)["symbol"]) |
There was a problem hiding this comment.
WARNING: No error handling for missing universe CSV
Same issue as line 45: pd.read_csv(UNIVERSE_CSV, keep_default_na=False) will raise FileNotFoundError if universe command hasn't been run first.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
|
|
||
| kwargs = {"start": start} if start else {"period": period or INITIAL_PERIOD} | ||
| raw = yf.download( | ||
| symbols, interval="1d", auto_adjust=True, group_by="ticker", progress=False, threads=True, timeout=30, **kwargs |
There was a problem hiding this comment.
SUGGESTION: 30s timeout may be too short for 100 symbols
yf.download(..., timeout=30, ...) with BATCH=100 and threads=True may time out on slow networks or when Yahoo rate-limits. Consider increasing to 60-120s or making it configurable.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
|
|
||
|
|
||
| def main(argv: list[str] | None = None) -> int: | ||
| logging.basicConfig(level=logging.WARNING) |
There was a problem hiding this comment.
SUGGESTION: logging.basicConfig called on every main() invocation
If main() is called multiple times (e.g., in tests), this adds duplicate handlers. Guard with if not logging.getLogger().handlers: or configure logging once at module level.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
Code Review SummaryStatus: 10 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (9 files)
Fix these issues in Kilo Cloud Reviewed by free · Input: 202.4K · Output: 10.1K · Cached: 267.8K |
Résumé
Phases 1 et 2 du plan
docs/PLAN_MIGRATION_PULLBACK_2026-10-12.md, empilées sur #110 (base =docs/plan-migration-pullback, à rebaser surmainaprès le merge de #110).Phase 1 : univers et données
src/pullback/universe.py: 6 877 symboles Yahoo issus des_US_EQen USD de T212 (symbole =shortName), filtre de liquidité sans regard vers le futur.src/pullback/store.py: Parquet par symbole dansdata_cache/pullback/(gitignoré), barres manquantes seulement, séries périmées refusées, aucune donnée comblée.src/pullback/cli.py:universe,update,stats.Phase 2 : règles en fonctions pures (
src/pullback/rules.py)Suite complète : 451 passed, 3 skipped. Ne touche ni
src/t212_executor.pynischedule.py(contrat, critère 28).À noter
🤖 Generated with Claude Code