通常、取引ロボットの作成はかなり時間のかかるタスクです-取引の原理を理解する(およびこの戦略またはその戦略がどのように見えるかを理解する)ことに加えて、取引に使用されるプロトコルを知って、それを操作できる必要があります。 要するに、取引所またはブローカーによって提供される2つの主要なプロトコルグループがあります。FIXはボトルなしでは理解できず、独自のバイナリプロトコルはめったに優れていません。 これは、2つの問題のうちの1つにつながります。コードが後輩に頭を抱えているように見えるか、ほぼ何でもできる(そしてさまざまな予期しない問題でできること)良い、美しいコードです。
上記の問題を解決し、できるだけ多くの参加者を引き付けるために、ブローカーは通常、json / xml /よりエキゾチックなものでシリアル化された通常のHTTP APIを提示します。 特に、交換機と通信するこの方法は、ビットコイン交換などの多くのファッショナブルなスタートアップにとってほとんど唯一の方法です。 私たちはそれらに追いつくことに決め、最近APIに追加を導入しました (Habréの古い機能についてはこちらとこちらをご覧ください )。
カットの下では、これはHTTP APIを介した取引方法に関する金曜日のチュートリアル記事ではありません。
初心者の方は、 こちらを読むことをお勧めします。
グリッド戦略で取引するロボットを実装します。 次のようになります。
- 価格ステップ(グリッド)
step
と1つの注文size
量を選択しsize
。 - 現在の価格を保存します。
- 新しい価格を取得し、保存されている価格と比較します。
- 価格が
step
未満で変化した場合は、step
3に戻ります。 - 価格が
step
超えて変化した場合:
a。 価格が上昇した場合、販売するsize
の量をリクエストします。
b。 減少した場合-同じ金額で購入した場合。 - アイテム2に戻ります。
明らかにビットコインのチャートでは、戦略は次のとおりです。
プログラミング言語の代わりに、Pythonを選択してください。これは、いくつかのものを扱うのが簡単であり、開発の速度が速いためです。 ロボットをテストするための誇大広告に続いて、暗号通貨を使用しLTC.EXANTE
(LTC.EXANTE LTC.EXANTE
(ビットコインにお金がないため))。
ログイン
以前と同様に、 https://developers.exante.euにアカウントが必要です(ちなみに、GitHub経由でログインすることもできます)。 古いガイドとの唯一の違いは、取引のために、新しく作成したユーザーで個人アカウントにログインする必要がある取引アカウントを作成する必要があることです。
今回は、ロボットを認証するためにjwt.ioの周りでタンバリンと踊る必要はありません-アプリケーションは開発者のコンピューター/サーバーで起動されるため、トークンの形で追加のセキュリティレベル(および困難)を挿入する必要はありません。 代わりに、通常のhttp基本認証を使用します。
結果のアプリケーションIDはユーザー名であり、アクセスキーの[値]列は実際にはパスワードです。
引用符を取得する
ロボットはいつ、どのように取引するかを知る必要があるため、再び市場データを取得する必要があります。 これを行うには、小さなクラスを作成します。
class FeedAdapter(threading.Thread): def __init__(self, instrument: str, auth: requests.auth.HTTPBasicAuth): super(FeedAdapter, self).__init__() self.daemon = True self.__auth = auth self.__stream_url = 'https://api-demo.exante.eu/md/1.0/feed/{}'.format( urllib.parse.quote_plus(instrument))
たとえば、スラッシュ/
( EUR/USD.E.FX
)を含めることができるため、楽器の名前をエンコードする必要があることを思い出します。 実際にデータを取得するには、ジェネレーターメソッドを記述します。
def __get_stream(self) -> iter: response = requests.get( self.__stream_url, auth=self.__auth, stream=True, timeout=60, headers={'accept': 'application/x-json-stream'}) return response.iter_lines(chunk_size=1) def run(self) -> iter: while True: try: for item in self.__get_stream(): # data = json.loads(item.decode('utf8')) # , API # . event # , - # {timestamp, symbolId, bid, ask} if 'event' in data: continue # yield data # except requests.exceptions.Timeout: print('Timeout reached') except requests.exceptions.ChunkedEncodingError: print('Chunk read failed') except requests.ConnectionError: print('Connection error') except socket.error: print('Socket error') time.sleep(60)
取引セッションアダプター
取引するには、標準的な知識(金融商品、注文サイズと価格、注文の種類)に加えて、アカウントを知る必要があります。 残念ながら、これを行うには、アカウントにログインして、 ブラウザ取引プラットフォームを試す必要があります 。 幸いなことに、将来的にはAPIが完成します-キャッシュデスクを離れることなく、ユーザーに関する情報(取引口座を含む)を見つけることが可能になります。 アカウントは右上隅にあり、ABC1234.001と入力します。
class BrokerAdapter(threading.Thread): def __init__(self, account: str, interval: int, auth: requests.auth.HTTPBasicAuth): super(BrokerAdapter, self).__init__() self.__lock = threading.Lock() self.daemon = True self.__interval = interval self.__url = 'https://api-demo.exante.eu/trade/1.0/orders' self.__account = account self.__auth = auth # self.__orders = dict()
お気づきかもしれませんが、注文して市場データを取得するためのプレフィックスは異なります- /trade/1.0
と/md/1.0
です。 ここでのinterval
は、サーバーからの要求に関するデータの要求の間隔を示すために使用されます(禁止を避けるために小さすぎる設定はお勧めしません):
def order(self, order_id: str) -> dict: response = requests.get(self.__url + '/' + order_id, auth=self.__auth) if response.ok: return response.json() return dict()
回答のフィールドについて詳しくはこちらをご覧ください 。 計算のためのフィールドは、 orderParameters.side
、 orderState.fills[].quantity
orderParameters.side
およびorderState.fills[].quantity
orderParameters.side
のみorderState.fills[].price
損失の 利益。
サーバーのアプリケーションを配置する方法:
def place_limit(self, instrument: str, side: str, quantity: int, price: float, duration: str='good_till_cancel') -> dict: response = requests.post(self.__url, json={ 'account': self.__account, 'duration': duration, 'instrument': instrument, 'orderType': 'limit', 'quantity': quantity, 'limitPrice': price, 'side': side }, auth=self.__auth) try: # , ID return response.json()['id'] except KeyError: # - print('Could not place order') return response.json() except Exception: # , print('Unexpected error occurs while placing order') return dict()
このコードセクションには、2つの新しいあいまいなフレーズが含まれています。
-
{'orderType': 'limit'}
は、いわゆる指値注文を出すことを意味します。これにより、{'orderType': 'limit'}
ならないようにします(制限とは異なり)。価格。 -
{'duration': 'good_till_cancel'}
は注文の有効期間を意味します。この場合、トレーダーが退屈する(または何かが壊れる)までです。
アプリケーションのウォッチドッグ
無限ループで機能し、作業の結果を標準出力にダンプします。
def run(self) -> None: while True: with self.__lock: for order_id in self.__orders: state = self.order(order_id) # , if state == self.__orders[order_id]: continue print('Order {} state was changed'.format(order_id)) self.__orders[order_id] = state # , filled = sum( fill['quantity'] for fill in state['orderState']['fills'] ) avg_price = sum( fill['price'] for fill in state['orderState']['fills'] ) / filled print( 'Order {} with side {} has price {} (filled {})'.format( order_id, state['orderParameters']['side'], avg_price, filled )) # time.sleep(self.__interval) # / watchdog def add_order(self, order_id: str) -> None: with self.__lock: if order_id in self.__orders: return self.__orders[order_id] = dict() def remove_order(self, order_id: str) -> None: with self.__lock: try: del self.__orders[order_id] except KeyError: pass
戦略の実施
お気づきかもしれませんが、私たちは最も興味深いもの、つまりトレーディング戦略の実装に到達していません。 次のようになります。
class GridBrokerWorker(object): def __init__(self, account: str, interval: str, application: str, token: str): self.__account = account self.__interval = interval # self.__auth = requests.auth.HTTPBasicAuth(application, token) # - self.__broker = broker_adapter.BrokerAdapter( self.__account, self.__interval, self.__auth) self.__broker.start() def run(self, instrument, quantity, grid) -> None: # feed = feed_adapter.FeedAdapter(instrument, self.__auth) old_mid = None for quote in feed.run(): mid = (quote['bid'] + quote['ask']) / 2 # , if old_mid is None: old_mid = mid continue # , # , if abs(old_mid - mid) < grid: continue # , side = 'sell' if mid - old_mid > 0 else 'buy' # order_id = self.__broker.place_limit( instrument, side, str(quantity), str(mid)) # if not order_id: print('Unexpected error') continue # elif not isinstance(order_id, str): print('Unexpected error: {}'.format(order_id)) continue # ! watchdog... self.__broker.add_order(order_id) # ... old_mid = mid
起動とデバッグ
# worker = GridBrokerWorker('ABC1234.001', 60, 'appid', 'token') # worker.run('LTC.EXANTE', 100, 0.1)
将来的には、ロボットがまったく取引できるようにするために、選択した金融商品の市場の変動に応じてgrid
パラメーターを調整します。 また、この戦略は外国為替以外にはほとんど使用されないことに注意してください。 それにもかかわらず、私たちのロボットは準備ができています。
既知の問題
- ロボットはかなり愚かで、パラメータを事前に修正して同じ戦略で取引する以外には何もできません...
- ...そして、それをひどく行うことができ、例外で落ちる...
- ...そして、それが壊れないとき、それはゆっくり働きます。
-
double
型の数値の表現には問題があります。double
をDecimal
置き換えると、ここで役立ちます。 - PnLなど、トレーダーにとって重要な値の計算はありません。
結論の代わりに
この例に特化したGitHubのリポジトリにある多くの問題を考慮に入れました。 リポジトリ内のコードは、ドキュメント化され、MITライセンスの下で公開される場合があります。 以下は、ロボットの動作を示す短いビデオです。