Système de trading automatique FX réalisé avec python et algorithme génétique Partie 1 Trading automatique FX avec algorithme génétique Partie 2 Implémentation de l'IA de trading en évolution
Je voudrais écrire sur l'API Oanda que j'ai utilisée lorsque j'ai créé le système de trading automatique et la fonction pour passer des ordres d'achat et de vente.
Il a été construit en utilisant 5 types d'API. Pour plus de détails, voir Document API d'Oanda Le jeton OAuth2 peut être émis sur le Web en ouvrant un compte et en vous connectant. Définissez le jeton sur Authorization: Bearer ******** sur la commande curl Header.
"""
01.API d'informations de compte
curl -H "Authorization: Bearer ********" https://api-
fxtrade.oanda.com/v1/accounts/12345
"""
{
"accountId" : 12345,
"accountName" : "Primary",
"balance" : 123456.5765,
"unrealizedPl" : 36.8816,
"realizedPl" : 235.5839,
"marginUsed" : 123456.154,
"marginAvail" : 123456.3041,
"openTrades" : 20,
"openOrders" : 0,
"marginRate" : 0.04,
"accountCurrency" : "JPY"
}
"""
02.Commande API
curl -H "Authorization: Bearer ********" -X POST -d "instrument=EUR_USD&units=2&side=sell&type=market" "https://api-fxtrade.oanda.com/v1/accounts/12345/orders"
"""
{
"instrument" : "EUR_USD",
"time" : "2013-12-06T20:36:06Z", // Time that order was executed
"price" : 1.37041, // Trigger price of the order
"tradeOpened" : {
"id" : 175517237, // Order id
"units" : 1000, // Number of units
"side" : "buy", // Direction of the order
"takeProfit" : 0, // The take-profit associated with the Order, if any
"stopLoss" : 0, // The stop-loss associated with the Order, if any
"trailingStop" : 0 // The trailing stop associated with the rrder, if any
},
"tradesClosed" : [],
"tradeReduced" : {}
}
"""
03.Obtenir la liste des positions actuelles
curl -H "Authorization: Bearer ####" https://api-fxtrade.oanda.com/v1/accounts/######/positions
"""
{
"positions" : [
{
"instrument" : "AUD_USD",
"units" : 100,
"side" : "sell",
"avgPrice" : 0.78216
},
{
"instrument" : "GBP_USD",
"units" : 100,
"side" : "sell",
"avgPrice" : 1.49128
},
{
"instrument" : "USD_JPY",
"units" : 850,
"side" : "buy",
"avgPrice" : 119.221
}
}
"""
04.Obtenez le taux du marché
curl -H "Authorization: Bearer ********" -X GET "https://api-fxtrade.oanda.com/v1/prices?instruments=EUR_USD%2CUSD_JPY%2CEUR_CAD"
"""
{
"prices" : [
{
"instrument" : "EUR_USD",
"time" : "2015-03-13T20:59:58.165668Z",
"bid" : 1.04927,
"ask" : 1.04993,
"status" : "halted"
},
{
"instrument" : "USD_JPY",
"time" : "2015-03-13T20:59:58.167818Z",
"bid" : 121.336,
"ask" : 121.433,
"status" : "halted"
},
{
"instrument" : "EUR_CAD",
"time" : "2015-03-13T20:59:58.165465Z",
"bid" : 1.34099,
"ask" : 1.34225,
"status" : "halted"
}
]
}
"""
05.API pour vérifier l'état des commandes d'achat et de vente
curl -H "Authorization: Bearer ************" https://api-fxtrade.oanda.com/v1/accounts/****/transactions
"""
{
"id" : 1789536248,
"accountId" : *****,
"time" : "2015-03-20T12:17:06.000000Z",
"type" : "TAKE_PROFIT_FILLED",
"tradeId" : 1789531428,
"instrument" : "USD_JPY",
"units" : 1,
"side" : "sell",
"price" : 121.017,
"pl" : 0.02,
"interest" : 0,
"accountBalance" : 33299999.1173
},
{
"id" : 1789560748,
"accountId" : *****,
"time" : "2015-03-20T12:49:38.000000Z",
"type" : "STOP_LOSS_FILLED",
"tradeId" : 1789500620,
"instrument" : "USD_JPY",
"units" : 100,
"side" : "sell",
"price" : 120.899,
"pl" : -18.6,
"interest" : 0,
"accountBalance" : 33299980.3023
}
Ceci est un exemple de mise en œuvre de l'API d'acquisition de prix. Accès Web avec requête urllib. La réponse sera renvoyée dans Json, donc je ferai de mon mieux pour écrire la classe parser. Lorsque le spread s'ouvre (le marché devient rugueux et la différence entre le prix d'achat et le prix de vente s'ouvre), j'ai essayé d'incorporer une fonction qui ne passe pas d'ordre lorsque le taux est défavorable.
■ Exemple de code accédant à l'API sandbox http://api-sandbox.oanda.com/v1/prices?instruments=EUR_USD%2CUSD_JPY%2CGBP_USD%2CAUD_USD
price_api.py
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
import urllib
import requests
import time
import ujson
import pytz
from enum import Enum
class CurrencyPair(Enum):
EUR_USD = 1
USD_JPY = 2
GBP_USD = 3
AUD_USD = 4
class OandaAPIMode(Enum):
PRODUCTION = 1
DEVELOP = 2
SANDBOX = 3
DUMMY = 4
@property
def url_base(self):
api_base_url_dict = {
OandaAPIMode.PRODUCTION: 'https://api-fxtrade.oanda.com/',
OandaAPIMode.DEVELOP: 'https://api-fxpractice.oanda.com/',
OandaAPIMode.SANDBOX: 'http://api-sandbox.oanda.com/',
}
return api_base_url_dict.get(self)
@property
def headers(self):
"""
:rtype : dict
"""
_base = {
'Accept-Encoding': 'identity, deflate, compress, gzip',
'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0',
'Content-type': 'application/x-www-form-urlencoded',
}
if self == OandaAPIMode.SANDBOX:
return _base
if self == OandaAPIMode.DEVELOP:
_base['Authorization'] = 'Bearer {}'.format(get_password('OandaRestAPITokenDemo'))
return _base
if self == OandaAPIMode.PRODUCTION:
_base['Authorization'] = 'Bearer {}'.format(get_password('OandaRestAPIToken'))
return _base
raise ValueError
class OandaServiceUnavailableError(Exception):
"""
Erreur lors de l'arrêt du service
"""
pass
class OandaInternalServerError(Exception):
"""
Erreur lors de l'arrêt du service
"""
pass
class OandaAPIBase(object):
mode = None
class Meta(object):
abstract = True
def __init__(self, mode):
"""
:param mode: OandaAPIMode
"""
self.mode = mode
def requests_api(self, url, payload=None):
#Si la communication échoue, réessayez jusqu'à 3 fois
for x in xrange(3):
response = None
try:
if payload:
response = requests.post(url, headers=self.mode.headers, data=payload)
else:
response = requests.get(url, headers=self.mode.headers)
assert response.status_code == 200, response.status_code
print 'API_Access: {}'.format(url)
data = ujson.loads(response.text)
self.check_json(data)
return data
except Exception as e:
if response is None:
raise
if response.text:
if str("Service Unavailable") in str(response.text):
raise OandaServiceUnavailableError
if str("An internal server error occurred") in str(response.text):
raise OandaInternalServerError
time.sleep(3)
if x >= 2:
raise ValueError, response.text
raise
def check_json(self, data):
raise NotImplementedError
class PriceAPI(OandaAPIBase):
"""
API de confirmation de tarif
%2C est une virgule
curl -H "Authorization: Bearer ********" -X GET "https://api-fxtrade.oanda.com/v1/prices?instruments=EUR_USD%2CUSD_JPY%2CEUR_CAD"
{
"prices" : [
{
"instrument" : "EUR_USD",
"time" : "2015-03-13T20:59:58.165668Z",
"bid" : 1.04927,
"ask" : 1.04993,
"status" : "halted"
},
{
"instrument" : "USD_JPY",
"time" : "2015-03-13T20:59:58.167818Z",
"bid" : 121.336,
"ask" : 121.433,
"status" : "halted"
},
{
"instrument" : "EUR_CAD",
"time" : "2015-03-13T20:59:58.165465Z",
"bid" : 1.34099,
"ask" : 1.34225,
"status" : "halted"
}
]
}
:rtype : dict of PriceAPIModel
"""
url_base = '{}v1/prices?'
def get_all(self):
instruments = ','.join([x.name for x in CurrencyPair])
instruments = urllib.quote(instruments, '')
url = self.url_base.format(self.mode.url_base)
url += 'instruments={}'.format(instruments)
data = self.requests_api(url)
d = {}
for price in data.get('prices'):
price_model = PriceAPIModel(price)
d[price_model.currency_pair] = price_model
return d
def check_json(self, data):
assert 'prices' in data
class PriceAPIModel(object):
"""
Classe qui analyse l'API de confirmation de taux
"""
instrument = None
time = None
bid = None #Cochez au moment de VENDRE
ask = None #Cochez au moment de ACHETER
status = None
def __init__(self, price):
self.ask = float(price.get('ask'))
self.bid = float(price.get('bid'))
self.time = parse_time(price.get('time'))
self.instrument = str(price.get('instrument'))
if 'status' in price:
self.status = str(price.get('status'))
self._check(price)
def _check(self, price):
if 'ask' not in price:
raise ValueError
if 'bid' not in price:
raise ValueError
if 'time' not in price:
raise ValueError
if 'instrument' not in price:
raise ValueError
self.currency_pair
@property
def currency_pair(self):
"""
:rtype : CurrencyPair
"""
for pair in CurrencyPair:
if str(pair.name) == str(self.instrument):
return pair
raise ValueError
@property
def is_maintenance(self):
"""
Vrai si pendant la maintenance
:rtype : bool
"""
if self.status is None:
return False
if str(self.status) == str('halted'):
return True
return False
def is_active(self):
"""
Vrai si tarif valide
:rtype : bool
"""
#Pas en maintenance
if self.is_maintenance:
return False
#Permet jusqu'à 4 ticks taux normal
if self.cost_tick > 4:
return False
return True
def get_password(key):
"""
Répondre mot de passe
"""
return "12345"
def parse_time(time_text):
"""
Convertissez une chaîne en UTC.
Exemple)
2015-02-22T15:00:00.000000Z
:param time_text: char
:rtype : datetime
"""
import datetime
t = time_text
utc = datetime.datetime(int(t[0:4]),
int(t[5:7]),
int(t[8:10]),
int(t[11:13]),
int(t[14:16]),
int(t[17:19]),
int(t[20:26]), tzinfo=pytz.utc)
return utc
def utc_to_jst(utc):
return utc.astimezone(pytz.timezone('Asia/Tokyo'))
print "START"
# API_Access
price_group_dict = PriceAPI(OandaAPIMode.SANDBOX).get_all()
#Imprimer le résultat
for key in price_group_dict:
pair = key
price = price_group_dict[key]
print "Pair:{} bid:{} ask:{} time:{}".format(pair.name, price.bid, price.ask, price.time)
print "FINISH"
Résultat d'exécution
>python price_api.py
START
API_Access: http://api-sandbox.oanda.com/v1/prices?instruments=EUR_USD%2CUSD_JPY%2CGBP_USD%2CAUD_USD
Pair:USD_JPY bid:120.243 ask:120.293 time:2015-09-23 21:07:30.363800+00:00
Pair:AUD_USD bid:0.70049 ask:0.70066 time:2015-09-23 21:07:30.367431+00:00
Pair:EUR_USD bid:1.13916 ask:1.1393 time:2015-10-13 07:16:53.931425+00:00
Pair:GBP_USD bid:1.52455 ask:1.5248 time:2015-09-23 21:07:30.362069+00:00
FINISH
Des erreurs inattendues se produisent souvent dans les systèmes de production. Puisque l'API Oanda est simple, je pense qu'il sera plus facile d'écrire la bibliothèque vous-même plus tard. Si vous souhaitez utiliser la bibliothèque, vous devez utiliser la bibliothèque en considérant que la réponse de l'API Saturday-Sunday s'arrêtera.
Lorsque 100 AI achète et vend des yens en dollars au moment où la barre de 5 minutes est mise à jour, si l'achat est de 60 et la vente est de 40, les 40 pièces qui se chevauchent seront à deux étages et seront perdues dans les frais. Pour contrer le problème du double étage, nous optimiserons l'achat et la vente en gros.
■ Exemple de cas Minimisez les frais de négociation lorsque AI1-6 passe un ordre comme suit. On suppose que les frais de transaction sont de 100 yens.
AI Name | Order | bid | Limit | StopLimit |
---|---|---|---|---|
AI.001 | Buy | 120.00 | 120.50 | 119.50 |
AI.002 | Buy | 120.00 | 120.70 | 119.50 |
AI.003 | Buy | 120.00 | 120.50 | 119.50 |
AI.004 | Buy | 120.00 | 120.50 | 119.00 |
AI.005 | Sell | 120.00 | 119.00 | 120.50 |
AI.006 | Sell | 120.00 | 118.50 | 120.50 |
■ Méthode 1. À 120,00 yens, passez une commande pour 4 achats et 2 ventes (frais de négociation 600 yens) Après cela, le prix du marché fluctue et les 6 ordres sont épuisés (frais de négociation 600 yens) 1200 yens au total
■ Méthode 2. À 120,00 yens, regroupez les commandes pour AI1 à 6 et passez une commande pour 2 dollars yens 120,00 Acheter (frais d'achat et de vente 200 yens) Après cela, le prix du marché fluctue et les 6 ordres sont épuisés (frais de négociation 600 yens) 800 yens au total
Oanda est un système à deux étages qui n'a pas de position, j'ai donc décidé d'acheter et de vendre en utilisant la solution 2.
Dans la méthode de la solution 1, vous pouvez définir la limite et la limite d'arrêt pour chaque commande et passer une commande, donc une fois que vous passez la commande, Oanda réglera automatiquement le paiement, mais si vous utilisez la méthode de la solution 2, l'unité de commande sera différente, alors commandez Vous devez également passer une commande de paiement à partir de votre propre programme.
Que faire si le programme de paiement s'arrête avec une erreur? Le système continue de passer des commandes, mais la position n'est pas clôturée. L'effet de levier des comptes continue d'augmenter. Pour éviter que cela ne se produise, le programme d'achat et de vente doit être hautement disponible. J'ai également installé Fonction de notification d'appel lorsqu'une erreur se produit.
Pour atteindre une haute disponibilité, il est facile de traiter le programme avec superviseur. Créez des commandes d'ouverture et de fermeture pour les positions (ordres d'achat et de vente) en Python et utilisez Supervisord pour lancer le processus. Des fonctions similaires peuvent être obtenues avec Cron et while True, mais elles présentent de nombreux inconvénients et n'ont pas été adoptées.
méthode | Démérite |
---|---|
cron | La réexécution immédiate n'est pas possible après la fin de la commande |
while True | Une fuite de mémoire se produit, l'erreur ne peut pas être automatiquement récupérée lorsqu'elle est supprimée |
supervisord | Tracas à installer, tracas à déployer |
order_open.py
# -*- coding: utf-8 -*-
#C'est un pseudo-langage parce qu'il est habilité. Peut-être que ça ne marche pas. Pardon
from __future__ import absolute_import
from __future__ import unicode_literals
import datetime
from django.core.management import BaseCommand
import time
import sys
import pytz
from requests import ConnectionError
ACCOUNT_ID = 12345
class CustomBaseCommand(BaseCommand):
class Meta(object):
abstract = True
def echo(self, txt):
d = datetime.datetime.today()
print d.strftime("%Y-%m-%d %H:%M:%S") + ':' + txt
class Command(CustomBaseCommand):
"""
Passer une commande avec BoardAI
"""
CACHE_PREV_RATES = {}
def handle(self, *args, **options):
try:
self.run()
except OandaServiceUnavailableError:
#Pendant la maintenance du samedi et du dimanche
self.echo("ServiceUnavailableError")
time.sleep(60)
except OandaInternalServerError:
#Pendant la maintenance du samedi et du dimanche
self.echo("OandaInternalServerError")
time.sleep(60)
except ConnectionError:
time.sleep(60)
#Arrêtez-vous pendant 3 secondes
time.sleep(3)
def run(self):
#Vérification de la marge
account = AccountAPI(OandaAPIMode.PRODUCTION, ACCOUNT_ID).get_all()
if int(account.get('marginAvail')) < 10000:
self.echo('MARGIN EMPTY!! marginAvail:{}'.format(int(account.get('marginAvail'))))
time.sleep(3000)
#prendre le prix
price_group = PriceAPI(OandaAPIMode.PRODUCTION).get_all()
#Génération d'instances IA
order_group = []
ai_board_group = AIBoard.get_all()
#Commande temporaire
now = datetime.datetime.now(tz=pytz.utc)
for ai_board in ai_board_group:
order = self.pre_order(ai_board, price_group, now)
if order:
order_group.append(order)
#Commande
self.order(order_group, price_group)
def pre_order(self, ai_board, price_group, now):
"""
Retourner True après la commande
:param ai_board: AIBoard
:param price_group: dict of PriceAPIModel
:param now: datetime
:rtype : bool
"""
ai = ai_board.get_ai_instance()
price = price_group.get(ai.currency_pair, None)
#Le prix est-il normal?
if price is None:
return None
if not price.is_active():
# print 'price not active'
return None
#Le taux n'est pas normal
prev_rates = self.get_prev_rates(ai.currency_pair, Granularity.H1)
if not prev_rates:
return None
prev_rate = prev_rates[-1]
#Écart par rapport au taux précédent dans les 3 minutes
if now - prev_rate.start_at > datetime.timedelta(seconds=60 * 3):
# print 'ORDER TIME IS OVER'
return None
#Limite d'achat par nombre de positions et limite d'achat par temps
if not ai_board.can_order(prev_rate):
# print 'TIME OR POSITION LIMIT'
return None
#Décision d'achat Similaire à la simulation d'IA, l'achat et la vente ne
order_ai = ai.get_order_ai(prev_rates, price.mid, price.time, is_production=True)
if order_ai is None:
return None
if order_ai.order_type == OrderType.WAIT:
return None
#Tir d'ordre temporaire
order = Order.pre_open(ai_board, order_ai, price, prev_rate.start_at)
return order
def order(self, order_group, price_group):
"""
Prenez un récapitulatif de la précommande et passez effectivement la commande
:param order_group: list of Order
:param price_group: dict of PriceAPIModel
:return:
"""
#Prenez un résumé
order_dict = {x: 0 for x in CurrencyPair}
for order in order_group:
if order.buy:
order_dict[order.currency_pair] += order.units
else:
order_dict[order.currency_pair] -= order.units
print order_dict
#Commande
api_response_dict = {}
for key in order_dict:
if order_dict[key] == 0:
print '{}:NO ORDER'.format(key)
continue
print '{}:ORDER!'.format(key)
units = order_dict[key]
api_response_dict[key] = OrdersAPI(OandaAPIMode.PRODUCTION, ACCOUNT_ID).post(key, units, tag='open')
#Mise à jour de la base de données
for order in order_group:
order.open(price_group.get(order.currency_pair))
def get_prev_rates(self, currency_pair, granularity):
key = self._get_key(currency_pair, granularity)
print key
r = self.CACHE_PREV_RATES.get(key)
if r:
return r
prev_rates = CurrencyPairToTable.get_table(currency_pair, granularity).get_new_record_by_count(10000)
self.CACHE_PREV_RATES[key] = prev_rates
return prev_rates
def _get_key(self, currency_pair, granularity):
return 'RATE:{}:{}'.format(currency_pair.value, granularity.value)
order_close.py
# -*- coding: utf-8 -*-
#C'est un pseudo-langage parce qu'il est habilité. Peut-être que ça ne marche pas. Pardon
from __future__ import absolute_import
from __future__ import unicode_literals
import datetime
from django.core.management import BaseCommand
import time
import pytz
from requests import ConnectionError
class Command(CustomBaseCommand):
"""
Fermer à l'aide de l'historique du compte
"""
CACHE_PREV_RATES = {}
def handle(self, *args, **options):
print '********************************'
self.echo('close start')
self.check_kill_switch()
try:
self.run()
except OandaServiceUnavailableError:
#Pendant la maintenance du samedi et du dimanche
self.echo("ServiceUnavailableError")
time.sleep(60)
except OandaInternalServerError:
#Pendant la maintenance du samedi et du dimanche
self.echo("OandaInternalServerError")
time.sleep(60)
except ConnectionError:
time.sleep(60)
except Exception as e:
self.critical_error('close', e)
time.sleep(3)
def run(self):
orders = Order.get_open()
#prendre le prix
price_group = PriceAPI(OandaAPIMode.PRODUCTION).get_all()
#Prenez un résumé
order_dict = {x: 0 for x in CurrencyPair}
for order in orders:
if order.can_close(price_group[order.currency_pair]):
if order.buy:
#Achat et vente inversés
print order.currency_pair, order.units
order_dict[order.currency_pair] -= order.units
print order_dict[order.currency_pair]
else:
#Achat et vente inversés
print order.currency_pair, order.units
order_dict[order.currency_pair] += order.units
print order_dict[order.currency_pair]
print order_dict
#Commande
api_response_dict = {}
for key in order_dict:
if order_dict[key] == 0:
print '{}:NO ORDER'.format(key)
continue
print '{}:ORDER'.format(key)
units = order_dict[key]
api_response_dict[key] = OrdersAPI(OandaAPIMode.PRODUCTION, 12345).post(key, units, tag='close')
#Record
for order in orders:
if order.can_close(price_group[order.currency_pair]):
order.close(price_group.get(order.currency_pair))
J'ai essayé de construire un cluster informatique d'algorithmes génétiques avec mes spécifications. L'ouvrier fait une commande pour calculer avec un algorithme génétique en python et le lance avec supervisord.
Avez-vous fait un profit du trading automatique après tout?
Mois | Bénéfice commercial | Frais de maintenance du serveur |
---|---|---|
Mars 2015 | +50 000 | 10 000 |
Avril 2015 | +20 000 | 10 000 |
Mai 2015 | -70 000 | 10 000 |
Juin 2015 | -50 000 | 10 000 |
Juillet 2015 | Arrêtez le système de trading automatique et l'ETF:Montant total en 1557 | - |
Merci de rester avec nous jusqu'à la fin. Le développement du système de trading automatique était très intéressant. Essayez-le.
Système de trading automatique FX réalisé avec python et algorithme génétique Partie 1 Trading automatique FX avec algorithme génétique Partie 2 Implémentation de l'IA de trading en évolution Trading automatique FX avec algorithme génétique Partie 3 Trading réel avec l'API Oanda
Recommended Posts