ビットコインの激しい値動きのチャートを見ていると、この上下の動きから利益を上げられないかなと考えたことあると思います。
最近の値動きでは、激しく下げてもしばらくすると元の価格に戻ってきていますので、下げたら買って戻るのを待ってから売るという裁量トレードで稼ぐことができました。
インジケーターも使わずに非常にシンプルなトレードですが、レバレッジ15倍を使って証拠金残高15万円→30万円のようなトレードを行うこともできました。
これもビットコインが非常に予測しやすい値動きをしているからですね。
Contents
ビットコインが下がったら買って上がったら売るを自動売買でできない?
さて、ここからが本題です。
単純に下がったら買って(ナンピン)、上がったら売るなら簡単にシステム化できそうで、細かい値動きにも追従できるはずと思いつきました。
下がったら買い増して上がったらそれを売るという戦略で思い出したのがトラリピやループイフダンでした。
早速これをbitFlyer FXで運用できるように実装してみました。
実装する前に、bitFlyer FXで気になることがいくつかありました。
- 両建てはできないこと。
- 決済注文はポジションに対して反対売買を行うことで実行されること。
- 複数回の注文で作られたポジションに対して決済注文を行う場合、決済するポジションを指定することができず、古いものから決済されること。
特に3つ目の古いポジションから順番に決済されるというものが大きな懸念点です。
ただ、この時に想定していたのはビットコインは上昇トレンドにある(この時の私の思い込み)ので、買い方向にポジションを立てるようにすれば利益は出るだろうということです。
下がったら買って待っておいて、上がったら売るという裁量トレードをやっていましたので。
とりあえず作ってみようとコーディングしてみて実弾(少額)で試してみることにしました。
ビットコインFXでトラリピ・ループイフダン型のbot初期タイプを開発、運用開始
初期タイプは1日で出来ました。簡単なロジックでしたので。
出来上がったのが夜でデバッグした後に少し運用してみて意図通りに動いてくれたので深夜に少額(0.01 BTC)で運用開始しました。
その後就寝しましたが、今から振り返って考えるとよくそんなことしたなと自分をってやりたい・・・。
この時は絶対儲かると疑ってなかったんですね。
起きてみると、意図した通りに利益が積みあがっていました。
1時間で3,000円ほど利益が出ていましたので、ロットを増やして0.1 BTCで運用すれば1時間に3万円!と皮算用してほくそえんでいました。
利益が出ていたのはたまたま価格が上昇を続けていたからで、買いで入って指定の幅だけ上昇したら利確して次の買いを入れるというロジックにマッチしていたからでした。
朝起きて利益が出ていたので、そのまま運用して様子を見る事にしました。
昼過ぎからチャートは反転して下落傾向に移行していきました。
すると下がるたびに買いを入れていきます。
ナンピンするロジックなのでこれは想定通りです。
評価損は出ていますが、また反転してくると評価益になって売りも入ってくると待ちました。
ゆっくりと上昇を見せるようになりましたが、マイナスが出続けました。
下がった後、上昇に転じてすぐはマイナスになるだろうことは予想していました。
古いポジションから決済されるためです。
古いポジションが消化されるとプラスに転じるハズと運用状況を見守りましたが、マイナスが続いた後にプラスになっても極々わずかです。
最初の利益を食いつぶしてしまいました。
これはちょっとシミュレーションしてみれば分かることですが、古いポジションとして残っているのは値下がりを始めたころのポジションで、これは価格が高いところで買いを入れたものです。
一方、価格の方向が転換した直後は底値から上がっていくことになり、ここで売りで入れて解消されるポジションは一番価格の高いもので、損失が一番大きくなる条件で決済されてしまうということになります。
トレンドの方向が一致したときぐらいしか利益を出せないのでこれはボツにしようと思ったのですが、一つ追加しておきたい機能を実装することにしました。
ビットコインFXのトラリピ・ループイフダン型のbot第2期タイプを開発
追加したい機能とは、保持しているポジションのうち一番古いもの(次に決済されるもの)で利益が出るまで保持するというものです。
V字型に価格が変動する場合は、一番上まで価格が回復してから決済を始めるというものです。
今回公開するソースはこの2期型のものです。
注意点としては、価格が回復するまでポジションを保持することになるので、どれぐらいの価格差ごとにポジションを発生させるのかということと、証拠金額と取引数量を上手く調整する必要があります。
価格差が狭すぎると多数のポジションを抱えることになり、より多くの証拠金が必要になります。
広すぎると機会損失になります。
bitFlyerビットコインFX用のトラリピ・ループイフダン型のbotソースコード
作成したbitFlyer用botですが、現在は運用を停止しています。
その理由は、レバレッジが最大4倍になってしまったからです。
ずっと15倍でトレードしていたのですが、4倍になってしまい驚くほど資金効率が落ちてしまいました。
同じロジックでbitMEXで稼働するbotも作成したので、bitMEXでの運用に移っていくことになると思います。
# -*- coding:utf-8 -*- from django.core.management.base import BaseCommand from ...models import Param, Bitflyermanage, Bitflyerorder import ccxt from .bitflyer import bitflyerApi from decimal import * import logging import time import datetime import sys symbol = 'FX_BTC_JPY' # 対象通貨 open_side = 'buy' # エントリー注文タイプ buy or sell close_side = 'sell' # 決済注文タイプ buy or sell order_type = 'limit' # 注文種別 limit or market price_diff = Decimal("2000.0") # 注文間の価格差 amount = Decimal("0.01") # 注文サイズ now = datetime.datetime.now() logic_started = now.strftime("%Y%m%d%H%M%S") bitflyer = bitflyerApi.bitflyer() # BaseCommandを継承して作成 class Command(BaseCommand): # python manage.py help count_entryで表示されるメッセージ help = 'Display the number of blog articles' # コマンドライン引数を指定します。(argparseモジュール https://docs.python.org/2.7/library/argparse.html) # 今回はaccount_idという名前で取得する。(引数は最低でも1個, int型) def add_arguments(self, parser): parser.add_argument('account_id', nargs='+', type=int) # コマンドが実行された際に呼ばれるメソッド def handle(self, *args, **options): if len(options['account_id']) != 1: sys.exit() account_id = options['account_id'][0] # balance = bitmex.fetch_balance() manage_id = self.create_bitflyermanage().pk base_price = Decimal("0.0") # 基準価格 この価格を更新していく position = Decimal("0.00") average_price = Decimal("0.0") while True: while True: try: ticker = bitflyer.fetch_ticker(symbol) break except ccxt.BaseError as e: self.logging('error', e) time.sleep(10) oldest_profit_order = self.get_oldest_profit_price(manage_id) price, base_price, profit_price, entry_price = self.get_price(ticker, base_price) if position == 0: open_order_id, order_result, position = self.open_order(manage_id, account_id, price, profit_price, position) time.sleep(10) if order_result == 'success': self.logging('debug', 'First Entry buy Price:%s, Position:%s' % (price, position)) self.close_order(manage_id, open_order_id, account_id, profit_price) self.logging('debug', 'Close order to First Entry sell Price:%s' % profit_price) time.sleep(10) else: if oldest_profit_order is not None: close_price = Decimal(oldest_profit_order.price) close_amount = Decimal(oldest_profit_order.amount) # 現在価格が利確ラインを超えた場合 if (open_side == 'buy' and close_price <= price) or \ (open_side == 'sell' and close_price >= price): position = self.update_close_ordered(oldest_profit_order.id, close_amount, price, position) time.sleep(10) # エントリーラインを超えた場合、次の注文を入れる if (open_side == 'buy' and entry_price >= price) or \ (open_side == 'sell' and entry_price <= price): open_order_id, order_result, position = self.open_order(manage_id, account_id, price, base_price, position) time.sleep(10) if order_result == 'success': self.close_order(manage_id, open_order_id, account_id, base_price) self.logging('debug', 'Close order to sell Price:%s' % base_price) time.sleep(10) base_price = self.check_base_price(base_price, price) time.sleep(10) def get_price(self, ticker, base_price): if open_side == 'buy': price = Decimal(ticker['ask']) elif open_side == 'sell': price = Decimal(ticker['bid']) if base_price == 0: base_price = price if open_side == 'buy': profit_price = base_price + price_diff entry_price = base_price - price_diff elif open_side == 'sell': profit_price = base_price - price_diff entry_price = base_price + price_diff return price, base_price, profit_price, entry_price def get_params(self, account_id, logic_id): params = Param.objects.filter( account_id=account_id, logic_id=logic_id, ended_at__isnull=True).order_by('id').reverse().first() return params def create_bitflyermanage(self): manage = Bitflyermanage( status=0, count_open_ordered= 0, count_close_ordered=0, total_profit=0, ) manage.save() return Bitflyermanage.objects.latest('id') def open_order(self, manage_id, account_id, price, price_profit, position): params = {"product_code": "FX_BTC_JPY"} try: open_result = bitflyer.create_order( symbol, order_type, open_side, float(amount), float(price), params) order_result = 'success' position = position + amount except ccxt.BaseError as e: order_result = 'error' self.logging('error', e) open_result = e order = Bitflyerorder( manage_id=manage_id, account_id=account_id, product_code=symbol, order_type=order_type, open_or_close='open', order_side=open_side, amount=amount, price=price, price_result=price, price_profit=price_profit, message=open_result, order_result=order_result, status=0, profit_ordered=0, logic_started=logic_started, ) order.save() return Bitflyerorder.objects.latest('id').pk, order_result, position def close_order(self, manage_id, open_order_id, account_id, price): order = Bitflyerorder( manage_id=manage_id, open_order_id=open_order_id, account_id=account_id, product_code=symbol, order_type=order_type, open_or_close='close', order_side=close_side, amount=amount, price=price, status=0, profit_ordered=0, logic_started=logic_started, ) order.save() def get_oldest_profit_price(self, manage_id): if open_side == 'buy': order = Bitflyerorder.objects.filter( manage_id=manage_id, order_side=close_side, status=0).order_by('id').first() elif open_side == 'sell': order = Bitflyerorder.objects.filter( manage_id=manage_id, order_side=close_side, status=0).order_by('id').first() return order def update_close_ordered(self, order_id, close_amount, price, position): params = { "product_code": "FX_BTC_JPY" } while True: try: close_result = bitflyer.create_order( symbol, order_type, close_side, float(close_amount), float(price), params) order_result = 'success' Bitflyerorder.objects.filter(id=order_id).update( price_result=price, status=2, message=close_result, order_result=order_result) position = position - close_amount return position except ccxt.BaseError as e: self.logging('error', e) time.sleep(10) def check_base_price(self, base_price, price): if base_price - price_diff >= price: base_price = base_price - price_diff elif base_price + price_diff <= price: base_price = base_price + price_diff return base_price def logging(self, level, message): if level == 'debug': logger = logging.getLogger('debug') logger.debug(message) else: logger = logging.getLogger('error') logger.error(message)
botの実装について
Django上で動くように実装しています。
Djangoで動かす方法は以下をご覧ください。
以下のようにDBを設定します。
from django.db import models from datetime import datetime from django.utils import timezone # Create your models here. class Param(models.Model): account_id = models.IntegerField() logic_id = models.IntegerField(null=True) started_at = models.DateTimeField(null=True) ended_at = models.DateTimeField(null=True) product_code = models.CharField(max_length=50, null=True) order_side = models.CharField(max_length=5, null=True) price_diff = models.DecimalField(null=True,max_digits=8, decimal_places=2) stop_loss = models.DecimalField(null=True,max_digits=8, decimal_places=2) size = models.DecimalField(max_digits=8, decimal_places=4, null=True) order_num = models.IntegerField(null=True) leverage_level = models.IntegerField(null=True) price_range = models.IntegerField(null=True) min_period = models.IntegerField(null=True) stop_period = models.IntegerField(null=True) short_hour_period = models.IntegerField(null=True) middle_hour_period = models.IntegerField(null=True) long_hour_period = models.IntegerField(null=True) price_buffer = models.IntegerField(null=True) order_expire = models.IntegerField(null=True) position_expire = models.IntegerField(null=True) min_size = models.DecimalField(null=True,max_digits=8, decimal_places=4) perk_period = models.IntegerField(null=True) perd_period = models.IntegerField(null=True) slowd_period = models.IntegerField(order_side, null=True) memo = models.TextField(null=True) created_at = models.DateTimeField(default=timezone.now) updated_at = models.DateTimeField(auto_now=True) class Bitflyermanage(models.Model): status = models.CharField(max_length=20, null=True) count_open_ordered = models.IntegerField(null=True) count_close_ordered = models.IntegerField(null=True) total_profit = models.DecimalField(max_digits=15, decimal_places=8, null=True) created_at = models.DateTimeField(default=timezone.now) updated_at = models.DateTimeField(auto_now=True) class Bitflyerorder(models.Model): manage_id = models.IntegerField() open_order_id = models.IntegerField(null=True) account_id = models.IntegerField() product_code = models.CharField(max_length=10, null=True) order_type = models.CharField(max_length=20, null=True) open_or_close = models.CharField(max_length=6, null=True) order_side = models.CharField(max_length=4, null=True) amount = models.DecimalField(max_digits=12, decimal_places=8, null=True) price = models.DecimalField(max_digits=15, decimal_places=8, null=True) price_result = models.DecimalField(max_digits=15, decimal_places=8, null=True) price_profit = models.DecimalField(max_digits=15, decimal_places=8, null=True) price_loss = models.DecimalField(max_digits=15, decimal_places=8, null=True) message = models.TextField(null=True) order_result = models.CharField(max_length=10, null=True) status = models.CharField(max_length=20, null=True) profit_ordered = models.SmallIntegerField(null=True) logic_started = models.CharField(max_length=20, null=True) created_at = models.DateTimeField(default=timezone.now) updated_at = models.DateTimeField(auto_now=True)
bitFlyerに接続するためのAPIキー、シークレットを設定します。
# -*- coding: utf-8 -*- """ Created on Thu Mar 9 12:00:27 2017 @author: zaihack """ import ccxt class bitflyerApi: def bitflyer(): bitflyer = ccxt.bitflyer({ # APIキーをご自分のものに差し替えてください 'apiKey': '自分のAPIキー', 'secret': '自分のAPIシークレット', }) return bitflyer
botの使い方
最初にDBに接続してテーブルを作るため、以下を実行します。
manage.pyは、Django上のファイルです。
python manage.py makemigrations trades python manage.py migrate
上記のコマンドでエラーがなくDBにテーブルが作成されていたらbotを実行します。
nohup python manage.py loop_trade 1 &
nohupと末尾の&はsshのコンソールを閉じた後でもbotが終了しないようにするためのものです。
loop_tradeはbotの名前(loop_trade.py)で、1は引数のaccount_idです。