Passer au contenu
Toutes les stratégies

Stratégies

Flux de rééquilibrage des couvertures en fin de mois FX : négocier l’euro ETF (FXE) face au mouvement mensuel des actions US avant le fixing de fin de mois (USEQ 1-DAY, long/short, jambe de signal SPY, position neutre environ 85% du temps)

Résultat: Abandonné

MonthEndFxHedgeRebalanceFxe

Résumé des résultats

La stratégie a tenté de capter le flux de rééquilibrage des couvertures de fin de mois sur EUR/USD en négociant FXE contre le mouvement mensuel à date de SPY, et en conservant la position pendant 1-5 sessions autour de la fin de mois. Après 2 itérations, le backtest complet sur 20 ans comptait 183 transactions, un rendement de -5.0%, un Sharpe de -0.08 et un rendement par transaction de -0.024%. Les coûts d'impact ont dépassé le faible avantage brut, et la stratégie a perdu de l'argent chaque année à partir de 2020. L'analyste l'a abandonnée lors de l'examen du backtest en raison de son espérance négative et d'un signal sans information mesurable. L'analyste a précisé que cela ne rejette que la version quotidienne de ETF de l'idée, et non le résultat de l'article sur le taux de change au comptant intrajournalier EUR/USD au fixing de Londres.

Hypothèse

Dérivé par le responsable de recherche de Melvin & Prins (2015, « Couverture des actions et taux de change au fixing de 4 h à Londres »). Les institutions étrangères qui détiennent des actions US couvrent leur exposition au dollar avec des contrats à terme FX et redimensionnent ces couvertures en fin de mois, autour du fixing londonien 11:00 ET. Si les actions US ont progressé depuis le début du mois, elles doivent vendre davantage de USD, de sorte que EUR/USD tend à progresser à l’approche du fixing ; si les……Afficher plusAfficher moins

Dérivé par le responsable de recherche de Melvin & Prins (2015, « Couverture des actions et taux de change au fixing de 4 h à Londres »). Les institutions étrangères qui détiennent des actions US couvrent leur exposition au dollar avec des contrats à terme FX et redimensionnent ces couvertures en fin de mois, autour du fixing londonien 11:00 ET. Si les actions US ont progressé depuis le début du mois, elles doivent vendre davantage de USD, de sorte que EUR/USD tend à progresser à l’approche du fixing ; si les actions ont baissé, elles rachètent USD. Le flux est mécanique et fixé par le calendrier ; l’avantage est donc une prime de fourniture de liquidité, et non une prévision. FXE.USEQ (un ETF en euros, équivalant effectivement à EUR/USD, historique depuis décembre 2005) est négocié à l’achat ou à la vente, et SPY.USEQ constitue la jambe de signal. Sur des barres journalières USEQ, une décision à la clôture d’une séance est exécutée à l’ouverture de la séance suivante ; cette fonctionnalité est prise en charge nativement, aucune substitution n’est donc nécessaire. Aucun essai antérieur ne négocie une devise ni n’utilise ce canal. Environ 145 transactions sont attendues sur environ 20 ans. Calcul des frais : environ 0.12% de capture brute par transaction contre environ 0.04% de coût aller-retour sur USEQ, soit environ 0.08% net, approximativement 2x de coût et au-dessus du seuil +0.05% USEQ. Le contrôle du risque suit la voie (b) : une sortie calendaire avec un notionnel plafonné à 100% des fonds propres, sans effet de levier.

negative_expectancy. Comparaison au seuil plancher : avg_trade_return_pct est -0.024% par rapport au seuil minimal requis pour l’évaluation +0.05% USEQ, sur un échantillon fiable de 183 transactions de fin de mois réparties sur 20 ans (2006-2026). PF est 0.926, le ratio de Sharpe est de -0.08, le rendement total de -5.0%, et le drawdown maximal est de 18.8% par rapport au seuil 15% préenregistré. La prévision IC du score z depuis le début du mois pour SPY est de 0.025 (t=0.72), si bien que l’optimiseur ne dispose……Afficher plusAfficher moins

