Takehana Lab

System Trading : MultiCharts, TradingView, Python, R

ブログ趣旨

本ブログは, 管理人である TAKEHANA のシステムトレード, Bot 開発に関する研究・分析の成果を広く一般に公開・共有することを目的としています.

スクリプト公開方針

多くの方にとって(トレード Bot 使用有無と無関係に)有用だと考えるスクリプトについては, 積極的に無償で公開していきます.

サンプルスクリプトに関しては極力プログラムコード全文を公開し, 再現性のある実験内容を共有することを目指しますが, 金融市場関連のデータは権利関係が複雑であることが多く, 特に市場分析に関する記事では分析対象としたデータを開示できないことが多くなります.

そのようなケースでは, 文章及び画像を使用して, 極力分かりやすく研究内容を解説するよう努めます.

パフォーマンス公開方針

自分の実際の運用成績について公開する予定はありません.

仮想システムのパフォーマンスについてシミュレーションを載せるケースはあると思いますが, 実際の運用成績を掲載したところで, せいぜいが自慢や煽り程度の役にしか立たないと考えるからです.

読んでくださる方を不快にするのは当ブログの趣旨に反します.

更新スケジュールについて

管理人の TAKEHANA は専業トレーダーであり, 日夜アルゴリズムの開発と運用業務に取り組んでいるため, 基本的にこのブログの更新はタスクリストの中で優先度下位に属しています.

ただし, 多くの方と自分の研究内容を共有したい気持ちはあるため, 時間の許す範囲で更新頻度を上げるよう努力します.

更新頻度の分布にはバラつきが出ると思いますが, 生暖かく見守っていただけると幸いです.

使用するツール

研究には R 言語及び RStudio を使用しているため, これらを使ったスクリプトによる市場解析に関する報告がこのブログのメインコンテンツとなります.

また, 時系列データ解析には MultiCharts 及び PowerLanguage を使用しています. これらを使用して設計したアルゴリズムについてもブログ内で紹介していきます.

ブログ名

  1. システムトレード(仮)
  2. システムトレード Ver2.0
  3. TAKEHANA Lab <- NEW!

これまで3回のブログ名変更を実施しました. もともと「システムトレード」というのは正式名が決まるまでの仮名として考えていたので, 今後は 「TAKEHANA Lab」 に固定してブログ運用をしていきます.

価格データエクスポートインジケータ

前にブログにアップロードしていたマルチチャートでリアルタイム価格データを指定パスに落とすスクリプトを再度公開します.

MC には QM アプリが付属するため, そちらでも定期的な価格データの更新は可能なのですが, 高頻度のリアルタイムデータを更新することはできないため, このようなスクリプトが必要になります.

コード

コードは以下になります. Git にうpしておきました.

Github

// ---- function: export price data.

input: dir(stringSimple);

// ---- symbol name.

var: strL(""), strR(""), sym("");

sym = getsymbolname;

if category = 12 then
begin
    value1 = InStr(sym, "/");
    value2 = StrLen(sym);
    strL = LeftStr(sym, value1-1);
    strR = RightStr(sym, value2 - value1);
    sym = text(strL, strR);
end;

// ---- time frame.

var: tf_num(""), tf_value(0), tf_type("");

tf_num = numToStr(Barinterval, 0);
tf_value = barType_ex;

switch(tf_value)
begin
    case is = 1:
        tf_type = "Ticks";
    case is = 2:
        tf_type = "Minutes";
    case is = 3:
        tf_type = "Hours";
    case is = 4:
        tf_type = "Days";
    case is = 5:
        tf_type = "Weeks";
    case is = 6:
        tf_type = "Months";
    case is = 7:
        tf_type = "Years";
end;

// ---- init data path.

var: file_name(""), file_path("");

file_name = sym + "-" + tf_type + "-" + 
tf_num + ".txt";
file_path = dir + file_name;

once fileDelete(file_path);

if barNumber = 1 then
begin
    Print(file(file_path), 
    "dttm", ",", 
    "open", ",", 
    "high", ",", 
    "low", ",", 
    "close", ",",
    "volume"
    );
end;

// ---- export price data.

var: adj_date(""), adj_time(""), adj_datetime("");

adj_date = formatDate("yyyy/MM/dd", dateTime);
adj_time = formatTime("HH:mm:ss", dateTime);
adj_datetime = text(adj_date, " ", adj_time);

if barNumber >= 1 then
begin
    Print(file(file_path),
    adj_datetime + ", " +
    NumToStr(Open, Digits) + ", " +
    NumToStr(High, Digits) + ", " +
    NumToStr(Low, Digits) + ", " +
    NumToStr(Close, Digits) + ", " +
    NumToStr(Volume, Digits)
    );
