Automatisches FX-Handelssystem mit Python und genetischem Algorithmus Teil 1 Automatischer FX-Handel mit genetischem Algorithmus Teil 2 Implementierung der sich entwickelnden Handels-KI
Ich möchte über die Oanda-API schreiben, die ich beim Erstellen des automatischen Handelssystems und der Funktion zum Platzieren von Kauf- und Verkaufsaufträgen verwendet habe.
Es wurde mit 5 Arten von APIs erstellt. Weitere Informationen finden Sie unter Oandas API-Dokument. OAuth2-Token können im Web ausgestellt werden, indem Sie ein Konto eröffnen und sich anmelden. Setzen Sie das Token im Curl-Befehl Header auf Authorization: Bearer ********.
"""
01.Kontoinformations-API
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.Bestell-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.Holen Sie sich die aktuelle Positionsliste
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.Marktpreis erhalten
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 zur Überprüfung des Bestellstatus von Kauf- und Verkaufsaufträgen
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
}
Dies ist ein Implementierungsbeispiel für die Preiserfassungs-API. Webzugriff mit urllib-Anfrage. Die Antwort wird in Json zurückgegeben, daher werde ich mein Bestes geben, um die Parser-Klasse zu schreiben. Wenn sich der Spread öffnet (der Markt wird rau und die Differenz zwischen Kauf- und Verkaufspreis öffnet sich), habe ich versucht, eine Funktion zu integrieren, die keine Bestellung aufgibt, wenn der Kurs ungünstig ist.
■ Beispielcode für den Zugriff auf die Sandbox-API 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):
"""
Fehler beim Servicestopp
"""
pass
class OandaInternalServerError(Exception):
"""
Fehler beim Servicestopp
"""
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):
#Wenn die Kommunikation fehlschlägt, versuchen Sie es bis zu dreimal
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):
"""
Ratenbestätigungs-API
%2C ist ein Komma
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):
"""
Klasse, die die Ratenbestätigungs-API analysiert
"""
instrument = None
time = None
bid = None #Häkchen zum Zeitpunkt des VERKAUFS
ask = None #Häkchen zum Zeitpunkt des KAUFENS
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):
"""
Richtig, wenn während der Wartung
:rtype : bool
"""
if self.status is None:
return False
if str(self.status) == str('halted'):
return True
return False
def is_active(self):
"""
True wenn gültige Rate
:rtype : bool
"""
#Nicht in Wartung
if self.is_maintenance:
return False
#Ermöglicht bis zu 4 Ticks normale Rate
if self.cost_tick > 4:
return False
return True
def get_password(key):
"""
Passwort beantworten
"""
return "12345"
def parse_time(time_text):
"""
Konvertieren Sie eine Zeichenfolge in UTC.
Beispiel)
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()
#Drucken Sie das Ergebnis
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"
Ausführungsergebnis
>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
In Produktionssystemen treten häufig unerwartete Fehler auf. Da die Oanda-API einfach ist, wird es meiner Meinung nach später einfacher sein, die Bibliothek selbst zu schreiben. Wenn Sie die Bibliothek verwenden möchten, sollten Sie die Bibliothek verwenden, da die Antwort der Samstag- und Sonntag-API stoppt.
Wenn 100 AI zu dem Zeitpunkt, zu dem der 5-Minuten-Balken aktualisiert wird, Dollar-Yen kauft und verkauft, wenn Kauf 60 und Verkauf 40 sind, sind die überlappenden 40 Teile Doppeldecker und gehen in der Gebühr verloren. Als Gegenmaßnahme für das Doppeldeckerproblem werden wir den Kauf und Verkauf in Großbestellungen optimieren.
■ Beispielfall Minimieren Sie die Handelsgebühren, wenn AI1-6 eine Bestellung wie folgt aufgibt. Es wird angenommen, dass eine Transaktionsgebühr 100 Yen beträgt.
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 |
■ Methode 1. Geben Sie bei 120,00 Yen eine Bestellung für 4 Kauf und 2 Verkauf auf (Handelsgebühr 600 Yen) Danach schwankt der Marktpreis und alle 6 Aufträge sind erschöpft (Handelsgebühr 600 Yen) Insgesamt 1200 Yen
■ Methode 2. Stellen Sie bei 120,00 Yen Bestellungen für AI1 bis 6 zusammen und bestellen Sie 2 Dollar Yen 120,00 Kaufen (Kauf- und Verkaufsgebühr 200 Yen) Danach schwankt der Marktpreis und alle 6 Aufträge sind erschöpft (Handelsgebühr 600 Yen) Insgesamt 800 Yen
Oanda ist ein Doppeldeckersystem, das keine Position hat, daher habe ich beschlossen, mit der Lösung 2 zu kaufen und zu verkaufen.
Bei der Methode von Lösung 1 können Sie für jede Bestellung ein Limit und ein Stop-Limit festlegen und eine Bestellung aufgeben. Sobald Sie die Bestellung aufgeben, wird die Zahlung von Oanda automatisch abgerechnet. Wenn Sie jedoch die Methode von Lösung 2 verwenden, ist die Bestelleinheit unterschiedlich Sie müssen auch eine Bestellung für die Zahlung aus Ihrem eigenen Programm aufgeben.
Was ist, wenn das Zahlungsprogramm mit einem Fehler beendet wird? Das System gibt weiterhin Bestellungen auf, aber die Position ist nicht geschlossen. Die Hebelwirkung der Konten steigt weiter an. Um dies zu verhindern, muss das Kauf- und Verkaufsprogramm hoch verfügbar sein. Ich habe auch Benachrichtigungsfunktion anrufen, wenn ein Fehler auftritt installiert.
Um eine hohe Verfügbarkeit zu erreichen, ist es einfach, das Programm mit Supervisord zu verarbeiten. Erstellen Sie Öffnungs- und Schließbefehle für Positionen (Kauf- und Verkaufsaufträge) in Python und starten Sie den Prozess mit Supervisord. Ähnliche Funktionen können mit Cron und zwar True erreicht werden, sie haben jedoch viele Nachteile und wurden nicht übernommen.
Methode | Fehler |
---|---|
cron | Eine sofortige erneute Ausführung ist nach Beendigung des Befehls nicht möglich |
while True | Ein Speicherverlust tritt auf. Der Fehler kann beim Löschen nicht automatisch behoben werden |
supervisord | Problematisch zu installieren, mühsam zu implementieren |
order_open.py
# -*- coding: utf-8 -*-
#Es ist eine Pseudosprache, weil sie befähigt ist. Vielleicht funktioniert es nicht. Es tut uns leid
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):
"""
Bestellen Sie mit BoardAI
"""
CACHE_PREV_RATES = {}
def handle(self, *args, **options):
try:
self.run()
except OandaServiceUnavailableError:
#Während der Wartung am Samstag und Sonntag
self.echo("ServiceUnavailableError")
time.sleep(60)
except OandaInternalServerError:
#Während der Wartung am Samstag und Sonntag
self.echo("OandaInternalServerError")
time.sleep(60)
except ConnectionError:
time.sleep(60)
#3 Sekunden lang anhalten
time.sleep(3)
def run(self):
#Margin Check
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)
#Preis nehmen
price_group = PriceAPI(OandaAPIMode.PRODUCTION).get_all()
#Generierung von KI-Instanzen
order_group = []
ai_board_group = AIBoard.get_all()
#Befristete Bestellung
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)
#Auftrag
self.order(order_group, price_group)
def pre_order(self, ai_board, price_group, now):
"""
Geben Sie nach der Bestellung True zurück
: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)
#Ist der Preis normal?
if price is None:
return None
if not price.is_active():
# print 'price not active'
return None
#Rate ist nicht normal
prev_rates = self.get_prev_rates(ai.currency_pair, Granularity.H1)
if not prev_rates:
return None
prev_rate = prev_rates[-1]
#Abweichung von der vorherigen Rate innerhalb von 3 Minuten
if now - prev_rate.start_at > datetime.timedelta(seconds=60 * 3):
# print 'ORDER TIME IS OVER'
return None
#Kauflimit nach Anzahl der Positionen und Kauflimit nach Zeit
if not ai_board.can_order(prev_rate):
# print 'TIME OR POSITION LIMIT'
return None
#Kaufentscheidung Ähnlich wie bei der KI-Simulation wird der Kauf und Verkauf nur für die Mitte entschieden
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
#Vorübergehende Auftragsentlassung
order = Order.pre_open(ai_board, order_ai, price, prev_rate.start_at)
return order
def order(self, order_group, price_group):
"""
Machen Sie eine Zusammenfassung der Vorbestellung und geben Sie die Bestellung tatsächlich auf
:param order_group: list of Order
:param price_group: dict of PriceAPIModel
:return:
"""
#Machen Sie eine Zusammenfassung
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
#Auftrag
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')
#DB-Update
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 -*-
#Es ist eine Pseudosprache, weil sie befähigt ist. Vielleicht funktioniert es nicht. Es tut uns leid
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):
"""
Schließen Sie mit dem Kontoverlauf
"""
CACHE_PREV_RATES = {}
def handle(self, *args, **options):
print '********************************'
self.echo('close start')
self.check_kill_switch()
try:
self.run()
except OandaServiceUnavailableError:
#Während der Wartung am Samstag und Sonntag
self.echo("ServiceUnavailableError")
time.sleep(60)
except OandaInternalServerError:
#Während der Wartung am Samstag und Sonntag
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()
#Preis nehmen
price_group = PriceAPI(OandaAPIMode.PRODUCTION).get_all()
#Machen Sie eine Zusammenfassung
order_dict = {x: 0 for x in CurrencyPair}
for order in orders:
if order.can_close(price_group[order.currency_pair]):
if order.buy:
#Reverse Kauf und Verkauf
print order.currency_pair, order.units
order_dict[order.currency_pair] -= order.units
print order_dict[order.currency_pair]
else:
#Reverse Kauf und Verkauf
print order.currency_pair, order.units
order_dict[order.currency_pair] += order.units
print order_dict[order.currency_pair]
print order_dict
#Auftrag
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')
#Aufzeichnung
for order in orders:
if order.can_close(price_group[order.currency_pair]):
order.close(price_group.get(order.currency_pair))
Ich habe versucht, mit meinen Spezifikationen einen Computercluster genetischer Algorithmen aufzubauen. Der Worker erstellt einen Befehl zum Berechnen mit einem genetischen Algorithmus in Python und startet ihn mit Supervisord.
Haben Sie doch vom automatischen Handel profitiert?
Mond | Handelsergebnis | Serverwartungsgebühr |
---|---|---|
März 2015 | +50 000 | 10 000 |
April 2015 | +20.000 | 10 000 |
Mai 2015 | -70.000 | 10 000 |
Juni 2015 | -50 000 | 10 000 |
Juli 2015 | Stoppen Sie das automatische Handelssystem und den ETF:Voller Betrag im Jahre 1557 | - |
Vielen Dank, dass Sie bis zum Ende bei uns bleiben. Die Entwicklung des automatischen Handelssystems war sehr interessant. Versuch es bitte.
Automatisches FX-Handelssystem mit Python und genetischem Algorithmus Teil 1 Automatischer FX-Handel mit genetischem Algorithmus Teil 2 Implementierung der sich entwickelnden Handels-KI FX automatischer Handel mit genetischem Algorithmus Teil 3 Tatsächlicher Handel mit Oanda API
Recommended Posts