cTraderのcBot開発にはC#4.0の機能までしか使えない?
cTraderフォーラムを見てるとときどきこんな話を目にします。これはある意味正しいのですが、正確ではありません。
確かにAutomate画面から右クリックで「VisualStudioで編集」した場合、そのままではほぼC#4.0までの機能しか使えません。
ただ、新しい機能を使う方法がないわけではないのです。少し詳しくみてみましょう。
C#の機能とは何を指すのか
「C#の機能」といってもちょっとざっくりしすぎてて何を指すのかわかりにくいので、ここでは2つに大別して考えます。
①言語自体の機能
いわゆるC#の新機能というとこっちを指すのが普通かもしれません。「こんな文法が追加され、こんな書き方がこんなことができるようになったよ!」みたいなのです。
たとえば文字列補完式。変数priceの中身を出力したいときはこんな風に書けます。
Console.WriteLine($"価格は{price}円です。");
この文字列の中に{}で変数を埋め込むというのはC#6.0から使える機能です。
同じことをC#4.0でやろうとした場合、こんな風に書く必要があります。
Console.WriteLine("価格は{0}円です。",price);
微妙にわかりにくくて不便。WriteLineで使うならまだいいのですが、普通に文字列生成したいときにはstring.Formatを使わなければならないため余計に面倒です。
実際(普通にやったら)cBotやIndicatorの開発では文字列補完式は使えません。これは実は使ってるC#のバージョンが低いわけではなく、C#4.0以上に相当する機能はcBot開発用拡張機能によってエラーとしてはじかれるようなのです。
逆に言えばこれら新しめの言語機能は拡張機能にさえ邪魔されなければ、コンパイルもできるし動作も問題ないといった機能なのです。
②.Netフレームワークの機能
例えばWeb接続なんかをしたい場合、現在だとSystem.Net.Http名前空間のクラスを使うのが一般的だと思いますが、cBot開発においては(そのままでは)使えません。
using System.Net.Httpと冒頭に書いてもエラーになりますし、参照追加しようとしても選択肢に出てこないのです。
「C#4.0までの機能しか使えないから仕方ない」とあきらめるのは早いです。これはC#の機能ではなくて.Net Frameworkの機能でして、.Net Frameworkのバージョンが古いのが問題なのです。
cBot開発時のターゲットフレームワークはデフォルトは.Net Frammework4 Client Profileですが、System.Net.Http名前空間は.Net Framework4.5で追加されたものなので使えないというだけです。
ターゲットフレームワークさえ変えられれば利用できる機能ということです。
フレームワーク依存の機能だけでも使えるようにする
先に簡単な②を解決する方法を紹介します。一応簡単にフレームワークの説明しますが、方法だけ知りたい方はこちらへ。
そもそもフレームワークってなんだ?
C#やVBといった言語は.Netのフレームワーク上で動きます。要は.NetのフレームワークというのはC#やVBを動かすために必要な基盤です。JAVAのプログラムを動かすためにJAVA仮想マシンが必要なのと同じように、VBやC#のプログラムを動かすには.Netのフレームワークが必要なのです。
例えばcTraderの場合は.Net Framework 4.0上で動くため、.Net Frameworkのバージョン4.0以上がPCにインストールされていないとcTrader自体動きません。
「そんなもんインストールした覚えがないぞ!」という方もご心配なく。.Net FrameworkはWindowsのインストール時に一緒にインストールされています。Windowsのバージョンによってインストールされてる.Net Frameworkのバージョンも異なります。
この.Net Frameworkが色々な機能をもったクラスを提供してくれるおかげで、C#のプログラムではネットに接続したりみたいな実際は複雑な処理が必要なことが、たった数行のコードを書くだけで手軽にできるわけです。
(なお、今では.Net Framework自体がすでに古いフレームワークです。.Net Frameworkはバージョン4.8で更新が終了しており、現在はWindowsのみならずMacやLinuxでも利用できる.Net Coreが主流となっています。)
C#と.NetFrameworkの関係
C#のバージョンと.Net Frameworkのバージョンというのは確かに密接な関係にはあります。例えばC#4.0であれば対応する.Net Frameworkは4.0です。
(それぞれバージョンの対応はQiitaのこのページが参考になります。必ずしも同じ番号が振られてるわけではありません。)
こういうと、「C#4.0機能しか使えないのだから.Net Frameworkも4.0までしか使えないのは仕方ない」と思ってしまいそうですが、そういうことではありません。
あくまでC#4.0の機能が.Net Framework4.0までの機能をベースに設計されているというだけの話です。C#4.0の機能の中には.Net Frameworkが4.0以上じゃないと動かないのもあるよ、ってことです。
ターゲットフレームワークは変更できる
cBot開発で一見.Net Framework4.0までの機能しか使えないのは、ターゲットフレームワークが最初からそう設定されてるからというだけです。
別にcTraderが.Net Framework4.0で開発されていたからといって、新しい.Net Frameworkさえ用意できる環境であれば、cBotやインジケーターまでcTrader本体に合わせる必要はありません。
cTraderのcBotやインジケーターを作る場合、ターゲットフレームワークとしてはデフォルトで「.Net Framework 4 Client Profile」が指定されていますが、そもそも今cTraderが動いてるPCで.Net Framework 4 ClientProfileしか入ってないなんてことはそうそうありません。
少なくともWindows10であれば必ず.Net Framework4.6以上が入ってます。ターゲットフレームワークは新しいものに変更してしまいましょう。(自分が使うだけのcBotやインジなら選択肢に出てくる一番新しいものにして大丈夫です。)
VisualStudioの「プロジェクト」メニューの一番下、「 (プロジェクト名) のプロパティ」を開いて、「対象のフレームワーク」を変更します。
これだけで「〇〇というクラスが使えない」「〇〇が参照に追加できない」といった問題は解決することが多いです。
C#言語機能の新しいものも使う方法
次に①の新しめのC#言語機能を使う方法。要はcBotの拡張機能が邪魔なだけなので、拡張機能が動いてないところでcBotを作ってしまえばいいのです。
C#の新しい機能使いたいなら、メイン処理は別ライブラリとして作るべし
結論としてはこれだけなんですけどね。ライブラリを作る際はターゲットフレームワークは自分で指定するため、これもできるだけ新しいの選んでおきましょう。
これによりC#7.0までの機能はすべて使うことができます。以下で手順を詳しく説明します。
手順
ライブラリとして作っても、cTraderで使えないと意味がありません。ライブラリの機能をcBotやインジケーター本体のalgoファイルから呼び出す必要があります。
そのため、こんな手順で作っていきます。(cBotで説明しますが、インジケーターでもRobotクラスがIndicatorクラスに変わるだけで、ほぼ同じです。)
①ライブラリ側でRobotクラスをメンバに持つMyTestLogicクラスを作る。
②ライブラリ側でロジックを実装する。
③algoファイルからはライブラリを読み込み、MyTestLogicオブジェクトを作り、メソッドを呼び出すだけ。
では一つずつ見てみましょう
①ライブラリ側を作る
VisualStudioで新規プロジェクトを作成します。
新しいプロジェクトの作成で「クラスライブラリ(.Net Framework)」を選んでください。
次の画面で名前やターゲットフレームワークを設定します。
名前はなんでもいいです。フレームワークは自分で使うだけであれば、一番新しいものを選んでおけば、上記②の機能も問題なく使えます。(デフォルトで新しいものが選ばれてると思います。)
あとでcBot本体から参照するときに作成した「場所」が必要になりますので覚えておいてください。そのままであれば自分のフォルダのsource\reposの中だと思います。
続いて、cAlgoのクラスを使えるようにcTrader APIのDLLを読み込みます。
ソリューションエクスプローラの参照を右クリックしてして、「参照の追加」。cAlgoAPI.dllファイルを探して、追加します。おそらく PC>ドキュメント>cAlgo>APIにあります。
あとは冒頭にusing文を追記すれば
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;
これで、cBotを作っている時と同じ感覚でcAlgoのクラスが使えるようになります。
あとはClass1の名前をMyTestLogicとでも変更して、こんな感じの雛形を作ります。パラメータを持つcBotの場合は、コンストラクタの引数にでも追加しておいてください。(インジケーターの場合はOutput属性のIndicatorDataSeriesも)
namespace MyLogic {
public class MyTestLogic {
// --- フィールドにrobotを持たせる
private Robot _robot;
// --- パラメータがあればこんな感じで
//public int Period { get; set; }
// --- コーディングしやすいようにプロパティ化しておく
public Chart Chart => _robot.Chart;
public Bars Bars => _robot.Bars;
public double Bid => _robot.Bid;
public double Ask => _robot.Ask;
public Symbol Symbol => _robot.Symbol;
public string SymbolName => _robot.SymbolName;
public Positions Positions => _robot.Positions;
public PendingOrders PendingOrders => _robot.PendingOrders;
public DateTime Time => _robot.Time;
public History History => _robot.History;
public MarketData MarketData => _robot.MarketData;
public IIndicatorsAccessor Indicators => _robot.Indicators;
public void Print(string msg, params object[] parameters) => _robot.Print(msg, parameters);
public void Print(params object[] parameters) => _robot.Print(parameters);
public void Print(object value) => _robot.Print(value);
public TradeResult ExecuteMarketOrder(TradeType tradeType, string symbolName, double volume)
=> _robot.ExecuteMarketOrder(tradeType, symbolName, volume);
public TradeResult ExecuteMarketOrder(TradeType tradeType, string symbolName, double volume, string label)
=> _robot.ExecuteMarketOrder(tradeType, symbolName, volume, label);
public TradeResult ExecuteMarketOrder(TradeType tradeType, string symbolName, double volume, string label, double? stopLossPips, double? takeProfitPips)
=> _robot.ExecuteMarketOrder(tradeType, symbolName, volume, label, stopLossPips, takeProfitPips);
public TradeResult ExecuteMarketOrder(TradeType tradeType, string symbolName, double volume, string label, double? stopLossPips, double? takeProfitPips, string comment)
=> _robot.ExecuteMarketOrder(tradeType, symbolName, volume, label, stopLossPips, takeProfitPips, comment);
public TradeResult ExecuteMarketOrder(TradeType tradeType, string symbolName, double volume, string label, double? stopLossPips, double? takeProfitPips, string comment, bool hasTrailingStop)
=> _robot.ExecuteMarketOrder(tradeType, symbolName, volume, label, stopLossPips, takeProfitPips, comment, hasTrailingStop);
public MyTestLogic(Robot robot) {
_robot = robot;
// --- パラメータがあれば引数で受け取ってセットしておく
// Period = period;
}
//------------------------------------------------
// 下記をそれぞれ本体の同名メソッド内で呼び出す
//------------------------------------------------
public void OnStart() {
}
public void OnTick() {
}
public void OnBar() {
}
public void OnStop() {
}
}
}
Robotクラスのプロパティやパラメータはほとんどpublicなので_robot.の形でアクセス可能ですが、いちいち書くのが面倒なので直接呼び出せるようにプロパティ化しておきます。
Robotのpublicメソッドも、PrintやExecuteMarketOrderなど頻繁に使うものは直接呼び出せるようにしておくと便利です。(他にも必要なものあれば加えてください。)
②ライブラリ側でロジックを実装する
中身を好きに作ります。ここはいつもと全く同じ。上記のOn~にロジックを書くだけです。必要なら別メソッドや別クラスも自由に作ってください。
一通りプロパティやメソッドを用意しておけば、普段cBotを作ってるときとほぼ同じ感覚でコーディングできると思います。
動作確認がてらこんなコードを書いてみました。毎ティックスプレッドをログに出力するという特に意味のないcBotです。
public void OnStart() {
Print($"{SymbolName}のチャートで開始しました。");
}
public void OnTick() {
Print($"スプレッド現在値{(Ask - Bid) / Symbol.PipSize:f1}pips");
}
public void OnBar() {
Print($"{Time}:ローソク足が更新されました");
}
public void OnStop() {
Print("停止しました");
}
ここまでできたらCtrl+Shift+Bを押してビルドしてライブラリ側は完成です。
③本体(algoファイル)を作って呼び出す
algoファイルを新しく作成します。MyLogicMainとでもしておきましょうか。
作成したらVisualStudioで開いて、参照に今作ったライブラリを追加します。
上記ライブラリを作った場所を開いて、MyLogic\bin\debugの中にMyLogic.dllがあると思うのでそれを参照します。
今回はこれだけでも動くのですが、あとあとのことを考えるとターゲットフレームワークも変更してライブラリ側に合わせておく方がいいでしょう。(上記参照)
あとは簡単、using MyLogicを冒頭で宣言して、本体側でMyTestLogicオブジェクト作って、各所でメソッド呼び出してあげるだけです。
using cAlgo.API;
using MyLogic;
namespace cAlgo.Robots {
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class MyLogicMain : Robot {
[Parameter(DefaultValue = 0.0)]
public double Parameter { get; set; }
MyTestLogic _logic;
protected override void OnStart() {
_logic = new MyTestLogic(this);
_logic.OnStart();
}
protected override void OnTick() {
_logic.OnTick();
}
protected override void OnBar() {
_logic.OnBar();
}
protected override void OnStop() {
_logic.OnStop();
}
}
}
上記の通り進めているのであれば、algoファイルの方はこれをコピペでいけるはずです。
基本こっちのファイルはパラメータを追加するときくらいしかいじることはありません。ここまでできたら閉じてしまっていいです。
注意点としては、ライブラリ側を変更してビルドしたら、本体(algoファイル)もビルドしないと変更が反映されません。
面倒に感じるかもしれませんが、本体(algoファイル)側のビルドはcTrader内のボタン一つで済むため、大した手間ではありません。。
お試しあれ
最初だけ少し面倒ですが、簡単なcBotやインジケーターならともかく大掛かりなものを作るときはこの方法を使った方が結果的に効率は上がります。
もしこの丸投げ型を頻繁に使うのであればライブラリ側のひな形をもう少し作り込んでおいた方がいいかもしれませんね。この辺はまた気が向いたら別で投稿します。