end;

f_datOut = true;

// ---- eof.

使用方法

(2020/7/20 コードを編集しました.)

上記は関数コードです. 使用する場合 PLE の新規作成から function ファイルを指定してコードを入力しないとエラーになります.

関数の返値には truefalse を指定してください.

ファイル名は f_datOut としてください.

次にこの関数を呼び出すインジケータソースコードは以下のようになります.

// ---- data export.

input:
    datOut(true),
    pathDat("yourPath");

if datOut then f_datOut(pathDat);

// ---- eof.

パラメータ pathDat にデータ尾を保存したいフォルダのパスを入力してください.

C:\DataSaveFolder\ の形式で入力する必要があります. エンコードの関係で入力すると \ として表示されますが問題ありあせんのでそのまま実行してください.

このコードは LMAX チャートから為替データを取り出すために作ったので, 他の環境でどのように動作するかは知りませぬ. なにか気になる点などありましたらTwitterの方に連絡ください.

注意点

挿入先のチャートについてですが, このスクリプトは基本的に M1 か H1 または D1 で実行してください.

MC は分足単位か, 日足単位のデータをベースに全てのタイムフレームのレートを計算します. 他の時間足は全てそれらをコンバートして計算されているので, 元となる価格データをソースにしないと時間情報のエラーが発生する可能性があります.

もう一点.

特に 1 分足でこのスクリプトを実行すると MC の動作がかなり重くなることが懸念されます. 主にキャッシュが累積するためで, メモリが足りなくなる可能性があります. できれば 16GB 以上のメモリを積んだマシンで実行してください.

トレードステーション日本市場撤退

いきなりステーキってありますよね. いきなり登場して, いきなり店舗数を大幅に拡大し, 今いろいろなエリアからいきなり撤退している倒産寸前の企業です. あの会社を見ていると経営の難しさについて改めて考えさせられます.

さて, マネックス証券が突如トレードステーションの日本市場撤退をアナウンスしました.

この衝撃, この唐突感, まさに「いきなり」です.

自分は長きに渡ってマルチチャートを使い続けている人間なので, 当然互換性のあるトレードステーションにも思い入れがあり, 今回のことは残念でなりません.

なぜ撤退したか

撤退の理由について本当のところは勿論分かりませんが, おおよその予想はつきます. 少し前から全機能が無料だったトレードステーションの使用に条件が課されるなど, 前兆があったからです.

おそらく無料のチャートサービスやデータフィードのみを使用して, 取引には他のブローカーを使用する方が非常に多かったのでしょう.

トレードプラットフォームはサービスを提供するだけで, データ料金やサポートの設置, 定期的なメンテナンスなど, 大きな運用コストが掛かります.

直接トレードに使用されず, 収益性の低いトレードステーションでは, 今後もこれらのコストをペイできないと判断されたのだと思います.

また収益源を顧客の取引手数料だけに依存したことも失敗の大きな原因だったと思います. 例えば少額の月額利用料金を徴収するなどすれば簡単に撤退とはならなかったはずです.

なぜこのような事態になってしまったのでしょうか?

僕の個人的見解では, その原因は日本金融市場の特異性にあると思います.

なぜならトレードステーションはシステムトレードグローバル・スタンダードであり, これほど普及に失敗した例は, 世界的に見ておそらく日本だけだからです.

日本市場の特殊性

そもそも日本の株式や先物, 為替業界は今では完全なガラパゴスと化しています.

世界中のほとんどの市場でプライスデータの API が公開され, 自由にトレードプラットフォームを選択したり, プログラムによる取引が可能となっている時代に, 何故か日本のブローカーだけがあらゆるアセットクラスで API を公開していません.

株式, 先物, 為替, オプション... どのような分野でも十分なデータフィードを提供しているブローカーを見たことがありません. まともなサービスを提供している数少ないブローカーもほとんどが外資系です.

世界的に有名なブローカーといえば Interactive Brokers 証券ですが, ここの API は凄いです. ほとんどのプラットフォーム, プログラム言語について自社のデータを配信しています. 取引するには最高の環境でしょう.

シストレ業界で仮想通貨取引が流行っているのも, 仮想通貨ブローカーは日本市場の古くからある慣習と無関係な企業であるため, 柔軟な API を配信していることが大きいのです.

一般に考えれば, API を公開する → 取引量増える → 利用者増える → WINWIN となるはずですが, 日本でだけは得てしてこれがそうなりません.

今まで何度となく様々なブローカーが自動取引用のサービスを提供しては失敗している歴史からしてもその事実は明らかです.

