Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion docs/qc/quantconnect.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
> REFERENCE — vivante — dernière révision 2026-10-30
> REFERENCE — vivante — dernière révision 2026-11-04

# QuantConnect (QC) - Configuration et règles

Expand Down Expand Up @@ -261,6 +261,32 @@ Pattern standardise pour combiner plusieurs strategies dans un même algo QC :
- Supporte branches weight-hint ET equal-weight
- Utilise `set_rebalancing_func` pour le rebalance timing

### Source unique du PCM par poches — la garde

Le modèle par poches a longtemps existé en plusieurs copies, en plusieurs variantes, et le correctif `additive` (#19740) n'était présent que dans une partie d'entre elles : un correctif posé sur une copie ne se propageait pas, et rien ne le signalait. L'extraction (#21134) a fait de `SharedPCM/pouches_pcm.py` la source canonique, importée par les composites.

**Deux copies subsistent, et chacune a une raison qui ne se corrige pas par un copier-coller** :

| Copie | Raison | Ce qu'elle porte |
|-------|--------|-----------------|
| `partner-course-quant-trading/kit-transitoire/03-Framework-Composite/main.py` | kit pédagogique **autonome** : le code est lu et modifié par l'apprenant dans un seul fichier, sans bibliothèque QC attachée — le canonique ne peut pas y être importé | le correctif `additive` et les diagnostics XLU de #20188 |
| `projects/TrendWeatherPointInTime/portfolio_construction.py` | candidate **gelée** du registre de l'ombre (entrée `tw-pit`, code rejoué au SHA gelé) : #20188 interdit que l'extraction la touche | la variante antérieure au correctif `additive` — une seule insight par symbole, pas de reconstruction par `(symbole, source_model)` |

La garde ne pretend donc pas supprimer ces copies : elle refuse qu'une **nouvelle** copie apparaisse sans déclaration, et qu'une copie **déclarée** soit modifiée sans re-déclaration.

```bash
python scripts/quantconnect/check_pcm_single_source.py # verdict humain
python scripts/quantconnect/check_pcm_single_source.py --json # verdict structure
```

**Le manifeste** `scripts/quantconnect/pcm_single_source.json` porte la source canonique, les copies déclarées, l'empreinte de chacune (`sha256` du texte normalisé, donc insensible aux fins de ligne) et la raison de chaque déclaration. Les empreintes sont ce qui rend la garde discriminante : un fichier déclaré qui change sans re-déclaration est signalé (`DECLARATION_STALE`), pas absorbé.

**Déclarer une copie** (procédure) : ajouter l'entrée au manifeste avec son chemin, ses classes, son empreinte et sa raison, puis relancer l'organe — il doit rendre `VERDICT: OK`. Une copie nouvelle qui n'a pas de raison n'est pas une copie : elle s'importe du canonique.

**Câblage CI** : la garde est un garde **natif de la voie rapide** (`TRANCHE38` du registre `scripts/ci/fast_lane_registry.py`), **bloquant** : son check-run porte son nom canonique, donc un verdict rouge arrête la PR. Elle se déclenche sur toute modification d'un `.py` sous `MyIA.AI.Notebooks/QuantConnect/`, sur le manifeste et sur l'organe lui-même (une révision qui change la portée de la garde doit la rejouer).

**Contrôle positif** : le corps de tests (`scripts/quantconnect/tests/test_check_pcm_single_source.py`) crée un arbre temporaire où une copie déclarée est volontairement modifiée et exige un verdict rouge (`DECLARATION_STALE`) ; le cas nominal, l'arbre du dépôt, doit rester vert. Une garde qui ne rougit pas sur une copie modifiée n'est pas une garde.

### AlphaModel — règles

- DOIT avoir `self.name = "StrategyName"` dans `__init__`
Expand Down
4 changes: 2 additions & 2 deletions scripts/ci/fast_lane.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
TRANCHE14, TRANCHE15, TRANCHE16, TRANCHE17, TRANCHE18, TRANCHE19,
TRANCHE20, TRANCHE21, TRANCHE22, TRANCHE23, TRANCHE24, TRANCHE25, TRANCHE26,
TRANCHE27, TRANCHE28, TRANCHE29, TRANCHE30, TRANCHE31, TRANCHE32,
TRANCHE33, TRANCHE34, TRANCHE35, TRANCHE36, TRANCHE37, Guard,
TRANCHE33, TRANCHE34, TRANCHE35, TRANCHE36, TRANCHE37, TRANCHE38, Guard,
)

SHADOW_PREFIX = "fast-lane (ombre): "
Expand Down Expand Up @@ -636,7 +636,7 @@ def main(argv: list[str] | None = None) -> int:
+ TRANCHE20 + TRANCHE21 + TRANCHE22 + TRANCHE23 + TRANCHE24 + TRANCHE25
+ TRANCHE26 + TRANCHE27 + TRANCHE28 + TRANCHE29 + TRANCHE30
+ TRANCHE31 + TRANCHE32 + TRANCHE33 + TRANCHE34 + TRANCHE35
+ TRANCHE36 + TRANCHE37
+ TRANCHE36 + TRANCHE37 + TRANCHE38
if not args.only or g.name == args.only]
if args.effects:
guards = [g for g in guards if g.commenters]
Expand Down
67 changes: 67 additions & 0 deletions scripts/ci/fast_lane_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3505,3 +3505,70 @@ def _body_md_hierarchy(rc: int, payload: str, ctx: dict) -> str:
warn_rc=(2,), # 2 = base injoignable : UNKNOWN, pas un rouge
),
]


# ---------------------------------------------------------------------------
# Garde de source unique du PCM par poches (#20188, critere 2).
#
# Le modele de construction de portefeuille par poches a existe en neuf copies
# sous `MyIA.AI.Notebooks/QuantConnect/`, en six variantes, et le correctif
# `additive` (#19740) n'etait present que dans trois d'entre elles : un
# correctif pose sur une copie ne se propageait pas, et rien ne le signalait.
#
# L'extraction a ete livree (#21134) : `projects/SharedPCM/pouches_pcm.py` est
# la source canonique, importee par les composites. Deux copies subsistent, et
# chacune a une raison qui ne se corrige pas par un copier-coller : le kit
# pedagogique `03-Framework-Composite` est autonome (le code est copie par
# l'apprenant, sans bibliotheque QC attachee), et `TrendWeatherPointInTime`
# est une candidate gelee du registre de l'ombre (entree `tw-pit`, code rejoue
# au SHA gele) que #20188 interdit de toucher.
#
# La garde ne pretend donc pas supprimer ces copies : elle refuse qu'une
# **nouvelle** copie apparaisse sans declaration, et qu'une copie **declaree**
# soit modifiee sans re-declaration. Les declarations vivent dans
# `scripts/quantconnect/pcm_single_source.json` (le manifeste), qui porte la
# source canonique, les copies declarees et la raison de chacune.
#
# Pourquoi la voie rapide et pas un workflow dedie : un workflow
# `pull_request` auto-heberge doit etre enregistre dans l'allowlist de
# `check_self_hosted_runner_policy.py`, et le cliquet refuse `ubuntu-latest`
# sur une PR. La voie rapide emet le check-run sous son nom canonique
# (`absorbed=True`), donc avec une conclusion REELLE, sans payer un second
# clone du depot.
#
# Le manifeste figure dans `paths` : une revision qui change les declarations
# doit rejouer la garde, sinon une declaration perimee passerait sans temoin
# (lecon #11685/#12817). Idem pour le corps de tests, qui porte le controle
# positif exige par #20188 : une copie volontairement modifiee fait rougir.
# Les deux fichiers de l'organe lui-meme figurent aussi dans `paths` (le
# registre et le moteur) : une revision qui etend ou retrecit la portee de la
# garde doit la rejouer — les gardes natifs voisins font de meme.
#
# rc=2 = manifeste illisible ou organe absent : c'est un **defaut du depot**,
# pas un incident d'entree comme le base injoignable d'un garde de delta. La
# garde le declare donc en rouge : un manifeste casse est une garde debranchee.
# ---------------------------------------------------------------------------
TRANCHE38: list[Guard] = [
Guard(
name="pcm-single-source-guard",
source=FAST_LANE_NATIVE,
paths=[
# Toute definition nouvelle de la famille du PCM par poches est
# declencheuse : le motif `/**/` couvre ici la racine comme les
# sous-repertoires (semantique GitHub, cf path_matches).
"MyIA.AI.Notebooks/QuantConnect/**/*.py",
"scripts/quantconnect/check_pcm_single_source.py",
"scripts/quantconnect/pcm_single_source.json",
"scripts/quantconnect/tests/test_check_pcm_single_source.py",
# L'organe lui-meme : une revision qui change sa portee doit la
# rejouer (les 21 gardes natifs voisins declarent la meme paire).
"scripts/ci/fast_lane.py",
"scripts/ci/fast_lane_registry.py",
],
argv=[
"python", "scripts/quantconnect/check_pcm_single_source.py",
],
blocking=True,
absorbed=True,
),
]
Loading
Loading