Trading avec des algorithmes génétiques


Voici la traduction d’un article intéressant d’Andrew du site TheGeneticTrader qui s’est lancé dans un projet de trading avec algorithme génétique. Ne soyez pas dissuadé par les morceaux de codes documentant l’article, car le texte se suffit à lui même.

J’ai rencontré il y a un couple de semaines un ami de longue date qui m’a raconté qu’il s’intéressait au trading et plus particulièrement aux paris financiers (financial spread betting) et aux possibilités d’en faire de l’argent puisqu’il connaissait des gens qui en vivaient. Je dois avouer que j’ai souvent pensé qu’il était possible d’utiliser des logiciels pour faire de l’argent sur les marchés, mais je n’avais pas réalisé encore que les paris financiers offraient une entrée à faible coût (mais haut risque…).

Cela semble assez évident mais vous devriez être gagnant si vous gagnez plus sur vos paris gagnants que ce que vous perdez sur les perdants, même si cela signifie d’être plus souvent perdant que gagnants. L’accent devrait donc être mis plus sur le gain moyen que le pourcentage de gains et de pertes. Comme cela est expliqué dans tous les livres, le plus grand danger du trader est ses émotions qui le poussent à couper trop vite ses positions gagnantes et à pyramider ses pertes pour essayer de se rattraper.

Utiliser des logiciels devrait donc enlever la part d’émotion du trading permettant de suivre à la lettre son plan de trading. J’ai donc commencé à écrire des programmes en Scheme pour générer un ordre au marché pour un paris sur le FTSE100 pour le prochain jour en se basant sur les données du précédent. Voici un exemple des mouvements journaliers :

undefined

Le FTSE est une cible idéale pour le pari financier parce qu’il a de larges mouvements journaliers et que les spreads offerts par la plupart des brokers sont très faibles (par exemple IG offre seulement 2 points) et que vous pouvez avoir un stop placé à seulement 30 points de distance ce qui signifie que l’on peut placer des paris à très faible prix.

L’ordre pourrait dire : Buy à 4280 et Stop à 30 points, pas de limites et £1 par point. Si le marché est plus bas que 4280 a n’importe quel moment de la journée, votre ordre sera exécuté. pour chaque point que le marché fait vers le haut, vous gagnez un pound. Si le marché bouge ensuite dans la direction opposée, vous perdrez £1 pour chaque point jusqu’à ce que le stop de 30 points soit atteint et votre ordre fermé avec une perte sèche de 30 pounds. Si par contre, le marché a progressé de 100 points vous serez presque plus riches de 100 pounds (la différence étant le spread prélevé).

La première tâche était donc de construire un simulateur et d’obtenir des données historiques intraday, minutes par minutes et ce sur 3 mois. Le simulateur prends une liste d’ordres et les exécute sur un jour particulier pour calculer le profit ou pertes pour chaque ordre. A l’intérieur de cela, le simulateur fait appel à une fonction qui indique l’ordre à passer en fonction des statistiques du jour précédent. La profitabilité journalière et hebdomadaire est ensuite calculée pour la stratégie en cours afin de décider si elle est gagnante ou pas. Vous pouvez ensuite utiliser cette stratégie pour générer des ordres pour le jour suivant, c’est à dire demain avec l’espérance qe=ue ‘on gagnera plus que ce que l’on perdra.

En utilisant le simulateur, vous pouvez essayer des stratégies que vous pensez qu’elles puissent fonctionner. En voilà une qui n’est pas mauvaise :

 

{xtypo_code};; Stats used by strategy function
(define-type stats
prev-open
prev-close
prev-high
prev-low
)

;; A very simple but surprisingly effective strategy
(define (simple-strategy stats)
(let ((gain (- (stats-prev-close stats) (stats-prev-open stats)))
(range (- (stats-prev-high stats) (stats-prev-low stats)))
(mid (/ (+ (stats-prev-high stats) (stats-prev-low stats)) 2)))
(cond
((> gain 10) (list (make-order – (+ mid 5) 30 0 1.0)))
(else (list (make-order + (+ mid 10) 30 0 1.0)))))){/xtypo_code}

