アルファ銀行のターミナルを例に、MICEXでの株取引の自動化

空き時間には、取引ロボットの作成に取り組んでいます。 私は、金融市場と取引の自動化のトピックに長い間興味を持っていましたが、今日、Alfa Bankの有名な両替端末の例を使用して、簡単なロボットを作成する例を共有できます。



背景



多くの銀行(および他の企業)は現在、 仲介サービスを提供しています 。つまり、銀行との追加契約(メイン契約を除く)を締結することで、クライアントは貯蓄をさまざまな金融商品に投資できます。 かなり前に、取引端末が登場しました-承認を受けた銀行クライアントが申請書を提出し、売買できるプログラム。 たとえば、株式、先物、オプション。



市場が常に動いているため、価格は変化しています。 適切なタイミングで楽器を販売または購入すると、レートの差で稼ぐことができます。 人が常にコンピューターの前にいて、取引の進行状況を監視する必要がないように、所定のアルゴリズムに従って動作するロボットプログラムを開発します-売買の申請書を提出し、口座の残高を監視し、市場の状況を評価します。 そのようなロボットは最初に構成され、その後、もちろん理想的なケースでは人間によって時折調整されるだけです。 実際、すべてがはるかに複雑です。



システムの説明



さまざまなトレーディングターミナルに接続するという考え方はまったく新しいものではありませんが、クライアントバンキングソフトウェアでのユーザーアクションの自動化に最適です。 FIX / FASTプロトコルを使用してモスクワ取引所に直接アクセスできるようになったという事実にもかかわらず、銀行端末を介してすべての取引戦略を確認します。この記事では、Alpha Directバージョン3.5端末の例を使用してソフトウェア相互作用を示します。



大まかに言えば、タスクは次のように要約されます(ステップ)。



既存のソリューションは常に開発されていることに注意してください。 「単純であるほど良い」という原則に基づいて、以前に追加された(不必要なものとして)多くを除外します。 したがって、たとえば、現在のシステムでは、限られた注文のみを送信できます。これは、私の取引戦略に必要なものです。 その他はすべて、必要に応じて簡単に追加できます。



銀行端末と対話するためのインターフェース


新しい通信方法を追加するには、次のインターフェイスを実装する必要があります。



public interface IIntegrator { bool Connected {get; set;} void Initialize(); void Connect(string userName, string password); void Disconnect(); void Stop(); Action UpdateInfo { get; set; } bool AllowsHistory { get; } bool AllowsTesting { get; } bool AllowsTrading { get; } bool RequiresCredentials { get; } string[] AvaliableMarketCodes { get; } //Date, Open, High, Low, Close, Volume, Adj Close List<string[]> LoadHistory(string marketNameCode,string instrument,int period,DateTime dateFrom,DateTime dateTo); Helpers.Positions[] GetPositions(); double GetLastPrice(string code); int LimitSell(string briefCase, string ticker, string placeCode, int amount, double price, int timeOut, double? activateIfPriceHasGrown, double? activateIfPriceHasFallen); int LimitBuy(string briefCase, string ticker, string placeCode, int amount, double price, int timeOut, double? activateIfPriceHasGrown, double? activateIfPriceHasFallen); void DropOrder(int orderNo); }
      
      





履歴データのダウンロード、サーバーへの接続、注文の送信に加えて、このインターフェイスは読み取り専用の論理値を実装し、プラグインが履歴の読み込み、履歴データの戦略の確認、実際の取引を許可するかどうかをプログラムが理解できるようにします(下図を参照)。



たとえば、Quick and Alpha Direct銀行端末はアプリケーションを送信できますが、よく知られている交換サイトの1つから履歴データのみをダウンロードするモジュールがあります。 当然、そのようなアプリケーションモジュールは送信できません。



UpdateInfo()は、相場、残高、その他のデータが変更された場合に呼び出されます。これにより、プログラムは接続されたモジュールから受信した情報を更新できます。



追加のクラスポジションは、各市場商品の現在のポジションを、ポートフォリオ、マーケット、ティッカー、実際には、その日のバランス、損益に基づいて説明します。



 public class Positions { public string Briefcase { get; set; } public string MarketCode { get; set; } public string Ticker { get; set; } public double Amount { get; set; } public double DailyPL { get; set; } }
      
      





ポジションと履歴データの取得


以下は、Alpha Directバージョン3.5ターミナルと対話するためのクラスの実装です。プロジェクトリファレンスでは、Interpop.ADLide COMモジュールを追加する必要があります。



Alpha Direct 3.5のソースコード
  public class AlfaDirectIntegrator : IIntegrator, INotifyPropertyChanged { public bool Connected { get { if (_terminal == null) return false; return _terminal.Connected; } set { _terminal.Connected = value; OnPropertyChanged("Connected"); if (UpdateInfo!=null) UpdateInfo(); } } public Action UpdateInfo { get; set; } private AlfaDirectClass _terminal; public bool AllowsHistory { get { return true; } } public bool AllowsTesting { get { return true; } } public bool AllowsTrading { get { return true; } } public bool RequiresCredentials { get { return true; } } //        OnConnectionChanged //       //           private Timer _connectionStateChecker; private int _msToCheckConnectionState = 1000; //     public void Initialize() { _terminal = new AlfaDirectClass(); _connectionStateChecker = new Timer(_msToCheckConnectionState); _connectionStateChecker.Elapsed += _connectionStateChecker_Elapsed; _terminal.OnConnectionChanged += (obj) => { _connectionStateChecker_Elapsed(null, null); }; _connectionStateChecker.Enabled = true; } void _connectionStateChecker_Elapsed(object sender, ElapsedEventArgs e) { OnPropertyChanged("Connected"); UpdateInfo(); } public void Connect(string userName, string password) { _terminal.UserName = userName; _terminal.Password = password; Connected = true; } public void Disconnect() { Connected = false; } public void Stop() { if (_connectionStateChecker != null) { _connectionStateChecker.Enabled = false; _connectionStateChecker.Elapsed -= _connectionStateChecker_Elapsed; _connectionStateChecker = null; } _terminal = null; } public string[] AvaliableMarketCodes { get { return MarketCodes.Keys.ToArray(); } } private Dictionary<string, string> MarketCodes = new Dictionary<string, string>() { { " ", "MICEX_SHR_T" }, { "", "FORTS" }, { " ", "MICEX_SHR"}, { "FOREX", "CURRENCY"}, { "DJ Indexes", "DJIA"}, { " ", "EBONDS"}, { "OTC EUROCLEAR", "EUROTRADE"}, { ". ", "INDEX"}, { ". ", "INDEX2"}, { "IPE", "IPE"}, { "LME", "LME"}, { "LSE", "LSE"}, { "LSE(delay)", "LSE_DL"}, { " ", "MICEX_BOND"}, { " ", "MICEX_EBND"}, { " ", "MICEX_SELT"}, { "NEWEX", "NEWEX"}, { "-", "NONMARKET"}, { "- ()", "NONMARKET_DCC"}, { "NYSE", "NYSE"}, { " ()", "OTC_NDC"}, { " ()", "RTS_GAZP"}, { " ", "RTS_SGK_R"}, { "", "RTS_SHR"}, { " .", "RTS_STANDARD"} }; public List<string[]> LoadHistory(string marketNameCode, string instrument, int period, DateTime dateFrom, DateTime dateTo) { //  : 08.07.2010 15:22:21 //     marketNameCode = MarketCodes[marketNameCode]; List<string[]> data = new List<string[]>(); var rawData = (string)_terminal.GetArchiveFinInfo(marketNameCode, instrument, period, dateFrom, dateTo.AddDays(1), 2, 100); if (_terminal.LastResult != StateCodes.stcSuccess) { System.Windows.MessageBox.Show(_terminal.LastResultMsg,"",System.Windows.MessageBoxButton.OK,System.Windows.MessageBoxImage.Error); return data; } string[] stringSeparators = new string[] { "\r\n" }; //    var strings = rawData.Split(stringSeparators,StringSplitOptions.None).ToList(); foreach (var s in strings) if (s != "") { var values = s.Replace(',','.').Split('|'); data.Add(new string[] {values[0] , values[1], values[2], values[3], values[4], values[5], values[4] }); } //   return data; } public double GetLastPrice(string code) { var message = _terminal.GetLocalDBData("FIN_INFO", "last_price", "p_code like \'" + code + "\'"); if (message == null) return 0; return Convert.ToDouble(message.Split('|')[0]); } public int LimitSell(string briefCase, string ticker, string placeCode, int amount, double price, int timeOut, double? activateIfPriceHasGrown, double? activateIfPriceHasFallen) { return _terminal.CreateLimitOrder(briefCase, placeCode, ticker, DateTime.Now.AddMinutes(1), " Trade Robot System", "RUR", "S", amount, price, activateIfPriceHasGrown, activateIfPriceHasFallen, null, null, null, 'Y', null, null, null, null, null, null, null, null, null, null, timeOut); } public int LimitBuy(string briefCase, string ticker, string placeCode, int amount, double price, int timeOut, double? activateIfPriceHasGrown, double? activateIfPriceHasFallen) { return _terminal.CreateLimitOrder(briefCase, placeCode, ticker, DateTime.Now.AddMinutes(1), " Trade Robot System", "RUR", "B", amount, price, activateIfPriceHasGrown, activateIfPriceHasFallen, null, null, null, 'Y', null, null, null, null, null, null, null, null, null, null, timeOut); } public void DropOrder(int orderNo) { _terminal.DropOrder(orderNo, null, null, null, null, null, 0); } public Helpers.Positions[] GetPositions() { var result = new List<Helpers.Positions>(); var message = _terminal.GetLocalDBData("balance", "acc_code, p_code, place_code, forword_rest, daily_pl", ""); if ((message == null)||(!Connected)) return result.ToArray(); string[] stringSeparators = new string[] { "\r\n" }; //    var strings = message.Split(stringSeparators,StringSplitOptions.None).ToList(); foreach (var str in strings) if (str != "") { var fields = str.Split('|'); result.Add(new Helpers.Positions() { Briefcase = fields[0], Ticker = fields[1], MarketCode = fields[2], Amount = Convert.ToDouble(fields[3]), DailyPL = Convert.ToDouble(fields[4]) }); } return result.ToArray(); } #region PropertyChanged members public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName = "") { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion }
      
      







実際、市場コードは端末自体からも取得できます。 OnConnectionChangedイベントの処理に問題があったため、さらにタイマーを使用する必要がありました。



履歴データを読み込む例








以下では、さまざまな色(1日あたりの利益または損失に応じて)資産-株式とお金が表示されます。





戦略チェックと取引


取引戦略は、市場の状態に関するデータを取得して分析し、その結果、購入、販売、または非アクティブになり、市場に参入するのに最適な瞬間を待ちます。



明らかな理由により、ここでソースコードを提供することはできませんが、どの時点でも、戦略は現在のポジションと任意の時間間隔のクォートに関する情報、および売買のすべての注文に関する情報を受け取ると言います。 さまざまな指標を使用して傾向分析を実行します。 その後、決定が行われます-買うか、売るか、待つか。



その結果、簡単な取引ロボット図が作成され、その上で取引戦略を確認できます。 履歴データをテストするときは、仲介手数料を忘れないでください。



ご清聴ありがとうございました。



興味深い場合は、次回、回帰分析、計量経済学、いくつかの非標準的なトレンドインジケーター、インターネットからの交換データの取得方法、交換への直接接続およびFIX / FASTプロトコルについて説明します。



All Articles