negative_expectancy. Comparaison au seuil plancher : avg_trade_return_pct est -0.024% par rapport au seuil minimal requis pour l’évaluation +0.05% USEQ, sur un échantillon fiable de 183 transactions de fin de mois réparties sur 20 ans (2006-2026). PF est 0.926, le ratio de Sharpe est de -0.08, le rendement total de -5.0%, et le drawdown maximal est de 18.8% par rapport au seuil 15% préenregistré. La prévision IC du score z depuis le début du mois pour SPY est de 0.025 (t=0.72), si bien que l’optimiseur ne dispose d’aucun signal pour ajuster z_entry ou entry_sessions. Le gain brut avant impact est d’environ +0.10% par transaction, mais l’impact FXE modélisé (122% du gain brut ; capacité d’environ $67k) le dépasse. Chaque année de 2020 à 2026 est négative. La préoccupation de QA se confirme sur l’historique complet. La vérification entry_sessions=1 proposée par QA se situe dans la plage ajustable, mais un IC sans information et un avantage brut inférieur à l’impact ne laissent aucune zone viable vers laquelle optimiser. Portée : cela invalide uniquement la mise en œuvre opérationnelle du responsable de la recherche : le FXE ETF sur des barres journalières, avec exécution à l’ouverture suivante et détention pendant 1-5 séances à travers le fixing de fin de mois. Cela ne remet pas en cause les conclusions de Melvin & Prins, qui portent sur le spot intrajournalier EUR/USD au fixing de 4 h à Londres ; l’effet étudié ne peut pas être isolé avec des barres journalières ETF. Aucun défaut comptable ou de données n’est allégué.

Implémentation

Flux de rééquilibrage des couvertures FX en fin de mois sur FXE.USEQ (1-DAY), avec SPY.USEQ comme jambe de signal. Le mouvement logarithmique mensuel à date de SPY est normalisé en score z par sa volatilité quotidienne sur 60 sessions, multipliée par la racine carrée du nombre de sessions écoulées. Dans les entry_sessions dernières sessions avant le dernier jour de négociation du mois, si |z| >= z_entry, prendre une position longue sur FXE lorsque SPY progresse depuis le début du mois (les opérateurs étrangers couvrant leurs positions vendent USD), ou une position courte sur FXE lorsque SPY recule. Une transaction par mois. La position est clôturée à la clôture du dernier jour de négociation du mois, au moment de la décision. Toutes les décisions USEQ sont exécutées à l'ouverture de la session suivante.

L’itération 2 corrige la conclusion critique QA. position_size() dimensionnait la position à exactement 100% des fonds propres à partir du cours de clôture de la barre de décision, mais l’ordre est exécuté à l’ouverture de la séance suivante. Un gap haussier, auquel s’ajoutaient les frais, a fait dépasser à la marge initiale 1x les fonds propres MTM ; le contrôle de marge de base a donc refusé l’ordre. L’ordre refusé est ensuite resté dans _inflight_entries et a bloqué toutes les entrées ultérieures, ce qui……Afficher plusAfficher moins

