Automation of stock trading on the MICEX on the example of a terminal from Alfa Bank

In my free time I am engaged in the creation of trading robots. I have been interested in the topic of financial markets and trading automation for a long time, and today I am pleased to share an example of creating a simple robot using the example of a well-known exchange terminal from Alfa Bank.

Background


Many banks (and other companies) now provide brokerage services , which means that having concluded an additional agreement with the bank (except the main one), the client can invest his savings in various financial instruments. Quite a long time ago, trading terminals appeared - programs through which a bank client, having passed authorization, can submit applications, buy and sell. For example, stocks, futures, options.

As the market is in constant motion, prices are changing. Selling or buying an instrument at the right time, you can earn on the difference in rates. In order that a person does not have to constantly be at the computer and monitor the progress of trading, robotic programs are developed that work according to a predetermined algorithm - submit applications for purchase and sale, monitor the balance on accounts and evaluate the situation on the market. Such robots are configured initially and then only occasionally adjusted by humans, in the ideal case, of course. In fact, everything is much more complicated.

System description


The idea of ​​connecting to various trading terminals is not at all new, but it is ideal for automating user actions in client banking software. Despite the fact that I now have direct access to the Moscow Exchange using the FIX / FAST protocols, I check all trading strategies through a bank terminal, the software interaction with which in this article I want to show with the example of the Alpha Direct version 3.5 terminal.

Roughly speaking, the task boils down to the following (in steps):
  • Description of the interface for interaction with the terminal;
  • Receiving positions and historical data from a bank server;
  • Testing trading strategies on historical data;
  • Trade.

I want to note that the existing solution is constantly being developed by me. Guided by the principle "the simpler the better", I exclude much that has been added before (as unnecessary). Therefore, now the system, for example, is able to submit only limited orders, which are required by my trading strategies. Everything else is easily added as needed.

Interface for interacting with a bank terminal

To add a new communication method, you must implement the following interface:

    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 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);
    }

In addition to downloading historical data, connecting to the server and submitting orders, this interface implements read-only logical values ​​that allow the program to understand whether the plug-in allows loading history, checking strategies on historical data, and actually trading (see the figure below).

For example, Quick and Alpha Direct banking terminals can submit applications, but I have a module that only downloads historical data from one of the well-known exchange sites. Naturally, such an application module cannot be submitted.

UpdateInfo () is called in case of changes in quotes, balance and any other data, this allows the program to update the information that it receives from the connected module.

An additional class Positions describes current positions for each of the market instruments - by portfolio, market, ticker, in fact, balance, profit and loss for the day.

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; }
    }

Retrieving Positions and Historical Data

Below is the implementation of the class for interacting with the Alpha Direct version 3.5 terminal, in the project references you need to add the Interpop.ADLide COM module:

Source code for 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 MarketCodes = new Dictionary() {
            { "МБ ЦК", "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 LoadHistory(string marketNameCode, string instrument, int period, DateTime dateFrom, DateTime dateTo)
        {
            // Формат даты: 08.07.2010 15:22:21
            // получаем код торговой площадки
            marketNameCode = MarketCodes[marketNameCode];
            List data = new List();
            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();
            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
    }


In fact, market codes can also be obtained through the terminal itself. I was having problems handling OnConnectionChanged events, so I had to use a timer in addition.

Example of loading historical data


At the bottom, in different colors (depending on profit or loss per day) assets - stocks and money are shown.


Strategy Checks and Trading

A trading strategy receives data on the state of the market, conducts analysis, and, as a result, makes a purchase, sale, or is inactive, waiting for a better moment to enter the market.

For obvious reasons, I can’t provide the source code here, but I’ll say that at every moment of time the strategy receives information about current positions and any quotes for any time intervals, as well as about all placed orders for buying and selling. Conducts a trend analysis using different indicators. After that, a decision is made - to buy, sell or wait.

The result was a simple trading robot diagram, on which you can check any trading strategy. When testing on historical data, do not forget about the brokerage commission.

Thanks for attention.

If it was interesting, next time I can tell you about regression analysis, econometrics, some non-standard trend indicators, how to get exchange data from the Internet, about direct connection to exchanges and FIX / FAST protocols.

Also popular now: