By the end of the competition, the repository contained more strategy code than the account had ever used. That was not an inconsistency. It was the distinction I needed the research process to preserve.
A strategy could exist in four different senses:
- the idea had been described;
- the implementation existed;
- the configuration enabled it;
- a running process had loaded that configuration.
Those are separate claims. The first version of the workflow made them too easy to blur together. A class registered under quantsilico/signals/ could look finished even when it had never passed a validation gate or entered the live configuration.
Part 1 covered the common engine beneath the strategies. This article is about the decisions above it: why momentum and Kalman launched, why OU entered later, why the network family failed the longer test, and why several other candidates remained research-only or were disabled for operational reasons.
The validation metrics below come from named project outputs. They are backtest and decision records, not live competition returns. Their panel identity stays attached to them throughout the article.
Promotion required evidence, not code
The promotion path I was trying to enforce looked like this:
- 01HypothesisState the idea, expected behaviour and instruments required.
- 02ImplementationRegister the candidate behind the shared Alpha interface without giving it access to execution.
- 03Named-panel comparisonCompare it with the locked baseline on an identified dataset rather than whichever panel is easiest to load.
- 04Costs and sensitivityCheck whether the result survives the recorded cost model and reasonable parameter changes.
- 05Operational reviewDecide whether the inputs, positions and failure modes can be monitored and constrained during the competition.
- 06Configuration decisionRecord the enable flag, weight, symbols and commit that represent the promotion decision.
- 07Runtime evidenceUse the loaded configuration hash or equivalent state record to distinguish promotion in Git from confirmed activation in a running cycle.
The repository did not complete every stage equally for every candidate. I do not want to hide that by pretending the process was more formal than it was.
Momentum and Kalman have clear launch evidence. The network family has a clear long-panel rejection. BTC has a clear research-only boundary. Other candidates have implementation or configuration records without a complete research packet.
That unevenness is exactly why the status names matter.
Three records for one candidate
I eventually needed three different records for each candidate:
- Implementation state — whether the strategy existed and could be constructed.
- Configuration state — whether it was enabled, at what weight and for which symbols.
- Runtime state — whether a running process loaded that configuration and exposed it through telemetry.
A candidate could therefore be implemented without being enabled, enabled without a recovered first-cycle runtime hash, or operationally disabled after previously being permitted.
- Momentum
- live_at_launch
- Kalman gold/silver
- live_at_launch
- OU EUR-cross mean reversion
- promoted_later — configuration changed 23 Jun 2026
- Network / launch / armoured network
- rejected_by_validation — long panel
- Aggression profiles
- rejected_by_validation — sweep stage
- Churn modifications
- rejected_by_validation — base bundle retained
- BTC scalper
- researched_not_promoted
- Forecasting / TSFM / graph overlays
- gated_pending_evidence or planned_only
- Alternative crypto
- operationally_disabled during the finals window
rejected_by_validation does not mean the underlying idea can never work. It means the tested implementation did not earn promotion under the data, assumptions and deadline available at the time.
researched_not_promoted is different. It means the candidate did not reach a decision point where live eligibility was justified.
operationally_disabled is different again. That status reflects a control decision about live exposure rather than a general conclusion about the research hypothesis.
Launch configuration and later promotion
The launch snapshot records two enabled sleeves:
- momentum at weight 0.60;
- Kalman gold/silver mean reversion at weight 0.40.
That is the strongest evidence for the launch decision. Both strategies appear in the locked baseline and the launch snapshot with the same weights.
Their inclusion does not give me a complete historical research packet for each sleeve. I cannot recover every intermediate test, rejected parameter or contemporaneous note that led to 0.60 and 0.40.
What I can say is narrower and more useful: those were the two strategies represented in the documented launch configuration, and the rest of the engine was built to treat their output through the same portfolio, risk and execution path.
Momentum was the larger contribution. Kalman provided a relative-value sleeve around the gold/silver relationship. Neither strategy could submit an order directly; each supplied target weights to the common portfolio layer described in Part 1.
The launch decision was therefore not “these are the two best strategies in general.” It was “these are the two candidates I was willing to put into the first competition configuration.”
Adding OU after launch
OU mean reversion was not part of the documented launch blend.
Git history shows ou_score changing to enabled: true on 23 June 2026 in commit b813b9d. The same change reweighted momentum and Kalman from 0.60/0.40 to 0.55/0.25 and assigned OU a weight of 0.20. The relevant EUR-cross routing was also enabled in that configuration state.
- Momentum
- 0.60 → 0.55
- Kalman gold/silver
- 0.40 → 0.25
- OU mean reversion
- disabled / absent from launch blend → enabled at 0.20
- Configuration evidence
- Commit b813b9d
- Runtime evidence
- Exact first OU-enabled live-cycle hash not recovered
A stale YAML comment still describes OU as gated off. The executable value and Git diff outrank that comment.
This is also where configuration evidence and runtime evidence separate.
I can date the configuration change. I cannot recover the first heartbeat that proves the exact cycle which loaded it. I therefore describe OU as promoted in configuration on 23 June, not as definitely placing its first live trade at a specific timestamp.
That distinction may sound narrow, but it prevents Git history from being used as a substitute for runtime state.
Candidates that failed or remained gated
The network family received a much clearer rejection record.
The archived long-panel comparison records the locked baseline against three related configurations:
| config | panel | return | reported Sharpe | max DD | gate |
|---|---|---|---|---|---|
locked_baseline |
long | +14.50% | 0.377 | 24.38% | BASELINE |
network |
long | −16.46% | −0.112 | 45.41% | FAIL |
launch |
long | −16.46% | −0.112 | 45.41% | FAIL |
network_armored |
long | −16.46% | −0.112 | 45.41% | FAIL |
The decision report records FALLBACK_BASELINE.
I preserve the identical rows for network, launch and network_armored because that is what the archived output contains. I do not infer three independent performance paths merely because the configurations have different names.
The decision was straightforward: none of those tested variants earned promotion over the locked baseline on the long panel.
That does not establish that graph or network methods are useless. It establishes that these implementations, under this comparison, failed badly enough that I kept them out of the competition configuration.
Why panel identity mattered
The short and long comparisons were not interchangeable.
They represented different datasets and potentially different periods, coverage and assumptions. A return or Sharpe number without its panel identity is incomplete evidence.
This became one of the easiest ways to fool myself. A candidate could look acceptable on the smaller or friendlier dataset and then collapse when I moved to the longer panel. If I copied only the headline metric into a summary table, the difference in evidence quality disappeared.
The long-panel records therefore stay labelled panel = long. I do not average them with the shorter comparison, choose the better-looking value or present them as repeated estimates of the same experiment.
The validator also needed to fail loudly when a named dataset was unavailable. A missing long-history input should be an error, not permission to substitute whichever panel happens to exist locally.
The point was not to make every panel identical. It was to make the identity of the experiment impossible to lose.
Testing more aggressive sizing
The aggression sweep tested whether increasing the risk profile improved the competition setup enough to justify promotion.
The recorded conclusion retained baseline_hold. The tested aggressive profiles did not pass the chosen gate at that stage.
That decision can look inconsistent beside the later configuration, which allowed much more exposure. It is not the same question.
The sweep asked whether those specific candidate profiles passed the validation rules when tested. The later configuration history records operational decisions made as the competition developed and the remaining window shortened.
A later increase in permitted exposure does not retroactively turn the failed sweep into a pass. Equally, the failed sweep does not prove that every possible increase in exposure was wrong.
The evidence supports a timeline:
- I launched with a conservative baseline.
- The tested aggression profiles failed their recorded gate.
- I retained
baseline_holdat that stage. - Later configuration changes nevertheless raised risk limits during the competition.
Part 3 deals with the consequences of that timeline. Here, the important point is that the validation verdict belongs to the tested profiles and date that produced it.
Churn changes that stayed out
The churn work reached a similar decision by a different route.
The recorded conclusion retained base_bundle. The documented modifications did not pass the relevant gates, and some mechanisms required paper-live or live observations that were not available in the research output.
This was not a dramatic failure like the long-panel network result. It was a failure to produce enough evidence for promotion.
That difference matters.
A candidate can remain out because it loses money in the test. It can also remain out because its claimed benefit cannot be separated from turnover, execution timing or a short sample.
For the churn changes, the honest status is that the base bundle remained the supported choice. I do not turn the absence of a passing result into a stronger claim that every churn reduction mechanism was bad.
The BTC scalper stayed offline
The BTC scalp work never entered the live alpha blend.
The repository contains M1- and tick-oriented research, mocks and a history-fetch path. The fetch script keeps real MT5 retrieval behind an explicit --live-fetch flag. Without that flag, the script reports that no real MT5 fetch was executed.
The operational boundary was therefore visible in the tool itself:
# scripts/fetch_mt5_history.py — guarded behaviour
--live-fetch
Explicitly permit a real MT5 history fetch.
Default behaviour:
No real MT5 fetch is executed when --live-fetch is absent.
This was useful because a research script capable of contacting the same platform as the live system can otherwise blur another boundary: analysing historical data versus interacting with the execution environment.
The candidate status is researched_not_promoted.
There is no evidence that the BTC scalper entered the configured ensemble, received a live weight or affected the competition account. It should not be described as a live strategy that later failed.
Forecasting and graph work remained gated
The forecasting and graph-oriented work also remained outside the active competition blend.
The launch and archived configurations keep tsfm_forecast and xasset_graph_momentum disabled at weight 0.0.
Those modules show where the research was heading. They do not show competition-time live capability.
This is another reason I separate implementation from promotion. A repository can contain a model, tests and configuration fields while the actual decision remains “not enabled.”
The correct status is either gated_pending_evidence or planned_only, depending on the specific candidate record. Neither status should be rewritten as a live deployment merely because the code exists.
Operational controls during the live window
Alternative-crypto exposure produced a different kind of decision.
The record is not primarily a backtest rejection. It is a sequence of operational control changes during the live window.
Git history shows quarantine-related work around 22 June, progressive symbol disables and a 25 June change that disabled ETH, SOL, XRP and BAR while changing the handling of existing disabled-symbol positions. A later change adjusted that behavior after manual flattening.
That sequence supports the status operationally_disabled.
It does not provide enough public evidence to reconstruct a precise incident P&L, broker rejection or position timeline. I am therefore not filling the gap with a dramatic story.
What the repository proves is more limited:
- those symbols became subject to explicit controls;
- the handling of existing exposure changed;
- the decision occurred during the competition window;
- the change was operational rather than a general declaration that alternative-crypto strategies could never work.
The runtime event details belong in Part 3 if a suitably redacted record can support them.
What the promotion process still lacked
The promotion process became clearer during the competition, but it was not equally complete from the beginning.
The first weakness was provenance. I had outputs, configs and Git commits, but not every candidate had one compact record tying together its dataset, parameters, costs, result, decision and effective runtime state.
The second weakness was the gap between configuration and activation. A commit can prove when I enabled OU. Without the first matching runtime hash, it cannot prove the exact cycle that loaded the new configuration.
The third was panel discipline. A short-panel result and a long-panel result could look like two measurements of the same thing when they were actually different experiments. The panel name, source hash and generation command needed to travel with the metric.
The fourth was stale documentation. A comment that says “gated” while the executable field says enabled: true is worse than no comment because it gives the reader two incompatible answers.
The fifth was timing. I was researching, promoting candidates and operating the account inside the same fixed window. A technically valid candidate could still be a poor promotion decision if there was not enough time left to observe it safely.
None of those problems is solved by generating more strategies. They require better decision records and stricter boundaries between research, configuration and runtime evidence.
Rules I would retain
If I rebuilt the same workflow, I would keep the following rules:
- Name the panel
- Every metric carries the dataset or panel that produced it.
- Pin the baseline
- A candidate is compared with a fixed reference rather than the latest convenient configuration.
- Fail on missing data
- A missing named panel is an error, not permission to fall back silently.
- Separate status types
- Research-only, gated, rejected, promoted and operationally disabled mean different things.
- Trust executable state over comments
- Enable flags and committed values outrank stale prose.
- Record the effective commit
- Promotion requires a dated configuration state.
- Capture runtime identity
- A loaded configuration hash distinguishes a Git decision from confirmed activation.
- Review operations before promotion
- A candidate needs monitorable inputs, positions and failure modes, not only a return series.
The candidate generator was never the scarce part of the process. The scarce part was evidence strong enough to justify changing the account.
That is the aspect of the competition work that carried into QuantSilico: not the claim that every rejected candidate should stay rejected forever, but the requirement that a candidate earn a traceable path from research output to deployment state.
Part 3 starts where those decisions reached the running account: execution modes, live telemetry, risk changes and the difference between the last recorded telemetry point and the final project settlement.
Reproducibility
- Repository: PriestlyAdejo/quantsilico-model-to-market-competition
- Project page: /projects/model-to-market
- Series hub: /writing/series/model-to-market
- Part 1: /writing/model-to-market-system-design
- Part 3: /writing/model-to-market-live-execution-postmortem