Projet final : une stratégie vérifiable
Assemblez une stratégie paramétrable et vérifiez ses signaux, ses transactions et ses résultats.
Le projet final réunit dans un fichier les éléments appris : moyennes mobiles, inputs, conditions, fonction personnalisée, ordres et affichage. L’objectif est de pouvoir rattacher chaque décision du programme aux données et aux règles décrites.
Décrire le programme sans code
La stratégie surveille le croisement de deux EMA. Une troisième moyenne, une SMA plus longue, vérifie aussi si la clôture est au-dessus de la moyenne.
Une entrée longue exige que l’EMA rapide passe du dessous au-dessus de la lente et que la clôture soit au-dessus de la SMA de filtrage de tendance. Les longueurs doivent respecter rapide < lente < tendance. Si ce n’est pas le cas, le programme ne passe aucun ordre d’entrée.
Un croisement baissier des EMA demande une fermeture au marché de la position longue. L’entrée possède aussi un stop et un objectif en ticks. La simulation utilise une quantité fixe d’une unité, une commission de 0,1 % et un slippage de deux ticks.
Ce sont les règles de l’exercice. Elles ne permettent pas de conclure que le programme sera rentable sur un marché. Pour les vérifier, nous utilisons les données de démonstration fixes du livre.
Le programme complet
Créez final_strategy.py :
"""
@pyne edge
"""
from pynecore.lib import close, color, input, location, plot, plotshape, script, shape, strategy, ta
def lengths_are_ordered(fast: int, slow: int, trend: int) -> bool:
return fast < slow and slow < trend
@script.strategy(
"Filtered Crossing Strategy",
overlay=True,
initial_capital=10000,
default_qty_type=strategy.fixed,
default_qty_value=1,
pyramiding=1,
commission_type=strategy.commission.percent,
commission_value=0.1,
slippage=2,
)
def main(
fast_length: int = input.int(9, title="Fast EMA length", minval=1, maxval=200),
slow_length: int = input.int(21, title="Slow EMA length", minval=1, maxval=200),
trend_length: int = input.int(50, title="Trend SMA length", minval=1, maxval=200),
use_trend_filter: bool = input.bool(True, title="Use trend filter"),
stop_ticks: int = input.int(100, title="Stop distance (ticks)", minval=1),
target_ticks: int = input.int(200, title="Target distance (ticks)", minval=1),
):
fast_average = ta.ema(close, fast_length)
slow_average = ta.ema(close, slow_length)
trend_average = ta.sma(close, trend_length)
crosses_up = ta.crossover(fast_average, slow_average)
crosses_down = ta.crossunder(fast_average, slow_average)
valid_lengths = lengths_are_ordered(fast_length, slow_length, trend_length)
trend_passes = not use_trend_filter or close > trend_average
entry_signal = valid_lengths and crosses_up and trend_passes
if entry_signal:
strategy.entry("Long", strategy.long)
if crosses_down:
strategy.close("Long")
strategy.exit("Bracket", "Long", loss=stop_ticks, profit=target_ticks)
plot(fast_average, "Fast EMA", color=color.blue, linewidth=2)
plot(slow_average, "Slow EMA", color=color.orange, linewidth=2)
plot(trend_average, "Trend SMA", color=color.gray, linewidth=2)
plotshape(entry_signal, "Entry signal", style=shape.triangleup,
location=location.belowbar, color=color.green)Enregistrez et exécutez sur les données de démonstration. Le graphique affiche trois moyennes et des triangles verts marquant la condition d’entrée. Les repères de transactions de la stratégie montrent les entrées et sorties effectivement exécutées.