L’itération 2 corrige la conclusion critique QA. position_size() dimensionnait la position à exactement 100% des fonds propres à partir du cours de clôture de la barre de décision, mais l’ordre est exécuté à l’ouverture de la séance suivante. Un gap haussier, auquel s’ajoutaient les frais, a fait dépasser à la marge initiale 1x les fonds propres MTM ; le contrôle de marge de base a donc refusé l’ordre. L’ordre refusé est ensuite resté dans _inflight_entries et a bloqué toutes les entrées ultérieures, ce qui explique pourquoi le bac à sable affichait 0-2 transactions. Le dimensionnement applique maintenant une marge de size_buffer sur 0.97 (3% de marge de sécurité, suffisante pour couvrir les plus grands gaps nocturnes de FXE), à la fois au notionnel et au plafond de 100% des fonds propres. notional_fraction reste à 1.0 et l’effet de levier à 1.0, conformément aux paramètres fixes du contrat. Le contrôle du risque suit toujours la voie (b) : une sortie calendaire avec chaque position à hauteur d’au plus 100% des fonds propres. La logique du signal, de l’entrée et de la sortie est inchangée. Concernant l’avertissement QA : entry_month est toujours consommé dans should_enter. Cela préserve la règle d’une transaction par fenêtre de fin de mois, et le refus de marge susceptible de faire abandonner un ordre ne devrait plus se produire. Note consultative : la capture brute attendue d’environ 0.12% par transaction est faible par rapport au bruit quotidien de FXE ; le résultat pourrait donc être non concluant.

Résultats de vérification

Facultativement, définir entry_month dans on_position_opened (ou à la réception de l’exécution) plutôt que dans should_enter.

should_enter() définit entry_month avant que l'ordre soit dimensionné et exécuté. Si l'entrée est abandonnée (taille 0, notionnel inférieur au minimum ou refus au contrôle de marge), le reste de la fenêtre du mois est ignoré. La marge de 0.97 size_buffer rend un refus de marge peu probable ; l'effet est donc mineur.

Aucun besoin pour la recherche. Pour le trading en papier, envisagez de dériver entry_month à partir de la ts_opened de la position ouverte.

L'état scalaire (entry_month, mtd_anchor, la deque _spy_rets) est conservé en mémoire. Après le redémarrage d'un nœud de trading en papier, la phase de chauffe reconstruit l'état du signal jusqu'à calculate_signal, mais entry_month redémarre à 0. En cas de redémarrage pendant une fenêtre alors qu'une position est ouverte, celle-ci serait tout de même clôturée correctement, car cur_month != entry_month déclenche should_exit. Ce problème ne se pose qu'en cas de redémarrage ; le backtest ne redémarre jamais.

Sur une fenêtre de 1561 jours avec les paramètres par défaut (z_entry 0.5, entry_sessions 3), le bac à sable a réalisé 60 transactions, avec une moyenne de -0.29% du notionnel, PF 0.37 et un Sharpe de -1.14. Il a perdu de l’argent dans chaque tercile de volatilité et dans 2022. L’hypothèse prévoit environ +0.12% brut par transaction, ce qui est faible par rapport au bruit quotidien de FXE (environ 0.4-0.5%) sur une détention de 1-5 séances. L’impact modélisé ($10.6k sur $10.9M négociés, environ 0.1% par aller-retour) est déjà proche de la capture brute annoncée. Vérifier d’abord sur l’historique complet : les prévisions IC du score z depuis le début du mois pour SPY face au rendement FXE entre l’entrée et le fixing de fin de mois, et déterminer si une fenêtre de 1 séances (entry_sessions=1, avec détention uniquement jusqu’au fixing du dernier jour) donne de meilleurs résultats qu’une détention de 3-5 séances, qui consiste surtout en du bruit. Il s’agit d’un tirage de test sommaire ; il est donc consultatif et ne bloque pas.

Revue du backtest

Sharpe
-0,08
Rendement total
-5,00%
Drawdown max
18,83%
Total des transactions
183
Taux de réussite
48,1%
Facteur de profit
0,93

Les transactions mettent en œuvre le mécanisme décrit : 183 entrées de fin de mois sur 20 ans, proche des ~145 prévues. Il y a eu 119 positions acheteuses et 64 positions vendeuses, ce qui concorde avec le fait que SPY progresse plus souvent qu’il ne baisse depuis le début du mois. Les sorties sont déterminées par le calendrier, l’exposition est 11% et aucune position n’est ouverte à la fin.

La comptabilité concorde : les audits des entrées sont propres, end_unrealized_pct est de 0 et le registre des coûts ne présente aucun résidu.

