private void StartSubscribes(long ctraderAccountId) { //get the associated user config UserConfig config = Users.Where(x => x.Value.AccountId == ctraderAccountId).Select(x => x.Value).FirstOrDefault(); if (config == null) { ErrorHandler?.Invoke("Could not find account " + ctraderAccountId + ". Ticks will not be recorded for this account."); return; } //start subscribing to the tick data var msgFactory = new OpenApiMessagesFactory(); foreach (Symbol s in config.Symbols) { _trasmitQueue.Enqueue(msgFactory.CreateSubscribeForSpotsRequest(ctraderAccountId, s.Id)); } StartWrites(config); }
private void StartWrites(UserConfig config) { //Start a tick writing thread to record all the recieved ticks that have been queued for this user Thread writeThread = new Thread(() => { try { WriteTicks(config); } catch (Exception e) { //shutdown the background threads ErrorHandler?.Invoke("Tick writer exception: " + e); //wait one second and try to start the write thread again Thread.Sleep(1000); StartWrites(config); } }); writeThread.Start(); }
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; } ; } } }
private void WriteTicks(UserConfig config) { while (!isShutdown) { //batch tick writes to a maximum of 10000 to avoid a continuos loop from huge volumes of ticks in the queue List <TickData> collectedTicks = new List <TickData>(); int tickCounter = 0; while (_ticksToWrite[config.Token].Count() > 0 && tickCounter < 10000) { //Dequeue the tick and add it to the collected ticks list for writing TickData data = _ticksToWrite[config.Token].Dequeue(); if (data != null) { collectedTicks.Add(data); } tickCounter++; } if (collectedTicks.Count > 0) { //loop through each symbol as each symbols data is written in separate files foreach (Symbol symbol in config.Symbols) { //group ticks by symbol, day and bid/ask and batch write these groups to file in one go. var ticks = collectedTicks.Where(x => x.SymbolId == symbol.Id); if (ticks.Count() == 0) { continue; } //create a dictionary where the key is the filename and the list is the list of ticks, on on each line, to write to the file. Dictionary <string, string> fileWrites = new Dictionary <string, string>(); foreach (TickData tick in ticks) { //build the filename from the symbol name, day of tick and wither bid or ask string key = tick.GetFilename(symbol.Name); //if the key doesn't exist create a new key with an empty list to add the tick strings to if (!fileWrites.ContainsKey(key)) { fileWrites.Add(key, ""); } fileWrites[key] += tick.ToString(); } //There would have been a new key created for every day for both bid and ask ticks (most likely only 2 - 4 keys in total if ticks overalp 2 days) //write all these to file foreach (KeyValuePair <string, string> kvp in fileWrites) { string path = config.DataPath + kvp.Key; if (!Directory.Exists(Path.GetDirectoryName(path))) { Directory.CreateDirectory(Path.GetDirectoryName(path)); } File.AppendAllText(path, kvp.Value); //extract the date out of the key to send to the SymbolWriteMessageHandler string dateString = kvp.Key.Split(new char[] { '\\' })[1].Split(new char[] { '_' })[0]; SymbolWriteMessageHandler?.Invoke(symbol.Name, symbol.Id, dateString, kvp.Key.Contains("Bid")); } } } //Give some time to build up some ticks again Thread.Sleep(1000); } }