예제 #1
0
        public JsonResult SendMarketOrder([FromBody] Params param)
        {
            _tcpClient = new TcpClient(param.ApiHost, param.ApiPort);;
            _apiSocket = new SslStream(_tcpClient.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
            _apiSocket.AuthenticateAsClient(param.ApiHost);
            SendAuthorizationRequest(param);
            List <string> data = new List <string>();

            ProtoTradeSide tradeType = new ProtoTradeSide();

            if (param.TradeSide.ToUpper() == "BUY")
            {
                tradeType = ProtoTradeSide.BUY;
            }
            if (param.TradeSide.ToUpper() == "SELL")
            {
                tradeType = ProtoTradeSide.SELL;
            }
            var msgFactory = new OpenApiMessagesFactory();
            var msg        = msgFactory.CreateMarketOrderRequest(param.AccountId, param.AccessToken, param.SymbolName, tradeType, param.Volume * 100,
                                                                 param.StopLossInPips, param.TakeProfitInPips, param.Comment);

            Transmit(msg);
            byte[] _message     = Listen(_apiSocket);
            var    protoMessage = msgFactory.GetMessage(_message);

            data.Add(OpenApiMessagesPresentation.ToString(protoMessage));

            Thread.Sleep(1000);
            _message     = Listen(_apiSocket);
            protoMessage = msgFactory.GetMessage(_message);
            data.Add(OpenApiMessagesPresentation.ToString(protoMessage));
            return(Json(new { data }));
        }
예제 #2
0
        static void ProcessIncomingDataStream(OpenApiMessagesFactory msgFactory, byte[] rawData)
        {
            var _msg = msgFactory.GetMessage(rawData);

            if (isDebugIsOn)
            {
                Console.WriteLine("ProcessIncomingDataStream() Message received:\n{0}", OpenApiMessagesPresentation.ToString(_msg));
            }

            if (!_msg.payloadSpecified)
            {
                return;
            }

            switch (_msg.payloadType)
            {
            case (int)ProtoPayloadType.HEARTBEAT_EVENT:
                break;

            case (int)ProtoOAPayloadType.OA_EXECUTION_EVENT:
                var _payload_msg = msgFactory.GetExecutionEvent(rawData);
                if (_payload_msg.position != null)
                {
                    testPositionId = _payload_msg.position.positionId;
                }

                break;

            default:
                break;
            }
            ;
        }
예제 #3
0
        private void SendAuthorizationRequest(Params param)
        {
            var msgFactory = new OpenApiMessagesFactory();
            var msg        = msgFactory.CreateAuthorizationRequest(param.ClientId, param.ClientSecret);

            Transmit(msg);
            byte[] _message     = Listen(_apiSocket);
            var    protoMessage = msgFactory.GetMessage(_message);
        }
예제 #4
0
        private void SendAuthorizationRequest()
        {
            var msgFactory = new OpenApiMessagesFactory();
            var msg        = msgFactory.CreateAuthorizationRequest(_clientId, _clientSecret);

            Transmit(msg);
            byte[] _message     = Listen(_apiSocket);
            var    protoMessage = msgFactory.GetMessage(_message);

            lblResponse.Text = OpenApiMessagesPresentation.ToString(protoMessage);
        }
예제 #5
0
        protected void btnSendPingRequest_Click(object sender, EventArgs e)
        {
            var msgFactory = new OpenApiMessagesFactory();
            var msg        = msgFactory.CreatePingRequest((ulong)DateTime.Now.Ticks);

            Transmit(msg);
            byte[] _message     = Listen(_apiSocket);
            var    protoMessage = msgFactory.GetMessage(_message);

            lblResponse.Text = OpenApiMessagesPresentation.ToString(protoMessage);
        }
예제 #6
0
        protected void btnSendGetAllSubscriptionsForSpotEventsRequest_Click(object sender, EventArgs e)
        {
            SendAuthorizationRequest();
            var msgFactory = new OpenApiMessagesFactory();
            var msg        = msgFactory.CreateGetAllSpotSubscriptionsRequest();

            Transmit(msg);
            byte[] _message     = Listen(_apiSocket);
            var    protoMessage = msgFactory.GetMessage(_message);

            lblResponse.Text = OpenApiMessagesPresentation.ToString(protoMessage);
        }
예제 #7
0
        protected void btnUnsubscribeForTradingEvents_Click(object sender, EventArgs e)
        {
            SendAuthorizationRequest();
            var accountID  = ddlTradingAccounts.SelectedValue;
            var msgFactory = new OpenApiMessagesFactory();
            var msg        = msgFactory.CreateUnsubscribeForTradingEventsRequest(Convert.ToInt32(accountID));

            Transmit(msg);
            byte[] _message     = Listen(_apiSocket);
            var    protoMessage = msgFactory.GetMessage(_message);

            lblResponse.Text = OpenApiMessagesPresentation.ToString(protoMessage);
        }
예제 #8
0
        protected void btnSendClosePositionRequest_Click(object sender, EventArgs e)
        {
            SendAuthorizationRequest();
            var accountID  = ddlTradingAccounts.SelectedValue;
            var token      = Session["Token"].ToString();
            var msgFactory = new OpenApiMessagesFactory();
            var msg        = msgFactory.CreateClosePositionRequest(Convert.ToInt32(accountID), token, 100, 100000);

            Transmit(msg);
            byte[] _message     = Listen(_apiSocket);
            var    protoMessage = msgFactory.GetMessage(_message);

            lblResponse.Text = OpenApiMessagesPresentation.ToString(protoMessage);
        }
예제 #9
0
        public JsonResult SendAmendPosition([FromBody] Params param)
        {
            _tcpClient = new TcpClient(param.ApiHost, param.ApiPort);;
            _apiSocket = new SslStream(_tcpClient.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
            _apiSocket.AuthenticateAsClient(param.ApiHost);
            SendAuthorizationRequest(param);
            List <string> data = new List <string>();

            var msgFactory = new OpenApiMessagesFactory();
            var msg        = msgFactory.CreateAmendPositionProtectionRequest(param.AccountId, param.AccessToken.ToString(), param.PositionId, param.StopLossPrice, param.TakeProfitPrice);

            Transmit(msg);
            byte[] _message     = Listen(_apiSocket);
            var    protoMessage = msgFactory.GetMessage(_message);

            data.Add(OpenApiMessagesPresentation.ToString(protoMessage));
            return(Json(new { data }));
        }
예제 #10
0
        static void ProcessIncomingDataStream(OpenApiMessagesFactory msgFactory, byte[] rawData)
        {
            var _msg = msgFactory.GetMessage(rawData);

#if TRACE_DATA_INCOMING
            if (isDebugIsOn)
            {
                Console.WriteLine("ProcessIncomingDataStream() Message received:\n{0}", OpenApiMessagesPresentation.ToString(_msg));
            }
#endif

            if (!_msg.HasPayload)
            {
                return;
            }

            switch (_msg.PayloadType)
            {
            case (int)OpenApiLib.ProtoPayloadType.HEARTBEAT_EVENT:
                break;

            case (int)OpenApiLib.ProtoOAPayloadType.OA_EXECUTION_EVENT:
                var _payload_msg = msgFactory.GetExecutionEvent(rawData);
                if (_payload_msg.HasOrder)
                {
                    testOrderId = _payload_msg.Order.OrderId;
                }
                if (_payload_msg.HasPosition)
                {
                    testPositionId = _payload_msg.Position.PositionId;
                }
                break;

            default:
                break;
            }
            ;
        }
예제 #11
0
        public JsonResult SendClosePosition([FromBody] Params param)
        {
            _tcpClient = new TcpClient(param.ApiHost, param.ApiPort);;
            _apiSocket = new SslStream(_tcpClient.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
            _apiSocket.AuthenticateAsClient(param.ApiHost);
            SendAuthorizationRequest(param);
            List <string> data = new List <string>();

            var msgFactory = new OpenApiMessagesFactory();

            if (!string.IsNullOrEmpty(param.SelectedType))
            {
                foreach (var p in param.SelectedPositions)
                {
                    var msg = msgFactory.CreateClosePositionRequest(param.AccountId, param.AccessToken, p.PositionId, p.Volume);
                    Transmit(msg);
                }
            }
            byte[] _message     = Listen(_apiSocket);
            var    protoMessage = msgFactory.GetMessage(_message);

            data.Add(OpenApiMessagesPresentation.ToString(protoMessage));
            return(Json(new { data }));
        }
예제 #12
0
        void ProcessIncomingDataStream(OpenApiMessagesFactory msgFactory, byte[] rawData)
        {
            var _msg = msgFactory.GetMessage(rawData);

#if TRACE_DATA_INCOMING
            if (isDebugIsOn)
            {
                //if (_msg.PayloadType == (int)OpenApiLib.ProtoOAPayloadType.OA_SPOT_EVENT)
                //{
                //    Console.Write(".");
                //}
                //else
                {
                    Console.WriteLine("ProcessIncomingDataStream() received: " + OpenApiMessagesPresentation.ToString(_msg));
                }
            }
#endif

            if (!_msg.HasPayload)
            {
                return;
            }

            var lastLastHeartBeatReceived = lastHeartBeatReceived;
            lastHeartBeatReceived = DateTime.UtcNow;

            switch (_msg.PayloadType)
            {
            case (int)OpenApiLib.ProtoPayloadType.PING_RES:
            {
                var payload = msgFactory.GetPingResponse(rawData);
                if (payload.HasTimestamp)
                {
                    ServerTime = new DateTime((long)payload.Timestamp);
                    Console.WriteLine("[time] " + Server.Time.ToShortTimeString());
                    //Server.Time = DateTime.FromFileTimeUtc();
                }
                break;
            }

            case (int)OpenApiLib.ProtoPayloadType.HEARTBEAT_EVENT:
                //var _payload_msg = msgFactory.GetHeartbeatEvent(rawData);
                var timeBetween = (lastHeartBeatReceived - lastLastHeartBeatReceived).TotalSeconds;
                Console.WriteLine($"<3 ({timeBetween.ToString("N1")}s)");
                break;

            case (int)OpenApiLib.ProtoOAPayloadType.OA_EXECUTION_EVENT:
            {
                var msg          = "";
                var _payload_msg = msgFactory.GetExecutionEvent(rawData);

                if (_payload_msg.HasReasonCode)
                {
                    var executionType = _payload_msg.ExecutionType.ToString().Replace("OA_ORDER_", "");
                    var reason        = _payload_msg.HasReasonCode ? $"({_payload_msg.ReasonCode})" : "";
                    msg += $"*** [EXECUTION: {executionType} {reason}] *** ";
                }

                if (_payload_msg.HasOrder)
                {
                    orderId = _payload_msg.Order.OrderId;
                    var slPrice    = _payload_msg.Order.HasStopLossPrice ? " sl:" + _payload_msg.Order.StopLossPrice : "";
                    var tpPrice    = _payload_msg.Order.HasTakeProfitPrice ? " tp:" + _payload_msg.Order.TakeProfitPrice : "";
                    var limitPrice = _payload_msg.Order.HasLimitPrice ? " limit:" + _payload_msg.Order.LimitPrice : "";
                    var stopPrice  = _payload_msg.Order.HasStopPrice ? " stop:" + _payload_msg.Order.StopPrice : "";
                    msg += $"[ORDER {orderId}] {_payload_msg.Order.TradeSide} {_payload_msg.Order.RequestedVolume} {_payload_msg.Order.SymbolName} {limitPrice}{stopPrice} {slPrice}{tpPrice}";
                }
                else if (_payload_msg.HasPosition)
                {
                    positionId = _payload_msg.Position.PositionId;
                    var p = _payload_msg.Position;
                    msg += $"[POSITION {positionId}] {p.TradeSide} {p.Volume} {p.SymbolName} @ {p.EntryPrice}";
                }
                else
                {
                }
                Console.WriteLine(msg);
            }
            break;

            case (int)OpenApiLib.ProtoOAPayloadType.OA_AUTH_RES:
                //var payload = msgFactory.GetAuthorizationResponse(rawData);
                IsAuthorized = true;
                Console.WriteLine("[authorized]");
                break;

            case (int)OpenApiLib.ProtoOAPayloadType.OA_SPOT_EVENT:
            {
                if (rawData.Length > 40)
                {
                    Console.WriteLine("================= GOT LONG SPOT EVENT: " + rawData.Length + " ===================");
                }
                var payload = msgFactory.GetSpotEvent(rawData);

                WriteUnknownFields(_msg.PayloadType, payload);

                //var timestamp = timestampField.VarintList[0];
                //var time = new DateTime(1970, 1, 1) + TimeSpan.FromMilliseconds(timestamp);
                var time = new DateTime(1970, 1, 1) + TimeSpan.FromMilliseconds(payload.Timestamp);

                if (payload.TrendbarCount > 0 || payload.TrendbarList.Count > 0)
                {
                    foreach (var bar in payload.TrendbarList)
                    {
                        Console.WriteLine($"*********************** TRENDBAR: {bar.Period} o:{bar.Open} h:{bar.High} l:{bar.Low} c:{bar.Close} [v:{bar.Volume}]");
                        if (bar.Period == ProtoOATrendbarPeriod.H1)
                        {
                            AccountStats.Increment(StatEventType.H1Bar);
                        }
                        else if (bar.Period == ProtoOATrendbarPeriod.H1)
                        {
                            AccountStats.Increment(StatEventType.H1Bar);
                        }
                        else
                        {
                            AccountStats.Increment(StatEventType.Other);
                        }
                        throw new Exception("***** got a trendbar!  Celebrate!");
                    }
                }
                var tick = new SymbolTick
                {
                    Symbol = payload.SymbolName,
                    Ask    = payload.HasAskPrice ? payload.AskPrice : double.NaN,
                    Bid    = payload.HasBidPrice ? payload.BidPrice : double.NaN,
                    Time   = time
                };
                if (payload.HasAskPrice || payload.HasBidPrice)
                {
                    AccountStats.Increment(StatEventType.Tick);
#if DEBUG
                    //if (AccountStats.Totals.Ticks % 100 == 0)
                    //{
                    //    Debug.WriteLine($"[stats] {AccountStats.Totals.Ticks} ticks received");
                    //}
#endif
                }

                var symbol = (ISymbolInternal)GetSymbol(payload.SymbolName);

                symbol.OnTick(tick);
                break;
            }

            case (int)OpenApiLib.ProtoOAPayloadType.OA_SUBSCRIBE_FOR_SPOTS_RES:
            {
                var payload = msgFactory.GetSubscribeForSpotsResponse(rawData);

                uint?subId = payload.HasSubscriptionId ? (uint?)payload.SubscriptionId : null;
#if TRACE_SUBSCRIPTIONS
                Console.WriteLine($"[SUBSCRIBED] {subId}");
#endif

#if GET_SUBS_AFTER_SUB
                SendGetSpotSubscriptionReq(subId);
                SendGetAllSpotSubscriptionsReq();
#endif

                WriteUnknownFields(_msg.PayloadType, payload);
                break;
            }

            case (int)OpenApiLib.ProtoOAPayloadType.OA_UNSUBSCRIBE_FROM_SPOTS_RES:
            {
                var payload = msgFactory.GetUnsubscribeFromSpotsResponse(rawData);

                //uint? subId = payload. ? (uint?)payload.SubscriptionId : null;
                Debug.WriteLine($"[UNSUBSCRIBED]");

#if GET_SUBS_AFTER_SUB
                SendGetAllSpotSubscriptionsReq();
#endif

                WriteUnknownFields(_msg.PayloadType, payload);

                break;
            }

            case (int)OpenApiLib.ProtoOAPayloadType.OA_GET_ALL_SPOT_SUBSCRIPTIONS_RES:
            {
#if TRACE_SUBSCRIPTIONS
                Debug.WriteLine($"--- GET_ALL_SPOT_SUBSCRIPTIONS_RES: ---");
                var payload = msgFactory.GetGetAllSpotSubscriptionsResponse(rawData);
                foreach (var x in payload.SpotSubscriptionsList)
                {
                    foreach (var y in x.SubscribedSymbolsList)
                    {
                        Debug.Write($" - subscription {x.SubscriptionId}: {y.SymbolName} periods: ");
                        foreach (var z in y.PeriodList)
                        {
                            Debug.Write($" {z.ToString()}");
                        }
                        Debug.WriteLine();
                    }
                }
                Debug.WriteLine($"--------------------------------------- ");
#endif
            }
            break;

            case (int)OpenApiLib.ProtoOAPayloadType.OA_GET_SPOT_SUBSCRIPTION_RES:
            {
#if TRACE_SUBSCRIPTIONS
                var payload = msgFactory.GetGetSpotSubscriptionResponse(rawData);
                Debug.WriteLine($"--- GET_SPOT_SUBSCRIPTION_RES for subscription {payload.SpotSubscription.SubscriptionId}: --- ");
                foreach (var y in payload.SpotSubscription.SubscribedSymbolsList)
                {
                    Debug.Write($" - {y.SymbolName} periods: ");
                    foreach (var z in y.PeriodList)
                    {
                        Debug.Write($"{z.ToString()} ");
                    }
                    Debug.WriteLine();
                }
                Debug.WriteLine($"------------------------------------------------------ ");
#endif
            }
            break;

            case (int)OpenApiLib.ProtoOAPayloadType.OA_SUBSCRIBE_FOR_TRADING_EVENTS_RES:
            {
                var payload = msgFactory.GetSubscribeForTradingEventsResponse(rawData);
                Console.WriteLine("[TRADE EVENTS] SUBSCRIBED");
            }
            break;

            default:
                Console.WriteLine("UNHANDLED MESSAGE: " + _msg.PayloadType);
                break;
            }
            ;
        }
예제 #13
0
        // listener thread
        private void Listen(SslStream sslStream, Queue messagesQueue)
        {
            isShutdown = false;
            while (!isShutdown)
            {
                Thread.Sleep(1);

                byte[] _length   = new byte[sizeof(int)];
                int    readBytes = 0;
                do
                {
                    Thread.Sleep(0);
                    readBytes += sslStream.Read(_length, readBytes, _length.Length - readBytes);
                } while (readBytes < _length.Length);

                int length = BitConverter.ToInt32(_length.Reverse().ToArray(), 0);
                if (length <= 0)
                {
                    continue;
                }

                if (length > MaxMessageSize)
                {
                    string exceptionMsg = "Message length " + length.ToString() + " is out of range (0 - " + MaxMessageSize.ToString() + ")";
                    throw new System.IndexOutOfRangeException();
                }

                byte[] _message = new byte[length];
                readBytes = 0;
                do
                {
                    Thread.Sleep(0);
                    readBytes += sslStream.Read(_message, readBytes, _message.Length - readBytes);
                } while (readBytes < length);
                var msgFactory   = new OpenApiMessagesFactory();
                var protoMessage = msgFactory.GetMessage(_message);
                messagesQueue.Enqueue("Received: " + OpenApiMessagesPresentation.ToString(protoMessage));
                switch ((ProtoOAPayloadType)protoMessage.PayloadType)
                {
                case ProtoOAPayloadType.PROTO_OA_EXECUTION_EVENT:
                    var _payload_msg = msgFactory.GetExecutionEvent(_message);
                    if (_payload_msg.HasOrder)
                    {
                        testOrderId = _payload_msg.Order.OrderId;
                    }
                    if (_payload_msg.HasPosition)
                    {
                        testPositionId = _payload_msg.Position.PositionId;
                    }
                    break;

                case ProtoOAPayloadType.PROTO_OA_GET_ACCOUNTS_BY_ACCESS_TOKEN_RES:
                    var _accounts_list = ProtoOAGetAccountListByAccessTokenRes.CreateBuilder().MergeFrom(protoMessage.Payload).Build();
                    _accounts = _accounts_list.CtidTraderAccountList;

                    break;

                case ProtoOAPayloadType.PROTO_OA_TRADER_RES:
                    var trader = ProtoOATraderRes.CreateBuilder().MergeFrom(protoMessage.Payload).Build();
                    _traders.Add(trader.Trader);
                    break;

                default:
                    break;
                }
                ;
            }
        }
예제 #14
0
        private void Listen(SslStream sslStream)
        {
            while (!isShutdown)
            {
                //Read the message into a proto message
                Thread.Sleep(1);

                byte[] _length   = new byte[sizeof(int)];
                int    readBytes = 0;
                do
                {
                    Thread.Sleep(0);
                    readBytes += sslStream.Read(_length, readBytes, _length.Length - readBytes);
                } while (readBytes < _length.Length);

                int length = BitConverter.ToInt32(_length.Reverse().ToArray(), 0);
                if (length <= 0)
                {
                    continue;
                }

                if (length > MaxMessageSize)
                {
                    string exceptionMsg = "Message length " + length.ToString() + " is out of range (0 - " + MaxMessageSize.ToString() + ")";
                    throw new System.IndexOutOfRangeException();
                }

                byte[] _message = new byte[length];
                readBytes = 0;
                do
                {
                    Thread.Sleep(0);
                    readBytes += sslStream.Read(_message, readBytes, _message.Length - readBytes);
                } while (readBytes < length);
                var msgFactory   = new OpenApiMessagesFactory();
                var protoMessage = msgFactory.GetMessage(_message);

                //recieved a msg so show View connection is still alive
                HeartBeatHandler?.Invoke();

                if (protoMessage.PayloadType > 49 && protoMessage.PayloadType < 54)
                {
                    switch ((ProtoPayloadType)protoMessage.PayloadType)
                    {
                    case ProtoPayloadType.ERROR_RES:
                        ErrorHandler?.Invoke(protoMessage.ToString());
                        break;

                    case ProtoPayloadType.HEARTBEAT_EVENT:
                        //heartbeat Event
                        HeartBeatHandler?.Invoke();
                        break;

                    case ProtoPayloadType.PING_REQ:
                        MessageHandler?.Invoke("Ping req");
                        break;

                    case ProtoPayloadType.PING_RES:
                        MessageHandler?.Invoke("Ping res");
                        break;
                    }
                }
                else
                {
                    //check what the message type is and perform the relevant operations
                    switch ((ProtoOAPayloadType)protoMessage.PayloadType)
                    {
                    case ProtoOAPayloadType.PROTO_OA_ERROR_RES:
                        //an error has been received
                        var error = ProtoOAErrorRes.CreateBuilder().MergeFrom(protoMessage.Payload).Build();
                        ErrorHandler?.Invoke("Proto message error " + error.ErrorCode + " " + error.Description);
                        break;

                    case ProtoOAPayloadType.PROTO_OA_ACCOUNT_AUTH_RES:
                        //auth has been recieved for the account
                        var auth = ProtoOAAccountAuthRes.CreateBuilder().MergeFrom(protoMessage.Payload).Build();

                        GetSymbols(auth.CtidTraderAccountId);
                        break;

                    case ProtoOAPayloadType.PROTO_OA_APPLICATION_AUTH_RES:
                        //Application has been authorised so continue the connection to get account and symbol data
                        MessageHandler?.Invoke("App authorised.");
                        BeginConnection();
                        break;

                    case ProtoOAPayloadType.PROTO_OA_SYMBOLS_LIST_RES:
                        //When requesting the list of all available assets
                        var symbols = ProtoOASymbolsListRes.CreateBuilder().MergeFrom(protoMessage.Payload).Build();

                        MessageHandler?.Invoke("Symbols downloaded for account " + symbols.CtidTraderAccountId);

                        //get the associated user
                        UserConfig config = Users.Where(x => x.Value.AccountId == symbols.CtidTraderAccountId).Select(x => x.Value).FirstOrDefault();

                        //store the symbols in a dictionary where the key is the id
                        foreach (ProtoOALightSymbol symbol in symbols.SymbolList)
                        {
                            config.Symbols.Add(new Symbol((int)symbol.SymbolId, symbol.SymbolName));
                        }

                        //Save to file so they can be easily reloaded on program restart
                        try
                        {
                            config.SaveToFile();
                        }
                        catch (IOException ex)     //non critical so just flag an error
                        {
                            ErrorHandler?.Invoke("Could not save symbols list for account id " + symbols.CtidTraderAccountId + ": " + ex.Message);
                        }

                        //start subscribing to tick events
                        StartSubscribes(symbols.CtidTraderAccountId);

                        break;

                    case ProtoOAPayloadType.PROTO_OA_SPOT_EVENT:
                        //Tick has been recieved
                        var details = ProtoOASpotEvent.CreateBuilder().MergeFrom(protoMessage.Payload).Build();

                        //record the time of the tick in UTC time (the tick time doesn't actually come with the payload)
                        DateTime tickTime = DateTime.UtcNow;

                        //get the associated user
                        UserConfig config_spot = Users.Where(x => x.Value.AccountId == details.CtidTraderAccountId).Select(x => x.Value).FirstOrDefault();

                        //Queue this for writing to file - queue as TickData class which also has the time at which the tick was recieved
                        if (details.HasBid)
                        {
                            _ticksToWrite[config_spot.Token].Enqueue(new TickData((int)details.SymbolId, tickTime, true, details.Bid));

                            //Notify a tick has been recieved
                            SymbolTickHandler?.Invoke(details.SymbolId, true, details.Bid, tickTime);
                        }
                        if (details.HasAsk)
                        {
                            _ticksToWrite[config_spot.Token].Enqueue(new TickData((int)details.SymbolId, tickTime, false, details.Ask));

                            //Notify a tick has been recieved
                            SymbolTickHandler?.Invoke(details.SymbolId, false, details.Ask, tickTime);
                        }



                        break;

                    case ProtoOAPayloadType.PROTO_OA_GET_ACCOUNTS_BY_ACCESS_TOKEN_RES:

                        var accounts_list = ProtoOAGetAccountListByAccessTokenRes.CreateBuilder().MergeFrom(protoMessage.Payload).Build();

                        //get the first account - we only need 1 account to extract tick data - no trading will take place
                        ProtoOACtidTraderAccount account = accounts_list.CtidTraderAccountList.FirstOrDefault();

                        //assign the account id that will be used to extract ticks (Users are stored as a dictionary with token as the key)
                        if (account != null)
                        {
                            Users[accounts_list.AccessToken].AccountId = (long)account.CtidTraderAccountId;
                        }
                        else
                        {
                            throw new MissingFieldException("There are no trading accounts associated with this token.");
                        }

                        MessageHandler?.Invoke("Account selected: " + account.CtidTraderAccountId);

                        //Save to file so it can be easily reloaded on program restart
                        try
                        {
                            Config.SaveToFile();
                        }
                        catch (IOException ex)     //non critical so just flag an error
                        {
                            ErrorHandler?.Invoke("Could not save config file with updated account id: " + ex.Message);
                        }

                        //get the symbols available to this account
                        AuthAccount(accounts_list.AccessToken);
                        break;

                    case ProtoOAPayloadType.PROTO_OA_SUBSCRIBE_SPOTS_RES:
                        var spotRes = ProtoOASubscribeSpotsRes.CreateBuilder().MergeFrom(protoMessage.Payload).Build();
                        break;

                    default:
                        ErrorHandler?.Invoke((ProtoOAPayloadType)protoMessage.PayloadType + " message not handled.");
                        break;
                    }
                    ;
                }
            }
        }