日本人には価値あるものにはお金を払うという習慣がない

とにかく日本人はコストを嫌がります.

例えばプライスデータを有料契約して入手することは, 世界的にはある程度常識となっていますが, 日本のトレーダーでデータに使用料金を払っている人は稀でしょう.

取引コストも同じです.

為替や株式のブローカーはこぞって取引コストを下げ続けてきました. ツールの充実よりも, サービスの柔軟性よりも, コストコストコスト…です.

iPhone が高額であるにも関わらず日本で普及したのは, 当時斬新だった1円ケータイというパッケージングとラクラクホンのような誰にでも使いやすい(自由度の低い)使用感のためです.

日本人は昔から「単純で安いものだけ」を好む民族なのです.

勿論例外はあります. しかし大多数の観点から見れば日本人は「価値あるものを時間とお金をかけて長期的に育てる」というよりも「短期的に目先のコストを低く抑え, 楽して上手いことやりたい」という考え方をします.

このような市場でトレードステーションのような, まったく新しいサービスを時間をかけて浸透させていくアプローチが成功するはずがありません.

成功させるには, トレステに追随するブローカーが次々に現れ, API サービスを提供するブローカーが増え, 日本株システムトレード界隈全体が一気に活性化するような連鎖的科学反応が起きる必要があるのです.

そのような大規模な転換を起こすには, トレードステーション導入程度ではカンフル剤が足りないということでしょう.

プラットフォームごとのビジネスモデル

ではあらゆる取引プラットフォームが日本で普及に失敗しているかというとそうではありません. 2つの前例があります.

Meta Trader

通称 MT4, 5 は日本市場に完全に浸透し, 成功しました. 現在ではシステムトレードの代名詞ともいえるようなプラットフォームにまで成長しています.

なぜメタトレーダーが成功したかといえば, その要因は特異なビジネスモデルにあります.

一般にトレードプラットフォームはユーザーファーストで開発されます.

優れたプラットフォームを作成し, 機能をパッケージングして, 月額, 年額などのプランでユーザーに利用料を支払ってもらうというビジネスプランです.

しかし, メタトレーダーはブローカーファーストで開発されたプラットフォームです.

メタトレーダーを使用している人でターミナルの使用料金を払っている人はいないと思います. 当然です. 料金を支払うのはブローカーですから.

ブローカーが顧客にトレード用のサービスを提供するのは実は大変なんです. チャートサービスの提供や顧客管理, オーダーブック管理によるマリーやヘッジ. コストモデルの選定やサポートなど…初期投資, 運用コストともに非常に大きくなります.

メタコータス社はこの点に注目して, ブローカーが導入しやすいチャートツールを開発しました. 上記のような業務のほとんどを一括で導入できて, 運用も簡単, 新機能の開発も勝手にやってくれるとなれば, 多くのブローカーが飛びつくのは当然です.

多くのブローカーがメタトレーダーを導入 → 多くのトレーダーがメタトレーダーを使用 → 開発者の市場が形成される→ 拡張性の高いパッケージとして認知される → 普及率が幾何級数的に増加する. という連鎖反応を生み出しました.

しかし, このビジネスモデルには大きな欠点があります. それはブローカーファーストのサービスであるために, ブローカーが利益を得られることが最優先事項となってしまったことです.

その結果「いかにトレーダーからブローカーが金を巻き上げるか」というコンセプトでメタコータス社はプラグインを開発し, 数多くのブローカーがそのプラグインを自社のメタトレーダーに導入しました.

MT4 で取引しているとバッドティックがあったり, 極端な約定遅延やレート操作など, 不正ギリギリの(ギリギリアウト)体験をすることになります.

このような環境に不満を抱いたプロのトレーダーは次第に MT4 を離れていき, 独立系ベンダーのプラットフォームまたはネイティブなプログラム言語によるトレードを行うようになりました.

Trading View

最近になって注目され, 業界標準となりつつあるのが Trading View です. まず書いておきたいのですが, Trading View は Multi Charts 社が提供するクラウドプラットフォームであり, 機能の多くは Multi Charts と共通しています.

このサービスの最大の特徴はデータとプラットフォームがセットで提供されていることです.

これは実は凄いことなんです.

昔からある独立ベンダーのチャートパッケージではデータフィードとプラットフォームは別々のサービスとなっていることがほとんどです. 例えば僕はマルチチャートで Esignal のデータサービスを利用していますが, 月額2万円くらいします.

同じくらい多くのデータにアクセスできてかつ OS の縛りがなく, プログラミングによるインジケータ, ストラテジーの開発が可能で, 常に最新機能が使えるというのは, もはや奇跡としか言いようがありません.

