Bitflyer FX バックテストしてみる
下記に書いた1秒足作るコードで作った1秒足データを元に、バックテストを実施するコードを作成してみました。
https://blog.logicky.com/2019/02/19/130014 — blog.logicky.com
ただ、Firestoreを使うのをやめようと思ってるので、Firestoreデータをcloud storageにエクスポートして、bigQueryにインポートして、テーブル生成して、cloud storageにエクスポートして、変なJSON形式になっちゃったやつを使っております。わっはっはっは。csvでエクスポート出来なかったんじゃよ。
ルール
ちなみに下記のような運用ルールを想定してます。
- 売買ルール:5秒に1回アクションをとる。(4連続同一サイドで約定すると強制解除。)
- アクション:買う、売る、なにもしない(注文の有効期限は1分)
- 売買価格:直近1秒足の高値と安値の真ん中
- 報酬:約定時の利益変動
- 1秒足データの内容: 始値、終値、安値、高値、買サイズ、売サイズ
ツイッターで流行っているっぽい強化学習をやってみたいので、とりあえずデータを集めて、報酬をあげられるようにしようとしてるんです。
コード
コードです。何も考えずに5秒おきに買って、売ってを繰り返すと、2月13日では、-14280円値幅くらい損したようです。毎回0.2でポジってたら、-2856円位です。テスト不十分なので間違っていたら申し訳ありません。
const fs = require('fs');const readline = require('readline');
const orderTimeRange = 5; //注文間隔(秒)const orderTimeLimit = 60; //各注文の有効時間(秒)const oneShotSize = 0.2; //1回の注文サイズconst maxPosiSize = 0.6; //保有可能なポジションサイズconst lossCutPriceRate = 0.05; //強制ポジション解除の場合の金額すべらす率(%)
let executions = []; //約定データlet orderNum = 0; //全注文回数let actions = []; //今回のアクションが全てはいった配列let positions = []; //全ての約定が入った配列
//シミュレーションconst main = async () => { await readExecutions(); console.log('executions: ' + executions.length); orderNum = Math.ceil(executions.length / orderTimeRange); console.log('orderNum: ' + orderNum); getActions(); console.log('actions: ' + actions.length); for (let i = 0; i < orderNum; i++) { let idx = orderTimeRange * i; let action = actions[i]; const max = parseInt(executions[idx].maxPrice); const min = parseInt(executions[idx].minPrice); let price = parseInt((max - min) / 2 + min); let executed = checkExecuted(idx, price); if (!executed) continue; positions.push({ action: action, price: price, executed: executed }); } sortPositions(); checkPosi();};
const checkPosi = () => { const maxPosiCount = Math.ceil(maxPosiSize / oneShotSize); let posiData = []; let profit = 0; let totalProfit = 0; positions.forEach(posi => { if (posiData.length <= 0) { posiData.push(posi); } else { if (posi.action === posiData[0].action) { posiData.push(posi); } else { let old = posiData.shift(); if (old.action === 'BUY') { profit = posi.price - old.price; } else { profit = old.price - posi.price; } } } if (posiData.length > maxPosiCount) { let totalPrice = 0; posiData.forEach(old => { totalPrice += old.price; }); const oldPrice = parseInt(totalPrice / posiData.length); if (posi.action === 'SELL') { profit += posi.price - oldPrice; } else { profit += oldPrice - posi.price; } profit -= posi.price * lossCutPriceRate / 100; posiData = []; } totalProfit += profit; posi.profit = profit; posi.totalProfit = totalProfit; profit = 0; }); // console.log(positions); console.log(totalProfit); console.log(totalProfit * oneShotSize);};
const sortPositions = () => { positions.sort((a, b) => { if (a.executed < b.executed) return -1; if (a.executed > b.executed) return 1; return 0; });};
//約定できたかどうかを返す// idx + 1の開始時点で注文を出す// idx + 1の終了時点で注文1秒経過const checkExecuted = (idx, price) => { for (let i = 0; i < orderTimeLimit; i++) { let execIdx = idx + 1 + i; if (execIdx >= executions.length) break; let exec = executions[execIdx]; const max = parseInt(exec.maxPrice); const min = parseInt(exec.minPrice); if (price >= min && price <= max) return execIdx; } return null;};
//今回はとりあえず、買いと売りを繰り返すだけにしてみる。const getActions = () => { let action = 'BUY'; for (let i = 0; i < orderNum; i++) { actions.push(action); action = action === 'BUY' ? 'SELL' : 'BUY'; }};
//jsonファイルを読み込むconst readExecutions = async () => { const fileStream = fs.createReadStream('./one_sec_0213.json'); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity }); for await (const line of rl) { executions.push(JSON.parse(line)); }};
main();