pythonと遺伝的アルゴリズムで作るFX自動売買システム その1

  • 1322
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

作ったモノ

次の機能を実装してみました
1. 最新の為替レートを取得し続けるプログラム
2. AIを稼働させ自動売買するプログラム
3. 最新のデータを元に新しいAIを遺伝的アルゴリズムで生成するプログラム
4. AIのパフォーマンスを測定して引退と取引通貨単位を管理するプログラム

背景

OANDAが提供している取引用APIが、かなり良い感じだったので実現できました。
特に1通貨単位(1ドル単位)で売買できるため、AI100個動かし取引を重ねても損失は1日数十円に収まります。試験時に売買システムがバグで暴走しても安心です。このAPIが無ければ個人では実現出来なかったので、良い時代になったなーと思います。
http://developer.oanda.com/rest-live/development-guide/

遺伝的アルゴリズムの特徴

最適化問題の準最適解を短時間で解ける。
最適化問題や準最適解ってなんじゃらほいって時は、遺伝的アルゴリズムでマリオを攻略している動画を観るとなんとなく判るかもしれません。
【ニコ動】スーパーマリオブラザーズを学習させてみた

レートを取得する

curlでOandaAPI試しつつ、試し終わったらpythonで書いていきます

request
curl -X GET "https://api-sandbox.oanda.com/v1/candles?instrument=EUR_USD&count=100&candleFormat=midpoint&granularity=D&dailyAlignment=0&alignmentTimezone=America%2FNew_York"
response
{
    "instrument" : "EUR_USD",
    "granularity" : "D",
    "candles" : [
        {
            "time" : "2015-06-22T04:00:00.000000Z",
            "openMid" : 1.24121,
            "highMid" : 1.241535,
            "lowMid" : 1.240145,
            "closeMid" : 1.24113,
            "volume" : 24144,
            "complete" : true
        }
    ]
}
レート取得pythonコード
base_domain = MODE.get('production')
url_base = 'https://{}/v1/candles?'.format(base_domain)
url = url_base + 'instrument={}&'.format(currency_pair.name) + \
    'count=5000&' +\
    'candleFormat=midpoint&' +\
    'granularity={}&'.format(granularity.name) +\
    'dailyAlignment=0&' +\
    'alignmentTimezone=Asia%2FTokyo&' +\
    'start={}T00%3A00%3A00Z'.format(start)

response = requests_api(url)

def requests_api(url, payload=None):
    auth = 'Bearer {}'.format(get_password('OandaRestAPIToken'))
    headers = {'Accept-Encoding': 'identity, deflate, compress, gzip',
               'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0',
               'Content-type': 'application/json; charset=utf-8',
               'Authorization': auth}
    if payload:
        requests.adapters.DEFAULT_RETRIES = 2
        response = requests.post(url, headers=headers, data=payload, timeout=10)
    else:
        requests.adapters.DEFAULT_RETRIES = 2
        response = requests.get(url, headers=headers, timeout=10)
    print 'REQUEST_API: {}'.format(url)
    return response

簡単な売買AIを作ってみよう

■ 仕様
現在レートと24時間平均レートを比較し、30pip以上乖離している場合は順張りで利確と損切りを30pipで設定して売買を実施する.

■ 動作例
通貨:ドル円
現在時刻: 2015年10月1日 10:00:00
現在レート: 120.00円
24時間平均レート: 119.40円
購入通貨単位: 100
注文内容: 120円で100単位BUY、利益確定は120.30円、損切りは119.70円

code
# -*- coding: utf-8 -*-
import datetime
from enum import Enum


class OrderType(Enum):
    Buy = 1
    Wait = 0
    Sell = -1


class Rate(object):
    start_at = datetime.datetime(2015, 10, 1, 10, 0, 0)
    bid = float(120.00)
    h24 = float(119.40)
    base_tick = float(0.01)

    @classmethod
    def get(cls, currency=None, start_at=None):
        return Rate()

    def get_rate(self, tick, is_add):
        if is_add:
            return self.bid + float(tick * self.base_tick)
        else:
            return self.bid - float(tick * self.base_tick)


class AIOrder(object):
    def __init__(self, order_type, limit, stop_limit):
        self.order_type = order_type
        self.limit = limit
        self.stop_limit = stop_limit


class AI(object):
    DIFF = 30
    LIMIT = 30
    STOP_LIMIT = 30

    def __init__(self, rate):
        self.rate = rate

    def order(self):
        """
        発注する。
        """
        if self._can_order():
            order(self._get_order())

    def _can_order(self):
        """
        発注可能か返却
        rtype: bool
        """
        point = (self.rate.bid - self.rate.h24) / self.rate.base_tick
        return int(point / self.DIFF) not in [0]

    def _get_order(self):
        """
        発注クラスを返却
        rtype: AIOrder
        """
        limit_rate = self.rate.get_rate(AI.LIMIT, True)
        stop_limit_rate = self.rate.get_rate(AI.STOP_LIMIT, False)
        return AIOrder(self._get_order_type(), limit_rate, stop_limit_rate)

    def _get_order_type(self):
        if self.rate.bid > self.rate.h24:
            return OrderType.Buy
        if self.rate.h24 > self.rate.bid:
            return OrderType.Sell
        return OrderType.Wait


def order(ai_order):
    _base = "OrderDone:{}, Limit:{}, Stop-Limit:{}"
    print _base.format(ai_order.order_type,
                       ai_order.limit,
                       ai_order.stop_limit)

# レート取得
rate = Rate.get(currency='USDJPY', start_at=datetime.datetime.now())

# AI生成
ai = AI(rate)

# 発注
ai.order()

>>> OrderDone:True, Limit:120.3, Stop-Limit:119.7

売買AIをベンチマークしてみる

このAIが利益を出す優秀なAIかどうか、5分足のデータでベンチマークしてみましょう。優秀なAIの定義を次の通り定義してみました
1. 過去10年の相場にて取引回数が100回以上あること
2. 過去10年の相場にて利益が100万円以上であること
3. 過去10年の相場にて最大損失が50万円以下であること

過去に遡ったベンチマークのことをバックテストと言います。
またプログラム売買の場合、3の条件が重要視されます。というのも基本放置なので大きく儲かるけど、大きく損失もするプログラムは好まれません。3のことを最大ドローダウンと言います。

売買AIを遺伝的アルゴリズムで最適化する

売買AIは3つのパラメータで構成されています。この値を最適化してみます。
つまり売買AIが最適化問題として定義されたわけです。

class AI(object):
    # 発注を決定する要素、24h平均との差がこの値以上で発注する。
    DIFF = 30
    # 発注時の利益確定レートを決定する要素
    LIMIT = 30
    # 発注時の損切りレートを決定する要素
    STOP_LIMIT = 30

この3パラメータの上限と下限を400と10とすると、全パターンの計算回数は
390 ^ 3 = 59,319,000回です。
5分足で10年分のバックテストを行うと、pythonは計算が遅いので10秒くらい掛かります。CPU:8コアのパソコンで並列計算すると必要な計算時間は次の通りです。
59,319,000 / 8cpu * 10秒 / 3600秒 / 24時間 = 858日

計算時間858日です。100台のPCで分散計算しても9日掛かってしまいます。
全パターン計算することを線形探索と言います。

さて1番ではないけれど、59,319,000個のAIの中で利益が30位のAIは無意味なAIでしょうか?準最適解を求めるのに、遺伝的アルゴリズムは有効に働きます。

遺伝的アルゴリズムで計算する前の事前準備

遺伝的アルゴリズムで計算するときのルールを決定します。
1. 初期パラメータを10-400の間でランダムで決定する
2. 200世代まで計算する。
3. 売買利益をスコアとして改善していく。
4. 1世代での個体数を20とする
5. 突然変異率を1%とする ... etc

個体数や突然変異率によって計算効率が異なります。どんな値を使うと効率がいいかといった観点での論文も存在するので興味があったら読むといいかも

遺伝的アルゴリズムでよくある失敗 部分最適化

200世代で計算を終了せず、延々と進化させていくと、だいたいが部分最適化して効率が落ちます
スクリーンショット 2015-09-30 14.57.54.png

遺伝的アルゴリズムでの計算結果

最終的にこんな感じのデータが集まります。
スクリーンショット 2015-09-30 14.58.53.png

遺伝的アルゴリズムでの実装

遺伝的アルゴリズムでいうところの交叉・突然変異の実装方法
初期集団の生成方法
バックテストアルゴリズム
実際の売買システム構築

まだまだ書き切れていないので、後日その2で紹介していきたいなーと思います。

次の記事を書いてみました

遺伝的アルゴリズムでFX自動売買 その2 進化する売買AIの実装