Trading Edge - Stratégie & Algorithmes
Maximiser l'avantage statistique sur les marchés météo.
Objectif
Ce skill documente la logique métier du calcul d'edge, les formules de confiance, et les stratégies de trading optimales pour les marchés de prédiction météo Polymarket.
Fondamentaux du Trading Météo
Le Principe de l'Edge
code
EDGE = Probabilité Réelle - Prix du Marché
Exemple :
- •Open-Meteo prédit 35.2°F avec 89% de confiance
- •Le bracket "34-35°F" est pricé à 41¢ (= 41% de probabilité implicite)
- •Edge = 89% - 41% = +48%
Un edge positif signifie que le marché sous-évalue cette issue.
Pourquoi l'Edge Existe
- •Asymétrie d'information : Les traders retail n'ont pas accès aux meilleurs modèles météo
- •Biais psychologiques : Préférence pour les outcomes "round numbers"
- •Liquidité limitée : Peu d'arbitrageurs sur ce marché niche
- •Timing : Les modèles météo se mettent à jour plus vite que le marché
Calcul de la Confiance Météo
Confiance par Horizon Temporel
La précision météo décroît avec le temps. Voici les coefficients empiriques :
javascript
const BASE_CONFIDENCE = {
0: 0.95, // Aujourd'hui (J+0)
1: 0.90, // Demain (J+1)
2: 0.82, // J+2
3: 0.74, // J+3
4: 0.65, // J+4
5: 0.55, // J+5
6: 0.45, // J+6
7: 0.35, // J+7+
};
Confiance Multi-Modèle (Recommandé)
Quand plusieurs modèles météo sont disponibles, utiliser leurs statistiques :
javascript
function calculateMultiModelConfidence(models, bracket, daysAhead) {
// 1. Calculer la distribution des prévisions
const predictions = models.map(m => m.tempMax);
const mean = average(predictions);
const median = percentile(predictions, 50);
const stdDev = standardDeviation(predictions);
// 2. Calculer la probabilité que la température tombe dans le bracket
// Utiliser la CDF normale
const zMin = (bracket.min - mean) / Math.max(stdDev, 0.5);
const zMax = (bracket.max - mean) / Math.max(stdDev, 0.5);
const probability = normalCDF(zMax) - normalCDF(zMin);
// 3. Ajuster par l'accord entre modèles
const cv = stdDev / Math.abs(mean); // Coefficient de variation
const agreement = Math.max(0, Math.min(1, 1 - cv));
// 4. Facteur d'accord (0.7 à 1.0)
const agreementFactor = 0.7 + (agreement * 0.3);
// 5. Pénalité temporelle
const daysPenalty = Math.max(0, (daysAhead - 1) * 0.015);
// 6. Confiance finale
let confidence = probability * agreementFactor * (1 - daysPenalty);
// 7. Plafond basé sur le nombre de modèles
const modelCap = Math.min(1, 0.6 + (models.length * 0.05));
return Math.min(confidence, modelCap);
}
Pénalité de Bord de Bracket
Si la température prédite est proche du bord d'un bracket, réduire la confiance :
javascript
function applyEdgePenalty(confidence, predictedTemp, bracket) {
const bracketWidth = bracket.max - bracket.min;
const center = (bracket.max + bracket.min) / 2;
// Distance relative au centre (0 = centre, 1 = bord)
const distanceFromCenter = Math.abs(predictedTemp - center) / (bracketWidth / 2);
// Pénalité max 15% si exactement au bord
const penalty = Math.min(distanceFromCenter * 0.15, 0.15);
return confidence * (1 - penalty);
}
Seuils de Signal
Configuration Recommandée
javascript
const SIGNAL_THRESHOLDS = {
STRONG_BUY: 0.20, // Edge > 20%
BUY: 0.10, // Edge > 10%
WATCH: 0.05, // Edge > 5%
PASS: 0 // Edge < 5%
};
const SIGNALS = {
STRONG_BUY: {
label: "ACHAT FORT",
emoji: "🔥",
action: "Entrée agressive",
sizing: "2-3% du bankroll"
},
BUY: {
label: "ACHAT",
emoji: "✅",
action: "Entrée standard",
sizing: "1-2% du bankroll"
},
WATCH: {
label: "SURVEILLER",
emoji: "⚠️",
action: "Attendre confirmation",
sizing: "Pas d'action immédiate"
},
PASS: {
label: "PASSER",
emoji: "➖",
action: "Pas d'edge",
sizing: "N/A"
}
};
Signaux Additionnels
javascript
const ADVANCED_SIGNALS = {
VALUE_BET: {
condition: (edge, agreement) => edge > 0.25 && agreement > 0.9,
label: "VALUE BET",
description: "Tous les modèles d'accord, marché en désaccord majeur"
},
FADING: {
condition: (edge, prevEdge) => edge < prevEdge - 0.05,
label: "EDGE EN BAISSE",
description: "Le marché corrige vers les prévisions"
},
CONTRARIAN: {
condition: (edge, volumeTrend) => edge > 0.15 && volumeTrend < 0,
label: "CONTRARIAN",
description: "Edge élevé malgré volume en baisse"
}
};
Stratégie de Sizing (Kelly Criterion)
Kelly Fraction
javascript
function kellyFraction(edge, marketPrice) {
// f* = (p*b - q) / b
// où p = notre probabilité, b = odds, q = 1-p
const impliedOdds = (1 / marketPrice) - 1; // Si price=0.4, odds=1.5
const ourProbability = marketPrice + edge; // Notre estimation
const lossProbability = 1 - ourProbability;
const kelly = (ourProbability * impliedOdds - lossProbability) / impliedOdds;
// Ne jamais dépasser 25% Kelly (prudence)
return Math.max(0, Math.min(kelly * 0.25, 0.10));
}
Position Sizing Recommandé
| Signal | Kelly | Sizing Max | Note |
|---|---|---|---|
| STRONG BUY | Full (25%) | 3% bankroll | Volume > $5k |
| BUY | Half (12.5%) | 2% bankroll | Volume > $2k |
| WATCH | Quarter (6%) | 1% bankroll | Confirmation requise |
| PASS | 0 | 0 | Pas d'action |
Parsing des Brackets Polymarket
Patterns Supportés
javascript
function parseBracket(bracket) {
const b = bracket.trim();
// Pattern 1: "34-35°F" ou "34-35°C"
const rangeMatch = b.match(/(-?\d+(?:\.\d+)?)\s*[-–]\s*(-?\d+(?:\.\d+)?)/);
if (rangeMatch) {
return {
min: parseFloat(rangeMatch[1]),
max: parseFloat(rangeMatch[2]),
type: 'range'
};
}
// Pattern 2: "38°F or more" / "38+ F" / "38°F and above"
const orMoreMatch = b.match(/(-?\d+(?:\.\d+)?)\s*(?:°[FC])?\s*(?:or more|\+|and above|or higher)/i);
if (orMoreMatch) {
const base = parseFloat(orMoreMatch[1]);
return {
min: base,
max: base + 15, // Extension arbitraire
type: 'open_high'
};
}
// Pattern 3: "30°F or less" / "under 30" / "30°F and below"
const orLessMatch = b.match(/(-?\d+(?:\.\d+)?)\s*(?:°[FC])?\s*(?:or less|or lower|and below|or under)/i);
if (orLessMatch) {
const base = parseFloat(orLessMatch[1]);
return {
min: base - 15,
max: base,
type: 'open_low'
};
}
// Pattern 4: "under 30" / "below 30"
const underMatch = b.match(/(?:under|below)\s*(-?\d+(?:\.\d+)?)/i);
if (underMatch) {
const base = parseFloat(underMatch[1]);
return { min: base - 15, max: base, type: 'open_low' };
}
// Pattern 5: "over 38" / "above 38"
const overMatch = b.match(/(?:over|above)\s*(-?\d+(?:\.\d+)?)/i);
if (overMatch) {
const base = parseFloat(overMatch[1]);
return { min: base, max: base + 15, type: 'open_high' };
}
// Pattern 6: Valeur unique "35°F" (bracket exact)
const singleMatch = b.match(/^(-?\d+(?:\.\d+)?)\s*°[FC]?$/);
if (singleMatch) {
const val = parseFloat(singleMatch[1]);
return { min: val - 0.5, max: val + 0.5, type: 'exact' };
}
return null;
}
Correspondance Prévision → Bracket
javascript
function findMatchingBracket(outcomes, predictedTemp) {
// 1. Chercher correspondance exacte
for (let i = 0; i < outcomes.length; i++) {
const bracket = parseBracket(outcomes[i]);
if (!bracket) continue;
if (predictedTemp >= bracket.min && predictedTemp <= bracket.max) {
return {
index: i,
bracket: outcomes[i],
...bracket,
mid: (bracket.min + bracket.max) / 2,
matchType: 'exact'
};
}
}
// 2. Chercher le bracket le plus proche (max 3° de tolérance)
const MAX_TOLERANCE = 3;
let closest = null;
let minDistance = Infinity;
for (let i = 0; i < outcomes.length; i++) {
const bracket = parseBracket(outcomes[i]);
if (!bracket) continue;
const distance = Math.min(
Math.abs(predictedTemp - bracket.min),
Math.abs(predictedTemp - bracket.max)
);
if (distance < minDistance && distance <= MAX_TOLERANCE) {
minDistance = distance;
closest = {
index: i,
bracket: outcomes[i],
...bracket,
mid: (bracket.min + bracket.max) / 2,
matchType: 'closest',
distance
};
}
}
return closest;
}
Calcul du ROI Potentiel
javascript
function calculatePotentialROI(marketPrice, edge) {
if (edge <= 0 || marketPrice <= 0) return 0;
// ROI brut = (1 / prix) - 1 si le bet gagne
const grossROI = (1 / marketPrice) - 1;
// Ajuster pour les frais Polymarket (~2%)
const POLYMARKET_FEE = 0.02;
const netROI = grossROI - POLYMARKET_FEE;
return Math.max(0, netROI * 100); // En pourcentage
}
Gestion des Risques
Règles de Position
javascript
const RISK_RULES = {
// Max par position
maxPositionSize: 0.03, // 3% du bankroll
// Max exposition totale
maxTotalExposure: 0.15, // 15% du bankroll
// Corrélation
maxCorrelatedPositions: 3, // Max 3 positions même ville
// Stop-loss implicite
// (sur Polymarket, pas de stop-loss, mais on peut vendre)
exitOnEdgeLoss: -0.05, // Sortir si edge passe négatif > 5%
// Volume minimum
minVolume: 1000, // Ignorer marchés < $1000
// Horizon maximum
maxDaysAhead: 7, // Pas de trade > 7 jours
};
Checklist Avant Trade
javascript
const PRE_TRADE_CHECKLIST = [
(market) => market.volume >= RISK_RULES.minVolume,
(market) => market.daysAhead <= RISK_RULES.maxDaysAhead,
(market) => market.analysis.edge >= SIGNAL_THRESHOLDS.BUY,
(market) => market.analysis.modelAgreement >= 0.6,
(market) => !market.closed,
(market) => market.outcomes.length >= 3, // Assez de brackets
];
function validateTrade(market) {
return PRE_TRADE_CHECKLIST.every(check => check(market));
}
Métriques de Performance
KPIs à Tracker
javascript
const PERFORMANCE_METRICS = {
// Win rate
winRate: (wins, total) => wins / total,
// Expected Value
expectedValue: (edge, avgROI) => edge * avgROI,
// Sharpe Ratio (adapté)
sharpe: (avgReturn, stdReturn) => avgReturn / stdReturn,
// Max Drawdown
maxDrawdown: (peakValue, troughValue) => (peakValue - troughValue) / peakValue,
// Profit Factor
profitFactor: (grossProfit, grossLoss) => grossProfit / Math.abs(grossLoss),
// Brier Score (précision des probabilités)
brierScore: (predictions, outcomes) => {
return predictions.reduce((sum, p, i) => {
const outcome = outcomes[i] ? 1 : 0;
return sum + Math.pow(p - outcome, 2);
}, 0) / predictions.length;
}
};
Commandes
- •
/trading-edge:analyze [market_id]— Analyse détaillée d'un marché - •
/trading-edge:portfolio— État du portefeuille simulé - •
/trading-edge:backtest [days]— Backtester la stratégie - •
/trading-edge:optimize— Suggérer des améliorations d'algo