またビジネスモデルとしてトレーダーと利害が衝突しない点にも注目したいです.

所謂サブスクリプションモデルでサービスを提供しているため, 開発方針は顧客ファーストであるはずです. MT4 と違ってブローカーの悪意がトレーダーを邪魔することもないでしょう.

上で日本人はコスト中心に考えると書きましたが, 参照できるデータ量や, あらゆる OS で使用できる点, 洗練されたチャート機能など, これだけ多くのベネフィットがあれば, むしろ Trading View の有料サービスは割安に感じられる可能性すらあります.

しかしこのプラットフォームも残念ながら現状, 完全ではありません.

レートをよく見ていると分かると思うのですが, どうしても価格が大きく変動するときなどはブローカーよりも価格配信が遅くなります. デイトレードスイングトレードなら問題ありませんが, スキャルピング(HFT)の取引に利用することはほぼ不可能です.

また, マルチチャートでは Dot Net Frame Work を使用することでプラットフォームそのものを改造したり, データベースに価格データを保存したり, 外部ライブラリを参照したりと, ほぼ無限に近い拡張性を得ることができるのですが, クラウドプラットフォームの Trading View ではそれができません. この点は半永久的にどうにもならないでしょう.

結論

長くなりましたが, まとめます.

トレードステーションの撤退の主要な原因は日本人の投資スタンス, 価値観に原因があると考えます. この点を上手くカバーした例には Meta Trader と Trading View があります. どちらも日本人の目先コスト偏重主義を上手くクリアしていますが, 完全ではありません.

個人的に考える最高のプラットフォームとは, 現在の MC.net と同じだけの機能を要しながら, Trading View と似たようなデータ込みのサブスクリプションモデルを実現したものということになるのですが, 現状そのようなサービスは何処にも見当たりません.

破産確率シミュレーター

先日本棚をあさっていたところ, かなり昔に購入した「システムトレード」という本が出てきた. ペラペラめくっているとなんだかよく分からないことばかり書いてあってほぼ斜め読みだったが, 巻末に破産確率シミュレーターの概要と, そのVBAのコードが載っていたのでRで書いてみたくなった.

モチベーションとしては, あまりにも古い資料のコードを最近の言語でアップデートしたいというのがある. 今どきExcel VBAで市場分析してる人いないと思うので.

コード

コードは以下になります. Git にうpしておきました.

Github

#----破産確率関数
risk_of_ruin <-
        function(
                accuracy,
                payoff_ratio,
                mgt = 1,
                start_capital,
                fixed_percent_risked,
                ruin_point_drawdown,
                unit_of_money){
                if (mgt == 1 ){
                        money_mgt_approach = "fixed percentage risk money mgt"
                } else {
                        money_mgt_approach = "fixed dollar risk money mgt"
                }
                
                no_records = 10001
                trade_result = 0
                equity_curve = 0
                
                account_balance = start_capital
                account_new_high = start_capital
                account_drawdown_percent = 0
                number_of_trades = 1
                number_of_losses_before_ruin = 0
                number_of_trades_since_account_high = 0
                fixed_dollar_risk = start_capital / unit_of_money
                
                i = 1
                j = 1
                x = 0
                
                repeat{
                        if (account_balance > account_new_high){
                                account_new_high = account_balance
                                number_of_losses_before_ruin = 0
                                number_of_trades_since_account_high = 0
                        }
                        
                        win_of_loss = runif(1, min = 0, max = 1)
                        
                        if(win_of_loss >= (1 - accuracy)){
                                if(money_mgt_approach == "fixed percentage risk money mgt"){
                                        trade_result[j] = ((fixed_percent_risked * account_balance) * payoff_ratio)
                                }
                                if(money_mgt_approach == "fixed dollar risk money mgt"){
                                        trade_result[j] = fixed_dollar_risk * payoff_ratio
                                }
                                if(i == 1){
                                        equity_curve[i] = start_capital
                                        i = i + 1
                                        equity_curve[i] = equity_curve[i - 1] + trade_result[j]
                                } else {
                                        equity_curve[i] = equity_curve[i - 1] + trade_result[j]
                                }
                                account_balance = account_balance + trade_result[j]
                        } else {
                                if (money_mgt_approach == "fixed percentage risk money mgt"){
                                        trade_result[j] = -(fixed_percent_risked * account_balance)
                                }
                                if(money_mgt_approach == "fixed dollar risk money mgt"){
                                        trade_result[j] = -fixed_dollar_risk
                                }
                                if(i == 1){
                                        equity_curve[i] = start_capital
                                        i = i + 1
                                        equity_curve[i] = equity_curve[i - 1] + trade_result[j]
                                } else {
                                        equity_curve[i] = equity_curve[i - 1] + trade_result[j]
                                }
                                
                                account_balance = account_balance + trade_result[j]
                                account_drawdown = account_new_high - account_balance
                                account_drawdown_percent = account_drawdown / account_new_high
                                number_of_losses_before_ruin = number_of_losses_before_ruin + 1
                        }
                        number_of_trades = number_of_trades + 1
                        number_of_trades_since_account_high = number_of_trades_since_account_high + 1
                        
                        x = x + 1
                        j = j + 1
                        i = i + 1
                        
                        if(account_drawdown_percent >= ruin_point_drawdown || equity_curve[i-1] > 200000000 || x >= 10000) break
                }
                probility_of_ruin = number_of_losses_before_ruin / number_of_trades_since_account_high
                
                if(equity_curve[i-1] > 200000000 || x >= 10000){
                        probility_of_ruin = 0
                }
                print(paste0("Ruin prob = ", probility_of_ruin))
                plot(equity_curve,
                     type = "l",
                     main = "Risk of Ruin",
                     xlab = "Times",
                     ylab = "Equity")
                grid()
        }

