Skip to content

feat(books): modèle des livres cœur/poche/pétrole et STRATEGY_MODE (phase 2, PR 2-a) - #106

Open
laurentvv wants to merge 1 commit into
mainfrom
feat/livres-strategy-mode
Open

laurentvv wants to merge 1 commit into
mainfrom
feat/livres-strategy-mode

Conversation

@laurentvv

Copy link
Copy Markdown
Owner

Socle du mode cœur + poche active (plan §2.6, PR 2-a) : livres avec rôle et poids cibles (cœur 90 %, poche + pétrole ≤ 10 %), quantités par livre et par instrument (le cœur et la poche partagent la position Nasdaq-100 côté broker), pic d'equity et drawdown, réconciliation broker (signale, ne corrige jamais), interrupteur STRATEGY_MODE (legacy par défaut, valeur inconnue = erreur). Module pur : aucun câblage, le démo n'est pas touché. 15 tests.

🤖 Generated with Claude Code

… réconciliation (PR 2-a)

Module pur, n'active rien : le mode legacy reste le défaut.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment thread src/portfolio_books.py
fd, tmp = tempfile.mkstemp(dir=p.parent if str(p.parent) else ".", suffix=".tmp")
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.write(payload)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: save_state() n'ecrit pas les donnees sur le disque avant os.replace()

os.replace() est atomique pour l'entree de repertoire, mais ne force pas l'ecriture du contenu : si le processus (ou la machine) s'arrete juste apres cet appel, portfolio_books_state.json peut etre laisse tronque ou vide. Ce fichier est le seul enregistrement de la repartition core/sleeve des quantites, que le broker ne peut pas fournir (une seule position pour les deux livres) : l'etat est alors perdu definitivement.

Le depot resout deja exactement ce probleme dans _atomic_json_write (src/t212_executor.py:32-33) avec tmp_file.flush() puis os.fsync(tmp_file.fileno()) avant le rename. Reutiliser ce motif ici.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread src/portfolio_books.py
p = Path(path)
if not p.exists():
return BooksState()
data = json.loads(p.read_text(encoding="utf-8"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: load_state() laisse remonter une JSONDecodeError brute sur un fichier tronque

Le fichier existant mais corrompu (ecriture interrompue, disque plein, edition manuelle) leve une JSONDecodeError non traitee qui remonte jusqu'a l'appelant et arrete le cycle. Le motif retenu dans le depot pour ce fichier de litteral est _read_with_retry (src/t212_executor.py:45-61), qui tolere le JSON invalide.

Attention however : ici le fallback ne doit pas etre un BooksState() vide — un etat vide se lirait comme « aucune position core/sleeve » alors que le broker en detient une (invariant « Failed fetch != empty »). attraper l'erreur et la re-lever avec un message explicite (ou renvoyer un etat None/inconnu que l'appelant doit traiter comme tel) est plus sûr qu'un état vide silencieux.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0

Both findings are in the persistence layer of the new src/portfolio_books.py. The strategy model itself (mode switch, invariants, per-book/per-instrument quantities, broker reconciliation) is sound and the 15 tests cover it well. The concern is durability: portfolio_books_state.json is the only record of the core/sleeve split of a shared broker position, and neither the write nor the read path is hardened the way the equivalent t212_portfolio_state.json helpers already are (_atomic_json_write / _read_with_retry in src/t212_executor.py:21-61).

Fix these issues in Kilo Cloud

Issue Details (click to expand)

WARNING

File Line Issue
src/portfolio_books.py 155 save_state() calls os.replace() without flush() + os.fsync(); a crash right after can leave a truncated/empty state file, losing the core/sleeve split irrecoverably
src/portfolio_books.py 141 load_state() lets a raw JSONDecodeError escape on a corrupt state file; needs explicit handling that surfaces an unknown state rather than a silent empty BooksState() (phantom "no position")
Files Reviewed (2 files)
  • src/portfolio_books.py - 2 issues
  • tests/test_portfolio_books.py - 0 issues

Reviewed by free · Input: 0 · Output: 0 · Cached: 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant