
先日の記事で、MagicalNutsが日中足に対応したと書きました。
yooce.hatenablog.com
日中足に対応して何がしたかったかと言うと、暗号資産(仮想通貨)botです。参入時期としては、3周遅れくらいな気はしますが…。
暗号資産botはPythonで実装するのが一般的です。暗号資産取引所(特にCEX)は、板情報や注文、ポジションを取得できるAPIを提供しています。Web APIなのでどの言語でもコールできるのですが、Pythonによる実装が充実している印象です。例えば、複数の取引所のAPIコールをラップしたCCXTがあります。
github.com
最近では、まちゅけんさんによるpybottersもあります。
github.com
CCXTは広く使われ、実績のあるライブラリですし、pybottersは取引所が用意しているWebSocketによるリアルタイム通信に対応しているところが魅力です。(CCXTも対応しているが有料。)
「C#による投資検証ライブラリ」と銘打つからには、MagicalNutsも取引所のAPIやWebSocketに対応すべきなのでしょうが、私は取引所とのやり取りの部分は実績のあるPythonのライブラリを使うことにしました。
一方で、売買戦略の実装はMagicalNutsを使うことにしました。自分で言うのもなんですが、MagicalNutsの売買戦略の書き心地は結構気に入っているんですよね。これをASP.NET Coreでホスティングし、同じマシン内でC#をサーバー、Pythonをクライアントとして連携させます。実際の挙動は次のとおりです。
- CCXTまたはpybottersでロウソク足や板情報を取引所から取得
- 取引所から取得したデータをMagicalNuts.BackTest.BackTestStatusに合わせてシリアライズ
- PythonからASP.NET CoreにJSONを送信
- ASP.NET Coreで受信したJSONをBackTestStatusにデシリアライズ
- BackTestStatusと売買戦略を使って注文情報を生成
- 注文情報をシリアライズしてPythonに返信
- Pythonで受信した注文情報をCCXTやpybottersで取引所に送信
今回、MagicalNuts.Sample.CryptoBotSampleというサンプルを用意し、GitHubにプッシュしました。
github.com
C#サーバーの方は、ASP.NET CoreらしくDI(Dependency Injection)を使っています。まず、売買戦略を提供するIStrategyProvider
を定義します。
public interface IStrategyProvider
{
IStrategy Strategy { get; }
}
売買戦略のインターフェースMagicalNuts.BackTest.IStrategy
を持っているだけです。続いて、IStrategyProvider
を実装します。売買戦略クラスを切り替えられるようにジェネリッククラスとしています。
public class StrategyProvider<T> : IStrategyProvider where T : class, IStrategy, new()
{
private T _Strategy = null;
public IStrategy Strategy => _Strategy;
public StrategyProvider()
{
_Strategy = new T();
_Strategy.SetUpAsync().ConfigureAwait(false);
}
}
これを、ASP.NET CoreにDIします。
builder.Services.AddSingleton<IStrategyProvider, StrategyProvider<DonchianChannelBreakOut>>();
ここでは、MagicalNuts.Sample.BackTestSampleで実装したDonchianChannelBreakOut
をほぼそのまま使っています。実際、namespace以外変えていません。バックテスト時のロジックをそのまま使えるというのがこの方法の最大のメリットです。
最後にコントローラーを用意します。
[ApiController]
[Route("[controller]")]
public class StrategyController : Microsoft.AspNetCore.Mvc.Controller
{
[HttpPost]
public ActionResult Post([FromServices] IStrategyProvider strategyProvider, [FromBody] string data)
{
BackTestStatus state = Utf8Json.JsonSerializer.Deserialize<BackTestStatus>(data);
strategyProvider.Strategy.GetOrders(state, state.Orders);
return Ok(state.Orders);
}
}
DIからIStrategyProvider
を受け取り、MagicalNuts.BackTest.BackTestStatus
をクライアントからJSONで受け取ってデシリアライズします。これらを売買戦略に渡して注文を作り、それをステータスコードとともにシリアライズしてクライアントに返します。
Pythonクライアントの方は、pybottersの作者まちゅけんさんがDiscordで展開しておられたBybitのサンプルbotをベースにしています。DataStoreに溜めたロウソク足をMagicalNuts.Primitive.Candle
に沿ってシリアライズするのと、C#サーバーに送信するデータをMagicalNuts.BackTest.BackTestStatus
に沿ってシリアライズするところが主な変更箇所です。詳細はコードをご覧いただくのが良いと思います。
1点気を付けるところとして、このサンプルに動かすには、pybotters v0.10.0時点ではpybottesのbybit.pyのinitialize
で、
elif resp.url.path == "/v2/public/kline/list":
となっているところを、
elif resp.url.path == "/public/linear/kline":
のように修正する必要があります。(まちゅけんさんも把握しておられるのですぐ直るものと思います。)
私はこの仕組みで実際に暗号資産botを運用しています。繰り返しますが、この方法はバックテストで良好な結果が得られた売買戦略をそのまま実践に流用できることが最大のメリットです。一方、PythonクライアントとC#サーバー間でシリアライズ、デシリアライズを挟みますので、特に高頻度取引のようにアジリティが求められるケースでは、このあたりのオーバーヘッドを嫌って、Pythonで完結させる方が望ましいという考えもあるかと思います。