たぶん内容に間違いはないと思うのですが, 気になる点などありましたらTwitterの方に連絡ください.

このジェネレータはまず取引システムありきなので, システムで取引している方はストラテジーのバックテストデータから, ペイオフレシオと勝率を確認することが必要です.

また裁量の方が使用する場合, 一定期間のパフォーマンスレポートを集計して, 同じく勝率とペイオフレシオを算出することが必要です.

関数 risk_of_ruin() を実行すると, 正規乱数による破産確率計算とシミュレートデータのプロットを返します.

実行コマンドはこんな感じ.

#---- 実行テスト
accuracy             = 0.5 #勝率
payoff_ratio         = 1 #ペイオフレシオ(平均利益/平均損失)
mgt                  = 1 #マネジメントルール(1なら%、それ以外はドルベース)
start_capital        = 100 #ドル単位の当初資金
fixed_percent_risked = 0.05 #1トレードあたりの許容リスク
ruin_point_drawdown  = 0.5 #口座の破産ドローダウン
unit_of_money        = 20 #資金のユニット数

risk_of_ruin(
        accuracy,
        payoff_ratio,
        mgt,
        start_capital,
        fixed_percent_risked,
        ruin_point_drawdown,
        unit_of_money
)

引数の説明

引数 accuracy にはストラテジーの勝率を入れます.

引数 payoff_ratio にはそのまんまペイオフレシオ.

引数 mgt はマネジメントルールで, % ベースで計算するか $ ベースで計算するかを指定します.

引数 start_capital は初期資本額を $ 単位で入力します.

引数 fixed_percent_rsiked は 1 トレードあたりの最大許容リスクを指定した入力単位で入れます.

引数 ruin_point_drawdown は口座の破産ポイント(Max draw downの限界値) を指定した入力単位で入れます.

引数 unit_of_money は口座資産に対するユニット数です. 例えば口座資金が $10000 だとして, 1トレードあたりに使用する資金が $1000 とすると, ユニット数は 10 となります.

出力

実行すると破産確率が出力されます.

[1] "Ruin prob = 0.591549295774648"

シミュレーターによる乱数から計算した仮想リターンの累積プロットを出力します.

image

システムにせよ裁量にせよ破産確率を想定して取引枚数やアルゴリズムを決定することは重要です. 是非ご活用ください.

作業環境

毎日のBot開発作業でお世話になっているツールを紹介します。誰かの環境構築の助けになれば。

ハード

  • iMAC (2019)
  • ipadpro12.9 (2018)
  • iphone8

MultiChartsはParallelsでエミュレートしてます。めっちゃ落ちます。

エディター

  • VisualStudioCode
  • Textastics
  • GoCoEdit
  • JUNO

MACではVSC中心ですが、ipadではGoCoEditが主力です。

プラットフォーム

  • MultiCharts
  • MultiCharts.net

ほぼMCしか使っていません。後はREST / FIX APIで発注してます。

プログラム言語

主力言語

  • PowerLanguage
  • PineScript
  • Python
  • R

非主力言語

  • EasyLanguage
  • MQL4, 5
  • VBA
  • C#

主力言語は現在Bot開発に使用している言語です。非主力言語は自由に読み書きできますが興味ない言語です。もうあんまり新しい言語勉強したくありません…

ブローカー

  • InteractiveBroker
  • OANDA
  • Dukascopy
  • SaxoBank

メインはIBですが、他でもボチボチやってます…

RからOANDA-V20APIを叩く

概要