L'espérance nette est négative sur un échantillon fiable. avg_trade_return_pct est de -0.024%, sous le seuil de +0.05% USEQ (+0.10% de préférence). PF est de 0.926, Sharpe -0.08 (CI -0.53 à 0.32) et rendement total -5.0%. Le drawdown maximal est de 18.8%, contre 15% préenregistré, et la stratégie est sous l'eau depuis 2,992 jours.

La prévision n'apporte aucune information : le IC du signal exécuté est de 0.025 (t=0.72, n=858). Le diagnostic le classe comme rejected_no_information.

Les coûts dominent. L'impact modélisé est de $25.2k, soit 122% du PnL brut ; la capacité de FXE est d'environ $67k, contre des positions d'environ $100k. Le brut avant impact est d'environ +$20k sur environ $19M de notionnel aller-retour, soit environ +0.10% par transaction. C'est proche des 0.12% de l'hypothèse, mais l'impact d'environ 0.13% par aller-retour l'efface.

Les rendements s'érodent : la stratégie a été positive la plupart des années de 2008 à 2019, et négative chaque année de 2020 à 2026 (2022 -3.9%, 2023 -3.4%, 2024 -3.75%, 2025 -2.8%, 2026 -3.2% YTD). Le flux, tel qu'il est capté ici, a disparu des données récentes.

Historique des itérations

Échec de la vérification (couche 4 — examen QA) [class=code_defect]: - [edge_concern] L’hypothèse prévoit une capture brute d’environ 0.12% par transaction. La position est détenue pendant 2-6 FXE séances, chacune ayant un écart-type quotidien d’environ 0.4-0.6%, et les barres journalières incluent le retournement après le fixing jusqu’à la clôture du dernier jour ainsi que le gap nocturne jusqu’à l’ouverture suivante, comme le reconnaît le développeur. Le rapport signal/bruit par transaction est donc très……Afficher plusAfficher moins

Échec de la vérification (couche 4 — examen QA) [class=code_defect]: - [edge_concern] L’hypothèse prévoit une capture brute d’environ 0.12% par transaction. La position est détenue pendant 2-6 FXE séances, chacune ayant un écart-type quotidien d’environ 0.4-0.6%, et les barres journalières incluent le retournement après le fixing jusqu’à la clôture du dernier jour ainsi que le gap nocturne jusqu’à l’ouverture suivante, comme le reconnaît le développeur. Le rapport signal/bruit par transaction est donc très faible. Avec environ 145-180 transactions sur 20 ans, il faut s’attendre à une mesure non concluante ou proche de zéro plutôt qu’à un résultat net. Les positions vendeuses sur FXE supportent également un coût d’emprunt modélisé (environ $6.8 sur une position vendeuse de $99k pendant 5 jours dans ma relecture), ce qui compte face à un objectif de 0.12%. À titre consultatif uniquement. - [critical] position_size() dimensionne la position à exactement 100% des fonds propres, int(equity / decision-bar close), sans marge de sécurité. Sur une place où la séance dure USEQ, l’ordre est exécuté à l’ouverture de la séance NEXT, et le contrôle de marge de base submit_order() (refuse_over_margin, évalué à partir du cache plutôt que du cours de clôture de la barre de décision) refuse un ordre dont la marge initiale 1x plus les frais dépasse les fonds propres MTM, même de quelques dollars. J’ai reproduit cela avec le véritable BacktestRunner (USEQ, du 2020-03-17 au 2026-10-09, paramètres par défaut du contrat). La transaction 1 (SHORT 956 FXE, du 2020-03-27 au 2020-04-01) a été exécutée. Sur 2020-04-27, le message « Order refused: BUY 979 FXE.USEQ would need more initial margin than the account holds at 1x leverage » a été consigné. Le modèle de base avait déjà enregistré cet ordre refusé dans _inflight_entries avant que submit_order() ne renvoie son résultat. L’ordre n’est jamais soumis, donc il n’est jamais clôturé, et _entry_order_inflight() renvoie True pour le reste de l’exécution. Chaque entrée ultérieure (60 sur 62) est abandonnée avec le message « Blocked stacked entry ... still in flight ». entry_diag : entries_submitted 62, denied_margin 1, dropped_entry_inflight 60. Il en résulte une ou deux transactions en 6.5 ans alors que le signal se déclenche 64 fois ; ma relecture hors ligne de la même logique sur les barres du catalogue donne 183 entrées sur 2006-2026. C’est pourquoi le bac à sable Layer-3 affichait 0 transactions, et le backtest sur l’historique complet mesurerait une stratégie bloquée plutôt que l’hypothèse. (ligne 131) — correction : laisser une marge pour le gap jusqu’à la prochaine ouverture, les frais et la dérive MTM, tout en conservant notional_fraction=1.0 pour le contrat et le plafond de 100% des fonds propres. Par exemple : notional = equity * min(notional_fraction, 1.0) * leverage * 0.97, puis min(notional, 0.97 * equity). Une marge de 3% couvre les plus grands gaps nocturnes de FXE. Pour plus de sécurité seulement, comparer également la taille calculée à la marge disponible avant de la renvoyer. Relancer ensuite le bac à sable : il devrait afficher environ une transaction par fin de mois admissible, et non 0-2. - [warning] should_enter() définit entry_month avant que l’ordre soit dimensionné ou accepté. Si le modèle de base abandonne l’ordre (marge, notional minimum, taille 0), le mois est consommé et les séances ultérieures de la fenêtre ne peuvent pas réessayer. C’est acceptable pour « une transaction par fenêtre de fin de mois », mais un mois est perdu silencieusement lorsqu’un ordre est abandonné. (ligne 114) — correction : facultatif : définir entry_month dans on_position_opened (ou à l’exécution de l’ordre), ou accepter ce comportement et le documenter. - [warning] min_bars_required() est redéfini à vol_lookback + 2 = 62. C’est délibéré et acceptable. À noter que le score z nécessite déjà max(5, vol_lookback // 2) rendements ainsi qu’un ancrage mensuel valide ; la première entrée ne peut donc pas survenir avant environ la barre 62. (ligne 49) — correction : aucun changement requis.

Laisser une marge pour le gap jusqu’à la prochaine ouverture, les frais et la dérive MTM, tout en conservant notional_fraction=1.0 pour le contrat et le plafond de 100% des fonds propres. Par exemple : notional = equity * min(notional_fraction, 1.0) * leverage * 0.97, puis min(notional, 0.97 * equity). Une marge de 3% couvre les plus grands gaps nocturnes de FXE. Pour plus de sécurité seulement, comparer également la taille calculée à la marge disponible avant de la renvoyer. Relancer ensuite le bac à sable : il devrait afficher environ une transaction par fin de mois admissible, et non 0-2.

position_size() dimensionne exactement à 100% des fonds propres, int(equity / decision-bar close), sans marge de sécurité. Sur une place où les sessions durent USEQ, l'ordre est exécuté à l'ouverture de la session NEXT, et le contrôle de marge de base de submit_order() (refuse_over_margin, valorisé depuis le cache plutôt que depuis le cours de clôture de décision) refuse un ordre dont la marge initiale requise par 1x, plus les frais, dépasse les fonds propres MTM, même de quelques dollars. J'ai reproduit cela……Afficher plusAfficher moins

position_size() dimensionne exactement à 100% des fonds propres, int(equity / decision-bar close), sans marge de sécurité. Sur une place où les sessions durent USEQ, l'ordre est exécuté à l'ouverture de la session NEXT, et le contrôle de marge de base de submit_order() (refuse_over_margin, valorisé depuis le cache plutôt que depuis le cours de clôture de décision) refuse un ordre dont la marge initiale requise par 1x, plus les frais, dépasse les fonds propres MTM, même de quelques dollars. J'ai reproduit cela avec le véritable BacktestRunner (USEQ, de 2020-03-17 à 2026-10-09, paramètres par défaut du contrat). La transaction 1 (SHORT 956 FXE, du 2020-03-27 au 2020-04-01) a été exécutée. Le 2020-04-27, le message 'Order refused: BUY 979 FXE.USEQ would need more initial margin than the account holds at 1x leverage' a été enregistré. Le modèle de base avait déjà inscrit cet ordre refusé dans _inflight_entries avant le retour de submit_order(). L'ordre n'est jamais soumis, donc il n'est jamais clôturé, et _entry_order_inflight() renvoie True jusqu'à la fin de l'exécution. Chaque entrée ultérieure (60 sur 62) est abandonnée avec le message 'Blocked stacked entry ... still in flight'. entry_diag : entries_submitted 62, denied_margin 1, dropped_entry_inflight 60. Le résultat est une ou deux transactions sur 6.5 ans, alors que le signal se déclenche 64 fois ; ma simulation hors ligne de la même logique sur les barres du catalogue donne 183 entrées sur 2006-2026. C'est pourquoi le bac à sable de couche -3 affichait 0 transactions : le backtest sur tout l'historique mesurerait une stratégie bloquée plutôt que l'hypothèse.

En option : définissez entry_month dans on_position_opened (ou lors de la confirmation de l'exécution de l'ordre), ou acceptez ce comportement et documentez-le.

should_enter() définit entry_month avant que l'ordre soit dimensionné ou accepté. Si le système de base abandonne l'ordre (marge, notionnel minimal, taille 0), le mois est consommé et les sessions suivantes de la fenêtre ne peuvent pas réessayer. Cela convient pour « une transaction par fenêtre de fin de mois », mais un mois est silencieusement perdu si un ordre est abandonné.

Aucun changement requis.

min_bars_required() est redéfini comme vol_lookback + 2 = 62. C'est intentionnel et correct. Notez que le score z nécessite déjà max(5, vol_lookback // 2) rendements ainsi qu'un ancrage mensuel valide : la première entrée ne peut donc pas survenir avant environ la barre 62.

Résumé des résultats

Le test d'un flux au fixing intrajournalier FX, avec des barres quotidiennes de ETF et des exécutions à l'ouverture suivante, situe l'impact de marché d'un ETF à faible capacité (environ $67k) au-dessus de l'avantage brut d'environ 0.10%. Il faut donc des données intrajournalières au comptant proches du fixing pour tester correctement cet effet.

La stratégie a été abandonnée lors de l'examen du backtest, avant l'optimisation, en raison d'une espérance négative : rendement de avg_trade_return_pct pour -0.024%, contre le seuil de +0.05% USEQ ; un signal IC de 0.025 (t=0.72, classé sans information) ; et un drawdown supérieur à 15% préenregistré. Toutes les années de 2020 à 2026 ont affiché un résultat négatif.

D'après Melvin & Prins (2015), la stratégie a pris une position longue ou courte sur l'euro ETF FXE.USEQ, selon le sens du mouvement mensuel à date de SPY, normalisé en score z, durant les dernières sessions de chaque mois. Elle misait sur le fait que les détenteurs étrangers d'actions, en réajustant leurs couvertures USD au fixing de fin de mois, feraient évoluer EUR/USD dans ce sens.

Sur 183 transactions de fin de mois entre 2006 et 2026, le rendement total a été de -5.0%, le Sharpe de -0.08 (CI -0.53 à 0.32), le facteur de profit de 0.926, avg_trade_return_pct pour -0.024% et le drawdown maximal de 18.8%. La capture brute avant impact était d'environ +0.10% par transaction, mais l'impact FXE modélisé ($25.2k, 122% du PnL brut) l'a rendue négative.

Stratmill est un outil de recherche et de paper trading, pas un conseil financier ni un courtier. Les résultats de backtest et de paper trading sont hypothétiques. Le trading comporte un risque de perte.