Le simulateur montre que cette stratégie très simple génère autours de 500 points de profits sur les trois mois backtestés (nb: les performances passées ne sont pas des garanties des performances futures). Le faiseur d’ordre donne la direction (+ pour buy et – pour sell), la valeur de l’ouverture pour l’ordre limite, le take profit, le stop et le levier. Notez que la seule entrée que prends la stratégie est l’ouverture du jours précédent, sa cloture, son plus bas et son plus haut. La stratégie marche sur la base que si le FTSE ferme en hausse un jour, il est probable qu’il ferme perdant le lendemain. Evidemment ce n’est pas une lois universelle qui se révellera d’ailleurs souvent fausse mais quand la prédiction est fausse on perd 30 points tandis qu’on en gagne jusqu’à 100 et plus en cas de succès.

Grâce à de l’analyse chartiste classique et en calculant par ordinateurs quelques valeurs optimales, j’ai créé une variation de la règle précédente qui parvenait à obtenir une moyenne de 1000 points de profits pour les données historiques que je possédais mais je n’avais aucun moyen de savoir si c’était optimal ou pas


Algorithmes génétiques

La programmation génétique est une technique permettant de faire évoluer les programmes pour résoudre un problème. Notre souhait est d’obtenir une stratégie de trading en créant des fragments de programme qui se reproduisent et mutent avec le temps tout en étant évalué par une fonction de “fitness” qui permet de supprimer les fragments les moins performants et encourage la reproduction des plus performants. Vous devriez donc comprendre à présent d’où vient le terme de programmation génétique. Dans notre cas, la fonction de “fitness” est le simulateur dont nous avons précédemment parlé : le profit total après les trois mois de test et tout ce qui compte. Si vous faites un bon profit vous pouvez rester et vous reproduire, si vous ne faites pas de profits vous êtes éliminés. La reproduction (croisement) prends des éléments de manière aléatoire des deux parents pour les recombiner, tandis que la mutation modifie aléatoirement un des élément de l’algorithme muté pour en produire un légèrement différent. Après plusieurs générations, vous obtenez une solution optimale au problème que vous essayez de résoudre.

Scheme est le langage parfait pour la programmation génétique car :

  1. Vous pouvez modifier facilement le code en manipulant des listes,
  2. Vous pouvez évaluer votre nouveau code avec eval.

 

Donc pour résoudre notre problème, nous avons besoin d’un ‘trader’ :

{xtypo_code}(define-type trader
dir-func
start-func
stop-func
limit-func
profit){/xtypo_code}

Notre trader est basiquement une collection d’expressions qui sont évaluées pour produire les données nécessaires aux placement des ordres. La valeur “profit” permet de suivre quelles sont les traders à succès.

Il y a une population de traders qui existent dans une liste et sont initialisées avec des expressions aléatoires  :

{xtypo_code}(define (pick-from lst)
(list-ref lst (random-integer (length lst))))

(define (get-random-var)
(pick-from ‘(o c h l g r m)))

(define (get-random-constant x)
(let ((r (- x (random-integer (* x 2)))))
(if (= r 0) (get-random-constant x) r))) ;; Don’t want any divide by zero errors

(define (make-random-trader)
(make-trader (pick-from ‘(+ -)) (get-mutated-term (get-random-var)) (get-mutated-term (get-random-constant 100)) (get-mutated-term (get-random-constant 100)) 0))

(define (initialise-traders num)
(let ((traders ‘()))
(let nxt ((x 0))
(if (< x num)
(begin
(set! traders (cons (make-random-trader) traders))
(nxt (+ x 1))
)))
traders)){/xtypo_code}

 

“Initialise-traders” crée la population de taille “num” en utilisant la fonction “make-random-trader”. Les expressions sont basées sur des constantes aléatoires et des variables aléatoires qui sont o – ouverture d’hier, c – clôture d’hier, h – plus haut d’hier, etc. Une fonction utile prend des éléments d’une certaine liste de manière aléatoire.

Il faut noter que des études ont tout de même montré qu’il était utile d’initialiser la population avec certains bons spécimens connus comme la simple stratégie que nous avons précédemment évoqué.

Reproduction et Mutation

 

La fonction suivante permet le croisement (reproduction) de deux traders en sélectionnant des caractéristiques de chacun d’eux et en additionnant ensuite les mutations :

 

{xtypo_code}(define (breed t1 t2)
;; Create child from parents
(let ((t (make-trader
(pick-from (list (trader-dir-func t1) (trader-dir-func t2)))
(pick-from (list (trader-start-func t1) (trader-start-func t2)))
(pick-from (list (trader-stop-func t1) (trader-stop-func t2)))
(pick-from (list (trader-limit-func t1) (trader-limit-func t2)))
0 )))

;; Add mutation
(let ((r (random-integer 5))) ;; what to modify
(cond
((= r 1) (trader-dir-func-set! t (mutate (trader-dir-func t))))
((= r 2) (trader-start-func-set! t (mutate (trader-start-func t))))
((= r 3) (trader-stop-func-set! t (mutate (trader-stop-func t))))
((= r 4) (trader-limit-func-set! t (mutate (trader-limit-func t))))))
t)){/xtypo_code}

 

La sélection des traders pour mutation est faite dans cette fonction :

 

{xtypo_code}(define (mutate-list l)
(pick-from (list
(map (lambda (x)
(if (> (random-integer 10) 2) (mutate x) x))
l)
(simplify-list l))))

(define (get-mutated-term t)
(pick-from (list
(get-random-constant 100)
(get-random-var)
`(+ ,t ,(get-random-constant 100))
`(- ,t ,(get-random-var))
`(* ,t ,(get-random-constant 100))
`(/ ,t ,(get-random-var))
`(if (> ,t ,(get-random-constant 100)) ,t ,(get-random-var)))))

(define (mutate f)
(cond
((list? f) (mutate-list f))
((number? f) (pick-from (list (get-mutated-term f) (+ f (get-random-constant 10)))))
((or (eq? f ‘o) (eq? f ‘c) (eq? f ‘h) (eq? f ‘l) (eq? f ‘g) (eq? f ‘r) (eq? f ‘m)) (get-mutated-term f)) ;; Variable
((or (eq? f ‘+) (eq? f ‘-)) (pick-from (list ‘- ‘+ `(if (> ,(get-random-var) ,(get-random-constant 100)) + -))))
((or (eq? f ‘*) (eq? f ‘/)) (pick-from ‘(+ – / *))) ;; Arithmetic operator
((or (eq? f ‘>) (eq? f ‘<) (eq? f ‘=)) (pick-from ‘(> < =))) ;; Logical operator
(else f))) ;; No mutation – don’t know what to do<{/xtypo_code}

La fonction clé ici est “mutate” qui prends le terme d’une expression et la mute en utilisant “get-mutated-term”.

Le reste du code s’occupe de maintenir la population du réservoir de traders en évaluant la profitabilité des membres et ensuite supprimant les moins profitables et reproduisant ce qui le sont le plus.

Les résultats

Le simple exemple de ci dessus parvenait à produire un profit de 509 points sur 3 mois de données historiques, tandis que le trader généré génétiquement est parvenu à produire 1469 points de profits! Mes efforts manuels pour optimiser le trader simple m’ont permis d’atteindre les 1100 points, soit 30% de moins que le modèle génétique.

Par la suite, j’ai étendu les données disponibles aux traders par une moyenne mobile longue et courte ainsi que par les gains du Dow Jones industrial Average. J’ai ré exécuté l’algorithme de sélection génétique pour obtenir cette fois ci un trader avec un profit de 1700 points sur ces trois mois, mais qui curieusement n’utilisait pas les données du Dow Jones.

J’ai ouvert un blog pour documenter la suite de mes efforts dans cette aventure et mes avancées : http://www.thegenetictrader.com

Note de trading Automatique:  Attention toutefois à la sur optimisation…

Traduction : Nicolas Vitale

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>