個人的にOANDAのAPI仕様には前から興味があったのですが、PythonのライブラリはあるもののRで簡単にAPIを叩けるサンプルがネットになかったので、自分で書きました。

Gitにクラス定義のフルコードを上げておきました。 クラスの使用サンプルもアップロードしましたのでご活用ください github.com

上記のクラスを使用するとRからOADA-V20APIを使用することができます。以下に使用例を掲載します。

まずGitからコードをDLしてください。

git <- "https://raw.githubusercontent.com/Takehana13/"
path <- paste0(git, "oanda_v20/master/API.R")
downloader::source_url(path, prompt = FALSE, quiet = TRUE)

OANDAJAPANにデモ口座を開設してください。するとtokenの取得が可能になりますのでその情報を以下のようにして入力します。

access_token <- "your access token"
account_id <- "your account i’d" 
account_type <- "practice"

今回はR6とreticulateパッケージを使ってクラスを定義したので、コードについて分からない点があれば、それぞれのリファレンスを参照してください。

reticulateはRからPythonを呼ぶパッケージで、使用するにはpythonがPCにインストールされている必要があります。PC内のpythonのpathを確認するには以下のコマンドを使用します。

py_path <- reticulate::py_discover_config()
py_path$python

これで準備が整いました。クラスはR6を使用しているので$new()メソッドを使用してインスタンスを生成してください。

oanda <- OANDA_V20$new(
  symbol_name = "USD_JPY",
  account_type = account_type,
  account_id = account_id,
  access_token = access_token,
  python_path = py_path$python
)

これでインスタンスが生成できました。次にデータを読み込んで可視化してみます。

データ操作

oanda$get_data(n = "5000",  tf = "D")

$get_data()はデータを取得して$price_dataスロットに格納します。 パラメータnデータの観測数を入力し(max = 5000)、tfにはデータのタイムフレームを入力します。

oanda$price_data %>% mutate(dttm = as_date(dttm)) %>% tail(50) %>% 
  ggplot(aes(x = dttm, y = close)) +
  geom_candlestick(aes(open = open, high = high, low = low, close = close)) +
  theme_tq()

f:id:takehana13:20191018152721p:plain

ローソク足を描画できています。次に複数銘柄の価格データ取得をテストします。

まず$get_instrument()を使って、OANDA_APIで取得可能な銘柄データ一覧を取得します。

get_ticker <- function(env_obj, sym){
  env_obj$get_instrument()
  ins <- env_obj$instrument_lst$instruments %>%
    map(list.flatten) %>%
    map(flatten_df)
  
  ins <- ins %>% bind_rows()
  ins_nam <- env_obj$instrument_lst$instruments %>%
    list.select(name) %>%
    map_chr(as.character)
  
  sym_nam <- ins_nam %>% str_subset(sym)
  syms <- ins %>% filter(name %in% sym_nam) %>% pull(name)
}

syms <- get_ticker(oanda, "GBP")

今回はポンド関連銘柄のデータを取り出します。

get_dataset <- function(env_obj, sym){
  env_obj$symbol_name <- sym
  env_obj$get_data(n = "5000", tf = "D")
  env_obj$price_data
}

sym_dat <- syms %>%
  map(get_dataset, env_obj = oanda) %>%
  set_names(syms) %>% 
  bind_rows(.id = "symbol")

得られたデータを可視化してみましょう。

sym_dat %>% 
  mutate(dttm = as_date(dttm)) %>% 
  group_by(symbol) %>% 
  do(tail(., 20)) %>% 
  ungroup() %>% 
  ggplot(aes(x = dttm, y = close)) +
  geom_candlestick(aes(open = open, high = high, low = low, close = close)) +
  theme_tq() +
  facet_wrap(.~symbol, scale = "free")

f:id:takehana13:20191018154203p:plain

ポンド系銘柄のデータを簡単に可視化できました。

同様の手順でスプレッドデータを取得することも可能です。

get_sp <- function(env_obj, sym){
  env_obj$symbol_name <- sym
  env_obj$get_instrument()
  env_obj$get_data(n = "5000", tf = "M1")
  env_obj$chk_sp(mode = "hist")
  env_obj$hist_sp
}

sym_sp <- syms %>%
  map(get_sp, env_obj = oanda) %>% 
  set_names(syms) %>% 
  bind_rows(.id = "symbol")

sym_sp %>% 
  group_by(symbol) %>% 
  mutate(id = row_number()) %>% 
  ggplot(aes(id, sp)) +
  geom_line() +
  theme_tq() +
  facet_wrap(.~symbol, scale = "free")

f:id:takehana13:20191018194655p:plain

$chk_sp()は現在または過去のスプレットデータを取得することができます。