Que calcule-t-on à chaque bougie ?
Les trois moyennes et les deux croisements sont calculés avant les conditions, même si le filtre empêche ensuite l’entrée. Leur état de calcul suit ainsi la série complète de bougies.
Notre fonction lengths_are_ordered examine trois nombres et renvoie une valeur logique. Elle ne possède pas d’état propre aux bougies. valid_lengths conserve le résultat actuel du test pour les expressions suivantes.
trend_passes est vrai si le filtre est désactivé ou si la clôture est au-dessus de la SMA. input.bool fournit une entrée logique activable. not use_trend_filter est vrai quand le filtre est désactivé : la seconde condition n’a alors pas besoin d’être examinée.
Même filtre désactivé, la moyenne est calculée et tracée. L’interrupteur change le test d’entrée, pas l’affichage de la ligne.
Des longueurs dans un ordre invalide
Les limites séparées des inputs exigent seulement que chaque longueur soit comprise entre 1 et 200. Vous pouvez donc saisir une longueur rapide de 30 et une lente de 10, en violation de l’ordre imposé.
La fonction de vérification teste donc séparément leur relation. Si l’ordre est incorrect, les lignes restent visibles, mais entry_signal ne peut pas être vrai. Le programme ne passe ainsi pas d’entrées avec des longueurs contraires aux règles de l’exercice.
Vous pouvez le vérifier directement dans le débogueur avec valid_lengths. Si les inputs enregistrés sont incorrects, cette valeur reste fausse pendant toute l’exécution.
Un signal ne produit pas forcément une nouvelle transaction
Le triangle vert marque la condition d’entrée. La stratégie transmet l’ordre au simulateur. Son exécution peut nécessiter d’autres conditions : une bougie suivante, un capital suffisant et une quantité autorisée, par exemple.
Avec pyramiding=1, le simulateur n’autorise pas forcément une nouvelle entrée longue si une position longue est déjà ouverte. Ne supposez donc pas que le nombre de signaux égale le nombre de transactions.
Au début de la série, la SMA de tendance sur cinquante bougies manque encore. Avec le filtre activé, il est impossible de considérer le prix au-dessus de cette moyenne ; l’entrée ne remplit donc pas cette condition.
Vérifier le fonctionnement
Commencez avec les valeurs par défaut et vérifiez quantité, coûts et mode d’exécution dans Properties. Un réglage enregistré auparavant peut remplacer celui du code source.
Repérez un signal vert. Avec le débogueur ou les valeurs du graphique, examinez les deux EMA à cette bougie et à la précédente. Vérifiez le croisement, le filtre SMA et l’ordre des longueurs. Les trois conditions doivent être vraies.
Cherchez ensuite l’entrée correspondante dans Trades. Vérifiez à quelle bougie et à quel prix elle s’est exécutée. Pour la sortie, déterminez si le croisement, le stop ou l’objectif explique la fermeture.
Essayer des cas prévisibles
Pour vérifier le fonctionnement, choisissez des modifications dont vous pouvez prévoir l’effet :
| Modification | Vérification attendue |
|---|---|
| Longueurs rapide et lente identiques | Aucun nouveau signal d’entrée grâce au contrôle des longueurs |
| Filtre de tendance désactivé | Le croisement EMA n’exige plus une clôture au-dessus de la SMA |
| Distances du stop et de l’objectif accrues | Les niveaux modifiés peuvent changer la bougie de sortie |
| Quantité modifiée | Le signal de prix reste identique, le montant peut changer |
Effectuez une seule modification par exécution. Notez d’abord votre prévision, puis le résultat effectif. En cas d’écart, cherchez la première décision différente avec le débogueur.
Comparer les résultats
Pour comparer le filtre activé et désactivé, conservez données, quantité, coûts et longueurs. Examinez la courbe Performance avec Trades. Un faible nombre de transactions ou une dernière position ouverte influencent aussi le résumé.
Si vous utilisez des données historiques réelles, fixez la période de réglage et une période distincte de vérification avant de regarder les résultats. Modifier les paramètres après avoir vu la période de vérification revient à utiliser aussi celle-ci pour le réglage.
Les données doivent commencer avec suffisamment de bougies d’initialisation. Exécuter une période découpée séparément reconstruit les moyennes et l’état des positions. Le début de cette exécution peut donc différer du résultat obtenu pour la même période au milieu d’une série plus longue.
À vous de jouer
Ajoutez un interrupteur Use crossings exit. Activé, il doit conserver la fermeture par croisement baissier ; désactivé, seuls le stop et l’objectif doivent fermer la position. Calculez le croisement baissier dans les deux cas.
Solution de l’exercice
Dans les paramètres de main() :
use_crossing_exit: bool = input.bool(True, title="Use crossings exit"),La branche de fermeture :
if use_crossing_exit and crosses_down:
strategy.close("Long")Conservez crosses_down = ta.crossunder(...) avant les conditions. L’appel strategy.exit reste présent dans les deux positions de l’interrupteur.
Sans fermeture par croisement, une position peut rester ouverte après un croisement baissier des EMA. Un nouveau signal d’entrée peut alors apparaître sans ouvrir de transaction supplémentaire, puisqu’une position existe déjà.
Créer votre propre variante
Copiez le programme sous un autre nom et décrivez une seule règle à ajouter. Précisez ses données, la bougie où elle décide, sa vérification à la main et l’input qui la modifie.
Calculez d’abord cette règle dans une variable séparée. Tracez-la ou observez-la dans le débogueur avant d’en faire une condition d’entrée ou de sortie. Vous pourrez chercher une erreur au niveau du calcul avant d’expliquer le résultat de toute la stratégie.
Le code source, les réglages enregistrés et les données décrivent ensemble votre expérience. Les conserver rend l’exécution reproductible et vérifiable.