前回の記事に引き続き、今回も仮想通貨botの開発状況をまとめていきます。
今回は、トレンドフォローbotとバックテストについてまとめます。
この画像で検証しているのは逆張りロジックではなくて"シンプルなトレンドフォロー型bot"だったな。まぁ、バックテスト自体には問題ないのでOK。 https://t.co/EjBbvCkRt3
— よだか(夜鷹/yodaka) (@yodakablog) August 21, 2024
シンプルな戦略とその検証をするコードです。コードの一部はChatGPTに書かせています。
トレンドフォローbotのコード(雛形)
移動平均線のクロスオーバーに基づいて取引をするシンプルなbotです。
import ccxt import pandas as pd import time from datetime import datetime exchange = ccxt.binance({ 'apiKey': '', 'secret': '' }) symbol = 'BTC/USD' timeframe = '1m' # 1分足のデータ def fetch_data(symbol, timeframe): bars = exchange.fetch_ohlcv(symbol, timeframe, limit=100) df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') return df def calculate_signals(df): df['MA20'] = df['close'].rolling(window=20).mean() df['MA50'] = df['close'].rolling(window=50).mean() df['Signal'] = 0 df.loc[df['MA20'] > df['MA50'], 'Signal'] = 1 df.loc[df['MA20'] < df['MA50'], 'Signal'] = -1 df['Position'] = df['Signal'].diff() return df def run_bot(): while True: df = fetch_data(symbol, timeframe) df = calculate_signals(df) latest_position = df.iloc[-1]['Position'] if latest_position > 0: print(f"{datetime.now()} - Buy signal") #購入ロジック elif latest_position < 0: print(f"{datetime.now()} - Sell signal") #売却ロジック time.sleep(60) if __name__ == '__main__': run_bot()
-
仮想通貨botの開発記録#81(2024/7/27)「基本的な取引ロジックの雛形まとめ①」
続きを見る
バックテスト用のコード
import ccxt import pandas as pd import matplotlib.pyplot as plt exchange = ccxt.binance() symbol = 'BTC/USDT' # BinanceではUSDではなくUSDTを使います timeframe = '1m' def fetch_historical_data(symbol, timeframe, since): bars = exchange.fetch_ohlcv(symbol, timeframe, since=since, limit=1000) df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') return df def calculate_signals(df): df['MA20'] = df['close'].rolling(window=20).mean() df['MA50'] = df['close'].rolling(window=50).mean() df['Signal'] = 0 df.loc[df['MA20'] > df['MA50'], 'Signal'] = 1 df.loc[df['MA20'] < df['MA50'], 'Signal'] = -1 df['Position'] = df['Signal'].shift() return df def backtest_strategy(df): initial_balance = 10000 balance = initial_balance position = 0 # ポジション:0 = ノーポジション, 1 = ロング, -1 = ショート for i in range(1, len(df)): if df.iloc[i]['Position'] == 1 and position == 0: # Buy position = 1 buy_price = df.iloc[i]['close'] print(f"Buy at {buy_price}") elif df.iloc[i]['Position'] == -1 and position == 1: # Sell position = 0 sell_price = df.iloc[i]['close'] profit = (sell_price - buy_price) / buy_price * balance balance += profit print(f"Sell at {sell_price}, Profit: {profit}") return balance if __name__ == '__main__': since = exchange.parse8601('2023-01-01T00:00:00Z') df = fetch_historical_data(symbol, timeframe, since) df = calculate_signals(df) final_balance = backtest_strategy(df) print(f"Final balance: {final_balance}")
上記のパラメータをいじることで、戦略の有効性を確認することができます。
変更可能なパラメータ
- 使用する取引所
- 取引する通貨
- 運用期間
- 採用する時間枠(timeframe) = ◯分足,◯時間足,◯日足
- 移動平均の数値(◯日移動平均での比較を参入・退出のシグナルとするのか)
- 移動平均の算出方法(単純移動平均・修正移動平均・過重移動平均)
追加が必要なこと
- 取引手数料(取引所ごとに仕様が異なるので個別に設定する)
- 収益率の算出(リターンが何%になるのか)
- 損切りと利食いのライン設定ロジック
- 資産の総額に対して損切りと利食いの注文サイズを調整するロジック
- 利益が出た期間と損失を出した期間の市場分析
- 取得可能な限りの全過去データ
- 過去データを元にランダムな期間を抽出してバックテストを行う
損益をグラフで描写
import ccxt import pandas as pd import matplotlib.pyplot as plt exchange = ccxt.binance() symbol = 'BTC/USDT' # BinanceではUSDではなくUSDTを使います timeframe = '1d' def fetch_historical_data(symbol, timeframe, since): bars = exchange.fetch_ohlcv(symbol, timeframe, since=since, limit=1000) df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') return df def calculate_signals(df): df['MA20'] = df['close'].rolling(window=30).mean() df['MA50'] = df['close'].rolling(window=50).mean() df['Signal'] = 0 df.loc[df['MA20'] > df['MA50'], 'Signal'] = 1 df.loc[df['MA20'] < df['MA50'], 'Signal'] = -1 df['Position'] = df['Signal'].shift() return df def backtest_strategy(df): initial_balance = 10000 balance = initial_balance position = 0 # ポジション:0 = ノーポジション, 1 = ロング, -1 = ショート balance_over_time = [] # 各時点でのバランスを記録するリスト for i in range(1, len(df)): if df.iloc[i]['Position'] == 1 and position == 0: # Buy position = 1 buy_price = df.iloc[i]['close'] print(f"Buy at {buy_price}") elif df.iloc[i]['Position'] == -1 and position == 1: # Sell position = 0 sell_price = df.iloc[i]['close'] profit = (sell_price - buy_price) / buy_price * balance balance += profit print(f"Sell at {sell_price}, Profit: {profit}") balance_over_time.append(balance) # 時系列のバランスをDataFrameに変換 balance_df = pd.DataFrame({ 'timestamp': df.iloc[1:len(balance_over_time)+1]['timestamp'], 'balance': balance_over_time }) return balance_df if __name__ == '__main__': since = exchange.parse8601('2024-01-01T00:00:00Z') df = fetch_historical_data(symbol, timeframe, since) df = calculate_signals(df) balance_df = backtest_strategy(df) # 損益の流れを時系列で可視化する plt.figure(figsize=(12, 6)) plt.plot(balance_df['timestamp'], balance_df['balance'], label='Balance Over Time') plt.title('Balance Over Time from Backtesting Strategy') plt.xlabel('Time') plt.ylabel('Balance') plt.grid(True) plt.legend() plt.show()
10000ドルの原資に対してこのリターン。リスクを抑えているから当然と言えば当然。移動平均の算出方法もいろいろ試していく。この過程を全てbotにやらせたい。 pic.twitter.com/PnTRolbOCC
— よだか(夜鷹/yodaka) (@yodakablog) August 21, 2024
まとめ
今回はトレンドフォローbotとそのバックテスト用のコードをまとめました。
変更できるパラメータをいろいろ試す中で、有効な戦略を磨いていきたいです。
宿題
パラメータを変更・追加して、バックテストを行う
今後もこの調子で仮想通貨botの開発状況をまとめていきます。
なんとなく意識している流れ
①長期運用系のbotを作る
②中期運用系のbotを作る
③短期運用系のbotを作る①を組み上げたらあとは定期的なメンテのみで放置できるようにしていく。
稼ぎは小さくても土台となるものが継続的に稼ぎ続けてくれていれば、短期系のbot開発に集中できる。
まずは土台作り。— よだか(夜鷹/yodaka) (@yodakablog) August 21, 2024