mode = “hist”では現在読み込んでいる$price_data分の長さのヒストリカルスプレッドを取り出し、mode = “current”では現在のスプレッドを取り出します。

アカウント情報の取り出し

口座情報を取り出してみます。$get_account_detail()は現在の口座情報の詳細を取り出します。

  • $account_detailには口座情報全般
  • $account_ordersには保有中のオーダー情報
  • $account_tradesには保有中のトレード情報
  • $account_positionsにはこれまでの取引履歴

がそれぞれのスロットに格納されます。

oanda$get_account_detail()
oanda$account_detail
oanda$account_orders
oanda$account_trades
oanda$account_positions

$get_account_summary()は口座情報の概要を取得します。

oanda$get_account_summary()
oanda$account_summary

発注関連

発注を行うにはまず引数として渡したいトレードの情報を作成してください。magic=は俗に言うマジックナンバー、つまりストラテジーごとの固有識別値です。tp_dis, sl_disはpipsベースでのTPSLの範囲指定です。

mkt_param <- list(
  units = "+10000",
  type = "MARKET",
  pos_fill = "DEFAULT",
  magic = oanda$strat_magic,
  use_tpsl = FALSE,
  tp_dis = NULL,
  sl_dis = NULL
)

この情報を$new_order()に渡すとトレードが実行されます。

がそれぞれ格納されます。

lift_dl(oanda$new_order)(mkt_param)
oanda$trans_lst
oanda$magic_lst
oanda$last_tiket_id
oanda$last_tiket_unit

$close_order()を使用するとデフォルトでは直前のポジションを決済します。

oanda$close_order()
oanda$trans_lst
oanda$magic_lst

次に指値注文のテスト実行します。基本的な仕様は$new_order()と同じですが、$pending_order()では価格を指定した指値・逆指値をオーダーを送信することができます。

pend_param <- list(
  price = "111.000",
  units = "-10000",
  magic = oanda$strat_magic,
  type = "LIMIT",
  pos_fill = "DEFAULT"
)
oanda$pending_order()
oanda$trans_lst
oanda$last_order_id

同様に$cancel_order()はデフォルトで直前のオーダーをカットします。

oanda$cancel_order()

最後にポジションの全決済を方法を確認します。

現在保有中の全ポジションを決済したい場合は$close_all()を使用してください。この関数を使用するには$get_pos_info()で現在保有中のチケット情報を取得しておくことが必要です。

oanda$get_pos_info()
oanda$close_all()

この関数はデフォルトでは現在のMagicNumberに対応するポジションのみを全決済します。完全に全てのポジションを決済したい場合には以下のようにします。

oanda$get_pos_info(all = T)
oanda$close_all()

同様に全ての指値・逆指値注文をキャンセルする場合、以下のようにします。

oanda$get_order_info()
oanda$cancel_all()

これで一通りの機能紹介を終わります。トレードAPIのプログラムは非常に重要なので、もし実際に試してみて不具合などありましたらご連絡ください。

tidyquantでバックテスト(複数パラメーター最適化)

前回の記事に引き続きバックテストを進めていきます。

takehana13.hateblo.jp

今回は複数のパラメータによる最適化をテストしてみたいと思います。2本のEMAを計算し、それぞれのパラメータを変化させてパフォーマンスを最大化します。

まずtq_mutate()で2本のEMAを計算します。

require(tidyquant)
require(tidyverse)
data(FANG)

term1 <- FANG %>% group_by(symbol) %>% 
  tq_mutate(select = adjusted,
            mutate_fun = EMA, n = 10,
            col_rename = "ema1") %>% 
  tq_mutate(select = adjusted,
            mutate_fun = EMA, n = 20,
            col_rename = "ema2")

計算結果を可視化して確認しておきます。

term1 %>% 
  ggplot(aes(date, adjusted)) +
  geom_line() +
  geom_line(aes(date, ema1), colour = "red") +
  geom_line(aes(date, ema2), colour = "blue") +
  theme_tq() +
  facet_wrap(.~symbol, scale = "free")

f:id:takehana13:20190928162928p:plain

次に前回のテストと同様にストラテジーを関数化します。

strategy_ema_cross <- function(param1, param2){
  term1 <- FANG %>% group_by(symbol) %>% 
    tq_mutate(select = adjusted,
              mutate_fun = EMA, n = param1,
              col_rename = "ema1") %>% 
    tq_mutate(select = adjusted,
              mutate_fun = EMA, n = param2,
              col_rename = "ema2")
  
  term2 <- term1 %>% tq_mutate(select = adjusted,
                               mutate_fun = ROC,
                               col_rename = "roc")
  
  term3 <- term2 %>% mutate(sig = lag(if_else(ema1 > ema2, 1, -1)),
                            ret = roc * sig)
  
  term4 <- term3 %>% drop_na %>% mutate(eq = cumsum(ret)) %>% 
    select(symbol, date, eq)
}

