Self-improving trading intelligence — automated retrospectives, adaptive strategy weights, score calibration, and the migration path from manual to fully automated.
Without feedback, your system is static — rules that worked last month may fail this month as market conditions evolve. The feedback loop creates a virtuous cycle:
The target: from ~40% hit rate (random stock picking) to 65%+ hit rate after 8-12 weeks of feedback loop operation. Here's how each component drives improvement.
Every Friday at 23:00, the retrospective task runs automatically. It evaluates all setups from the past 10 days and produces a comprehensive performance report.
async function runRetrospective() {
// 1. Read all scans from past 10 days
const scans = readRecentScans(10);
// Each scan has 10 setups with entry, stop, TP1, TP2, strategy
// 2. Collect current prices for all setups
const allTickers = [...new Set(scans.flatMap(s => s.setups.map(t => t.ticker)))];
const currentData = await QueryData({
symbols: allTickers,
types: "quote,bars_daily"
});
// 3. Calculate outcome for each setup
const results = scans.flatMap(scan => scan.setups.map(setup => {
const bars = currentData[setup.ticker].bars_daily;
const entryBar = bars.find(b => b.date >= scan.date);
if (!entryBar) return null;
const postEntryBars = bars.filter(b => b.date > scan.date);
const hitTP1 = postEntryBars.some(b => b.high >= setup.tp1);
const hitTP2 = postEntryBars.some(b => b.high >= setup.tp2);
const hitStop = postEntryBars.some(b => b.low <= setup.stop);
// Determine order: which hit first?
const tp1Day = postEntryBars.findIndex(b => b.high >= setup.tp1);
const stopDay = postEntryBars.findIndex(b => b.low <= setup.stop);
let outcome;
if (hitTP1 && (!hitStop || tp1Day < stopDay)) outcome = "TP1_HIT";
else if (hitTP2 && (!hitStop || tp1Day < stopDay)) outcome = "TP2_HIT";
else if (hitStop) outcome = "STOP_HIT";
else outcome = "OPEN"; // Still within timeframe
const currentPrice = currentData[setup.ticker].quote.price;
const pnl = ((currentPrice - setup.entry) / setup.entry * 100).toFixed(1);
return {
...setup,
scanDate: scan.date,
outcome,
currentPrice,
pnl: parseFloat(pnl),
hitTP1, hitTP2, hitStop,
daysHeld: postEntryBars.length
};
})).filter(Boolean);
// 4. Calculate aggregate metrics
const metrics = {
totalSetups: results.length,
hitRateTP1: results.filter(r => r.outcome === "TP1_HIT").length / results.length,
hitRateTP2: results.filter(r => r.outcome === "TP2_HIT").length / results.length,
stopRate: results.filter(r => r.outcome === "STOP_HIT").length / results.length,
avgPnL: results.reduce((sum, r) => sum + r.pnl, 0) / results.length,
bestPick: results.sort((a, b) => b.pnl - a.pnl)[0],
worstPick: results.sort((a, b) => a.pnl - b.pnl)[0],
byStrategy: groupByStrategy(results),
byRegime: groupByRegime(results)
};
// 5. Compute grade
const grade = computeGrade(metrics);
// A+: HR > 60%, avg P&L > 3%
// A: HR > 55%, avg P&L > 2%
// B+: HR > 50%, avg P&L > 0%
// B: HR > 45%
// C+: HR > 40%
// Below: D or F
// 6. Generate HTML retrospective article
await generateRetroArticle(results, metrics, grade);
// 7. Update strategy weights based on results
await adjustStrategyWeights(metrics.byStrategy);
return { metrics, grade };
}
| KPI | Formula | Target |
|---|---|---|
| Hit Rate (TP1) | TP1 hits / total setups | ≥ 55% |
| Win/Loss Ratio | Avg win / Avg loss | ≥ 1.5 |
| Average P&L | Mean % change across all setups | ≥ 2.0% |
| Max Drawdown | Worst setup P&L | ≥ -8% |
| Consecutive Losses | Longest losing streak | ≤ 4 |
| Score Correlation | r(composite_score, pnl) | ≥ 0.3 |
After each retrospective, strategy weights are updated using a Bayesian-inspired approach. Successful strategies gain weight; underperformers lose weight.
function adjustStrategyWeights(byStrategy, currentWeights) {
const FLOOR = 0.05; // No strategy below 5%
const CEILING = 0.40; // No strategy above 40%
const LEARNING_RATE = 0.15; // How fast we adapt
const newWeights = { ...currentWeights };
for (const [strategy, results] of Object.entries(byStrategy)) {
const hitRate = results.filter(r => r.outcome.includes("TP")).length / results.length;
const avgPnL = results.reduce((s, r) => s + r.pnl, 0) / results.length;
// Performance score: combines hit rate and P&L
const perfScore = hitRate * 0.6 + Math.min(avgPnL / 10, 0.4) * 0.4;
// Bayesian update: blend prior weight with observed performance
const prior = currentWeights[strategy] || 0.15;
const posterior = prior * (1 - LEARNING_RATE) + perfScore * LEARNING_RATE;
newWeights[strategy] = Math.max(FLOOR, Math.min(CEILING, posterior));
}
// Normalize to sum to 1.0
const total = Object.values(newWeights).reduce((s, w) => s + w, 0);
for (const key of Object.keys(newWeights)) {
newWeights[key] = Math.round((newWeights[key] / total) * 100) / 100;
}
return newWeights;
}
// Example evolution over 4 weeks:
// Week 0 (default):
// { oversold_bounce: 0.25, momentum: 0.25, squeeze: 0.15,
// trend: 0.15, safe_haven: 0.10, value: 0.10 }
// Week 2 (oversold outperforming in risk-off):
// { oversold_bounce: 0.32, momentum: 0.18, squeeze: 0.14,
// trend: 0.12, safe_haven: 0.15, value: 0.09 }
// Week 4 (market transitions to neutral):
// { oversold_bounce: 0.26, momentum: 0.24, squeeze: 0.16,
// trend: 0.14, safe_haven: 0.10, value: 0.10 }
Is the scoring system actually predictive? Calibration analysis answers: do setups with score 90 perform better than those with score 80?
function calibrationAnalysis(allResults) {
// Group results by score bucket
const buckets = {};
for (const r of allResults) {
const bucket = Math.floor(r.compositeScore / 5) * 5; // 75, 80, 85, 90, 95
if (!buckets[bucket]) buckets[bucket] = [];
buckets[bucket].push(r);
}
// For each bucket, compute actual hit rate
const calibration = {};
for (const [score, results] of Object.entries(buckets)) {
calibration[score] = {
count: results.length,
hitRate: results.filter(r => r.outcome.includes("TP")).length / results.length,
avgPnL: results.reduce((s, r) => s + r.pnl, 0) / results.length
};
}
// Expected pattern:
// Score 75-79: hit rate ~40%, avg P&L ~0.5%
// Score 80-84: hit rate ~50%, avg P&L ~1.5%
// Score 85-89: hit rate ~57%, avg P&L ~2.5%
// Score 90-94: hit rate ~65%, avg P&L ~4.0%
// Score 95-100: hit rate ~72%, avg P&L ~5.5%
// If score 90 has LOWER hit rate than 80 → layer weights are wrong
// → re-calibrate by analyzing which layers predict success
return calibration;
}
function featureImportance(allResults) {
// Which layer correlates most with positive outcomes?
const layers = ['technical', 'fundamental', 'social', 'sector', 'macro', 'ai'];
return layers.map(layer => {
const scores = allResults.map(r => r.layers[layer]);
const outcomes = allResults.map(r => r.pnl);
const correlation = pearsonCorrelation(scores, outcomes);
return { layer, correlation, importance: Math.abs(correlation) };
}).sort((a, b) => b.importance - a.importance);
// Example output:
// { layer: "technical", correlation: 0.42, importance: 0.42 } ← Most predictive
// { layer: "macro", correlation: 0.35, importance: 0.35 }
// { layer: "sector", correlation: 0.28, importance: 0.28 }
// { layer: "fundamental", correlation: 0.22, importance: 0.22 }
// { layer: "ai", correlation: 0.18, importance: 0.18 }
// { layer: "social", correlation: 0.05, importance: 0.05 } ← Least predictive
// Action: if social has near-zero correlation,
// reduce its weight from 10% to 5% and redistribute.
}
With only 40-60 setups per month, calibration data is noisy. Wait for at least 100 data points (6-8 weeks) before making significant weight changes. Small samples + frequent changes = overfitting to noise.
The system remembers what worked and what didn't, building a knowledge base over time.
// Stored in scanner/pattern_memory.json
{
"blacklist": {
"SMCI": {
"reason": "3 consecutive stop hits, accounting concerns",
"since": "2026-02-15",
"expires": "2026-03-15",
"failCount": 3
},
"PLTR": {
"reason": "2 false breakouts, extreme overvaluation",
"since": "2026-02-20",
"expires": "2026-03-20",
"failCount": 2
}
},
"goldlist": {
"CF": {
"reason": "2 TP2 hits in 3 weeks, strong insider buying",
"since": "2026-02-28",
"winCount": 2,
"avgPnL": 8.5
}
},
"sectorPatterns": {
"Biotech": {
"falseSignalRate": 0.45,
"note": "High false signal rate — apply 10% score penalty",
"adjustment": -10
},
"Energy": {
"falseSignalRate": 0.22,
"note": "Below average false signals in risk-off — boost",
"adjustment": +5
}
},
"dayOfWeekPatterns": {
"Monday": { "hitRate": 0.52, "note": "Slightly above average" },
"Tuesday": { "hitRate": 0.58, "note": "Best day — gap fills" },
"Wednesday": { "hitRate": 0.45, "note": "Below average — FOMC?" },
"Thursday": { "hitRate": 0.48, "note": "Average" },
"Friday": { "hitRate": 0.42, "note": "Worst — weekend risk" }
},
"calendarEffects": {
"opex_week": { "hitRate": 0.38, "note": "Gamma effects reduce accuracy" },
"earnings_season": { "hitRate": 0.55, "note": "Slightly better — more catalysts" },
"fomc_week": { "hitRate": 0.40, "note": "Uncertainty reduces hit rate" }
}
}
During the scanner run, pattern memory modifies candidate scoring:
function applyPatternMemory(candidate, memory) {
let adjustment = 0;
// Blacklisted tickers: automatic reject
if (memory.blacklist[candidate.ticker]) {
return { ...candidate, rejected: true, reason: "blacklisted" };
}
// Goldlist tickers: bonus
if (memory.goldlist[candidate.ticker]) {
adjustment += 5;
}
// Sector adjustment
const sectorAdj = memory.sectorPatterns[candidate.sector]?.adjustment || 0;
adjustment += sectorAdj;
// Day of week
const dayAdj = (memory.dayOfWeekPatterns[today].hitRate - 0.50) * 20;
adjustment += dayAdj;
// Calendar effects
if (isOpexWeek() && memory.calendarEffects.opex_week) {
adjustment -= 3;
}
candidate.compositeScore += adjustment;
candidate.adjustments = { adjustment, reasons: [...] };
return candidate;
}
Don't go from 0 to 100% automated overnight. Follow this gradual migration path:
Run the automated scanner alongside your manual trading. Compare: do the A++ picks align with your own analysis? Track both results in a spreadsheet. Build trust.
Start taking the top 3 A++ trades. Small position sizes (1-2% of portfolio). Continue manual trading as well. Compare win rates side by side.
Half automated, half manual. First retrospective data starts informing weight adjustments. Pattern memory building. DSL v2.0 deployed based on initial feedback.
Automated setups only. Manual intervention only for system issues. Position sizing rules embedded in alerts. Monthly manual review of overall system performance.
You've built a complete, self-improving, zero-touch trading system. Here's what it gives you:
The edge: Speed of analysis (60 stocks scored in seconds), breadth of data (58 types per stock), emotional discipline (the AI doesn't panic-sell), and continuous improvement (every week the system gets smarter).
What AI can't do: Predict black swans, guarantee profits, or replace risk management. The system finds high-probability setups — but high-probability is not certainty. Always use stops. Always manage position sizes. The best trading system is one you trust enough to follow consistently.