次に最適化用のパラメータセットを用意します。expand.grid()は与えられた変数について総当たりのテーブルを計算します。

param_set <- expand.grid(
  param1 = seq(10, 50, by = 10),
  param2 = seq(20, 60, by = 10)
  )

purrr::pmap()を使ってストラテジーにパラメータセットを適用します。

opt_ret <- param_set %>% pmap(strategy_ema_cross)
opt_ret <- opt_ret %>% set_names(str_c("param", seq_len(25)))
opt_ret <- opt_ret %>% bind_rows(.id = "param")

計算結果を可視化します。

opt_ret %>% filter(symbol == "FB") %>% 
  ggplot(aes(x = date, y = eq, colour = param)) +
  geom_line() +
  theme_tq() +
  geom_hline(yintercept = 0, colour = "darkgray") +
  scale_color_tq()

f:id:takehana13:20190928163117p:plain 結果はかなり見辛くなりますが、明確な傾向が表れました。この傾向が何を意味するのか探っていきます。

確認のためパラメータごとのパフォーマンスを可視化します。

opt_ret %>% filter(symbol == "FB") %>% 
  ggplot(aes(x = date, y = eq, fill = param)) +
  geom_area() +
  theme_tq() +
  scale_fill_tq() +
  facet_wrap(.~param)

f:id:takehana13:20190928163236p:plain 極端にパフォーマンスの悪いパラメータが複数あることが分かります。

分析のためパラメータとパフォーマンスのデータをsummariesで集約して単純化します。

perf_tbl <- opt_ret %>% filter(symbol == "FB") %>% 
  group_by(param) %>% 
  summarise(perf = sum(eq))

次に使用したパラメータセットの変数XとYの差を示すデータを作成します。

param_tbl <- param_set %>%
  mutate(
    diff = param2 - param1,
    param = as.factor(str_c("param", seq_len(25)))
    )

両者を比較検討してみましょう。評価しやすいように結果をランキング形式で並べ替えます。

まずパラメータごとのパフォーマンスを確認します。

perf_tbl %>% mutate(
  param = as.factor(param),
  param = fct_reorder(param, perf)
  ) %>% 
  ggplot(aes(param, perf, fill = param)) +
  geom_bar(stat = "identity") +
  coord_flip() +
  theme_tq() +
  scale_fill_tq()

f:id:takehana13:20190928215226p:plain

次にパラメータごとの2つの値の差を確認します。

param_tbl %>% mutate(lab = fct_reorder(lab, diff)) %>% 
  ggplot(aes(lab, diff, fill = lab)) +
  geom_bar(stat = "identity") +
  coord_flip() +
  theme_tq() +
  scale_fill_tq()

f:id:takehana13:20190928215336p:plain

param_tblの中には負の値を取るものがありますが、これはストラテジーのシグナルを反転させることを意味します。つまり「短期MAが長期MAを上回ったらショート」というシグナルです。

さらにパラメータ値の差とパフォーマンスの関係を可視化します。

left_join(perf_tbl, param_tbl, .by = param) %>% 
  select(perf, diff, param) %>% 
  ggplot(aes(diff, perf, fill = param)) + 
  geom_bar(colour = "black", stat = "identity", position = "dodge") +
  theme_tq() +
  scale_fill_tq()

f:id:takehana13:20190929121220p:plain

上記のプロットから次のことが読み取れます。

  1. 必ずしも2つのパラメータの差の大きさ=リターンとはならない。
  2. パフォーマンスの高いパラメータセットに差の値が負となるものはない
  3. パラメータの差が0となるセットは例外なくリターンが悪化

この結果から対象の銘柄におけるストラテジーの挙動について「2つのパラメータ値の差が正の値を取り、推定区間内で中間的な値を取るパラメータセットについてリターンが高い傾向にある」ことが分かります。

またパラメータ値の差が負の値を取る、または値が0のものについてはパフォーマンスが悪化しています。

グラフは明かに与えられたパラメータ外のファクターがパフォーマンスに影響を及ぼしていることを示唆しているため、さらに詳細な分析が必要といえます。


パラメータ間に差がないものについて極端にパフォーマンスが悪くなっているのは、このストラテジーのコードだと条件が偽となる場合常にショートしていることになるからです。

本来このようなパラメータについては除外して考えるべきですが、今回はテストの意味も兼ねてそのまま計算しました。