//This event is raised when real time data arrives //We convert them and pass them on downstream private void _client_RealTimeBar(object sender, RealTimeBarEventArgs e) { RealTimeDataEventArgs args = TWSUtils.RealTimeDataEventArgsConverter(e); var originalRequest = _realTimeDataRequests[e.RequestId]; args.InstrumentID = originalRequest.Instrument.ID.Value; args.RequestID = _requestIDMap[e.RequestId]; RaiseEvent(DataReceived, this, args); }
/// <summary> /// This event is raised when historical data arrives from TWS /// </summary> private void _client_HistoricalData(object sender, Krs.Ats.IBNet.HistoricalDataEventArgs e) { //convert the bar and add it to the Dictionary of arrived data var bar = TWSUtils.HistoricalDataEventArgsToOHLCBar(e); int id; lock (_subReqMapLock) { //if the data is arriving for a sub-request, we must get the id of the original request first //otherwise it's just the same id id = _subRequestIDMap.ContainsKey(e.RequestId) ? _subRequestIDMap[e.RequestId] : e.RequestId; } //stocks need to have their volumes multiplied by 100, I think all other instrument types do not if (_historicalDataRequests[id].Instrument.Type == InstrumentType.Stock) { bar.Volume *= 100; } _arrivedHistoricalData[id].Add(bar); if (e.RecordNumber >= e.RecordTotal - 1) //this was the last item to receive for this request, send it to the broker { bool requestComplete = true; lock (_subReqMapLock) { if (_subRequestIDMap.ContainsKey(e.RequestId)) { _subRequestIDMap.Remove(e.RequestId); _subRequestCount[id]--; if (_subRequestCount[id] > 0) { //What happens here: this is a subrequest. //We check how many sub-requests in this group have been delivered. //if this is the last one, we want to call HistoricalDataRequestComplete() //otherwise there's more data to come, so we have to wait for it requestComplete = false; } else { //if it is complete, we'll need to sort the data because subrequests may not come in in order _arrivedHistoricalData[id] = _arrivedHistoricalData[id].OrderBy(x => x.DTOpen).ToList(); } } } if (requestComplete) { HistoricalDataRequestComplete(id); } } }
private void _client_HistoricalDataUpdate(object sender, QDMSIBClient.HistoricalDataEventArgs e) { if (e.Bar.Volume < 0) { return; } var originalRequest = _realTimeDataRequests[e.RequestId]; var args = TWSUtils.HistoricalDataEventArgsToRealTimeDataEventArgs(e, originalRequest.Instrument.ID.Value, _requestIDMap[e.RequestId]); RaiseEvent(DataReceived, this, args); }
/// <summary> /// This event is raised in the case of some error /// This includes pacing violations, in which case we re-enqueue the request. /// </summary> private void _client_Error(object sender, ErrorEventArgs e) { //if we asked for too much real time data at once, we need to re-queue the failed request if ((int)e.ErrorCode == 420) //a real time pacing violation { HandleRealTimePacingViolationError(e); } else if ((int)e.ErrorCode == 162) //a historical data pacing violation { HandleHistoricalDataPacingViolationError(e); } else if ((int)e.ErrorCode == 200) //No security definition has been found for the request. { HandleNoSecurityDefinitionError(e); } //different messages depending on the type of request var errorArgs = TWSUtils.ConvertErrorArguments(e); HistoricalDataRequest histReq; RealTimeDataRequest rtReq; var isHistorical = _historicalDataRequests.TryGetValue(e.TickerId, out histReq); if (isHistorical) { int origId = _subRequestIDMap.ContainsKey(histReq.RequestID) ? _subRequestIDMap[histReq.RequestID] : histReq.RequestID; errorArgs.ErrorMessage += string.Format(" Historical Req: {0} @ {1} From {2} To {3} - TickerId: {4} ReqID: {5}", histReq.Instrument.Symbol, histReq.Frequency, histReq.StartingDate, histReq.EndingDate, e.TickerId, histReq.RequestID); errorArgs.RequestID = origId; } else if (_realTimeDataRequests.TryGetValue(e.TickerId, out rtReq)) //it's a real time request { errorArgs.ErrorMessage += string.Format(" RT Req: {0} @ {1}", rtReq.Instrument.Symbol, rtReq.Frequency); errorArgs.RequestID = rtReq.RequestID; } RaiseEvent(Error, this, errorArgs); }
/// <summary> /// This event is raised when historical data arrives from TWS /// </summary> private void _client_HistoricalData(object sender, Krs.Ats.IBNet.HistoricalDataEventArgs e) { //convert the bar and add it to the Dictionary of arrived data var bar = TWSUtils.HistoricalDataEventArgsToOHLCBar(e); int id; lock (_subReqMapLock) { //if the data is arriving for a sub-request, we must get the id of the original request first //otherwise it's just the same id id = _subRequestIDMap.ContainsKey(e.RequestId) ? _subRequestIDMap[e.RequestId] : e.RequestId; } //stocks need to have their volumes multiplied by 100, I think all other instrument types do not if (_historicalDataRequests[id].Instrument.Type == InstrumentType.Stock) { bar.Volume *= 100; } _arrivedHistoricalData[id].Add(bar); if (e.RecordNumber >= e.RecordTotal - 1) //this was the last item to receive for this request, send it to the broker { bool requestComplete = true; lock (_subReqMapLock) { if (_subRequestIDMap.ContainsKey(e.RequestId)) { //If there are sub-requests, here we check if this is the last one requestComplete = ControlSubRequest(e.RequestId); if (requestComplete) { //If it was the last one, we need to order the data because sub-requests can arrive out of order _arrivedHistoricalData[id] = _arrivedHistoricalData[id].OrderBy(x => x.DTOpen).ToList(); } } } if (requestComplete) { HistoricalDataRequestComplete(id); } } }
/// <summary> /// real time data request /// </summary> public void RequestRealTimeData(RealTimeDataRequest request) { var id = _requestCounter++; lock (_requestIDMapLock) { _realTimeDataRequests.Add(id, request); _requestIDMap.Add(id, request.AssignedID); if (_reverseRequestIDMap.ContainsKey(request.AssignedID)) { _reverseRequestIDMap[request.AssignedID] = id; } else { _reverseRequestIDMap.Add(request.AssignedID, id); } } try { Contract contract = TWSUtils.InstrumentToContract(request.Instrument); if (_ibUseNewRealTimeDataSystem) { //the new system uses the historical data update endpoint instead of realtime data _client.RequestHistoricalData(id, contract, "", "60 S", QDMSIBClient.BarSize.FiveSeconds, HistoricalDataType.Trades, request.RTHOnly, true); //todo: write test } else { _client.RequestRealTimeBars( id, contract, "TRADES", request.RTHOnly); } } catch (Exception ex) { Log(LogLevel.Error, "IB: Could not send real time data request: " + ex.Message); RaiseEvent(Error, this, new ErrorArgs(-1, "Could not send real time data request: " + ex.Message)); } }
public void HistoricalRequestsAreNotSplitIfNotNecessary() { int[] requestCount = { 0 }; _ibClientMock.Setup( x => x.RequestHistoricalData( It.IsAny <int>(), It.IsAny <Contract>(), It.IsAny <DateTime>(), It.IsAny <string>(), It.IsAny <BarSize>(), It.IsAny <HistoricalDataType>(), It.IsAny <int>(), It.IsAny <List <TagValue> >())) .Callback(() => requestCount[0]++); var requests = new Dictionary <KeyValuePair <BarSize, int>, int> //left side is barsize/seconds, right side is expected splits { { new KeyValuePair <BarSize, int>(BarSize.OneDay, 300 * 24 * 3600), 1 }, { new KeyValuePair <BarSize, int>(BarSize.OneHour, 25 * 24 * 3600), 1 }, { new KeyValuePair <BarSize, int>(BarSize.ThirtyMinutes, 6 * 24 * 3600), 1 }, { new KeyValuePair <BarSize, int>(BarSize.OneMinute, 1 * 24 * 3600), 1 }, { new KeyValuePair <BarSize, int>(BarSize.ThirtySeconds, 21 * 3600), 1 }, { new KeyValuePair <BarSize, int>(BarSize.FifteenSeconds, 13400), 1 }, { new KeyValuePair <BarSize, int>(BarSize.FiveSeconds, 6900), 1 }, { new KeyValuePair <BarSize, int>(BarSize.OneSecond, 1500), 1 } }; var inst = new Instrument(); foreach (var kvp in requests) { _ibDatasource.RequestHistoricalData(new HistoricalDataRequest( inst, TWSUtils.BarSizeConverter(kvp.Key.Key), DateTime.Now.AddSeconds(-kvp.Key.Value), DateTime.Now, dataLocation: DataLocation.ExternalOnly)); Assert.AreEqual(kvp.Value, requestCount[0], kvp.Key.Key.ToString()); requestCount[0] = 0; } }
public void HistoricalRequestsAreSplitToRespectRequestLimits() { int[] requestCount = { 0 }; _ibClientMock.Setup(x => x.RequestHistoricalData( It.IsAny <int>(), It.IsAny <Contract>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <QDMSIBClient.BarSize>(), It.IsAny <HistoricalDataType>(), It.IsAny <bool>(), It.IsAny <bool>(), It.IsAny <List <TagValue> >())) .Callback(() => requestCount[0]++); var requests = new Dictionary <KeyValuePair <BarSize, int>, int> //left side is barsize/seconds, right side is expected splits { { new KeyValuePair <BarSize, int>(BarSize.OneDay, 500 * 24 * 3600), 2 }, { new KeyValuePair <BarSize, int>(BarSize.OneHour, 75 * 24 * 3600), 3 }, { new KeyValuePair <BarSize, int>(BarSize.ThirtyMinutes, 22 * 24 * 3600), 4 }, { new KeyValuePair <BarSize, int>(BarSize.OneMinute, 9 * 24 * 3600), 5 }, { new KeyValuePair <BarSize, int>(BarSize.ThirtySeconds, 40 * 3600), 2 }, { new KeyValuePair <BarSize, int>(BarSize.FifteenSeconds, 4 * 14400), 5 }, { new KeyValuePair <BarSize, int>(BarSize.FiveSeconds, 2 * 7200), 3 }, { new KeyValuePair <BarSize, int>(BarSize.OneSecond, 10 * 1800), 11 } }; var inst = new Instrument(); foreach (var kvp in requests) { _ibDatasource.RequestHistoricalData(new HistoricalDataRequest( inst, TWSUtils.BarSizeConverter(kvp.Key.Key), DateTime.Now.AddSeconds(-kvp.Key.Value), DateTime.Now, dataLocation: DataLocation.ExternalOnly)); Assert.AreEqual(kvp.Value, requestCount[0], kvp.Key.Key.ToString()); requestCount[0] = 0; } }
private void SendContractDetailsRequest(string symbol) { var contract = new Contract { Symbol = symbol, SecurityType = TWSUtils.SecurityTypeConverter(SelectedType), Exchange = SelectedExchange == "All" ? "" : SelectedExchange, IncludeExpired = IncludeExpired, Currency = Currency }; if (ExpirationDate.HasValue) { contract.Expiry = ExpirationDate.Value.ToString("yyyyMM"); } if (Strike.HasValue) { contract.Strike = Strike.Value; } SearchUnderway = true; //disables the search commands _client.RequestContractDetails(_nextRequestID, contract); }
/// <summary> /// Splits a historical data request into multiple pieces so that they obey the request limits /// </summary> private List <HistoricalDataRequest> SplitRequest(HistoricalDataRequest request) { var requests = new List <HistoricalDataRequest>(); //start at the end, and work backward in increments slightly lower than the max allowed time int step = (int)(TWSUtils.MaxRequestLength(request.Frequency) * .95); DateTime currentDate = request.EndingDate; while (currentDate > request.StartingDate) { var newReq = (HistoricalDataRequest)request.Clone(); newReq.EndingDate = currentDate; newReq.StartingDate = newReq.EndingDate.AddSeconds(-step); if (newReq.StartingDate < request.StartingDate) { newReq.StartingDate = request.StartingDate; } currentDate = currentDate.AddSeconds(-step); requests.Add(newReq); } return(requests); }
/// <summary> /// historical data request /// </summary> public void RequestHistoricalData(HistoricalDataRequest request) { //Historical data limitations: https://www.interactivebrokers.com/en/software/api/apiguide/api/historical_data_limitations.htm //the issue here is that the request may not be fulfilled...so we need to keep track of the request //and if we get an error regarding its failure, send it again using a timer int originalReqID = ++_requestCounter; _historicalDataRequests.TryAdd(originalReqID, request); _arrivedHistoricalData.TryAdd(originalReqID, new List <OHLCBar>()); //if necessary, chop up the request into multiple chunks so as to abide //the historical data limitations if (TWSUtils.RequestObeysLimits(request)) { //send the request, no need for subrequests SendHistoricalRequest(originalReqID, request); } else { //create subrequests, add them to the ID map, and send them to TWS var subRequests = SplitRequest(request); _subRequestCount.Add(originalReqID, subRequests.Count); foreach (HistoricalDataRequest subReq in subRequests) { lock (_subReqMapLock) { _requestCounter++; _historicalDataRequests.TryAdd(_requestCounter, subReq); _subRequestIDMap.Add(_requestCounter, originalReqID); SendHistoricalRequest(_requestCounter, subReq); } } } }
/// <summary> /// This event is raised in the case of some error /// This includes pacing violations, in which case we re-enqueue the request. /// </summary> private void _client_Error(object sender, ErrorEventArgs e) { //if we asked for too much real time data at once, we need to re-queue the failed request if ((int)e.ErrorCode == 420) //a real time pacing violation { lock (_queueLock) { if (!_realTimeRequestQueue.Contains(e.TickerId)) { //since the request did not succeed, what we do is re-queue it and it gets requested again by the timer _realTimeRequestQueue.Enqueue(e.TickerId); } } } else if ((int)e.ErrorCode == 162) //a historical data pacing violation { //turns out that more than one error uses the same error code! What were they thinking? if (e.ErrorMsg.StartsWith("Historical Market Data Service error message:HMDS query returned no data")) { //no data returned = we return an empty data set RaiseEvent(HistoricalDataArrived, this, new QDMS.HistoricalDataEventArgs( _historicalDataRequests[e.TickerId], new List <OHLCBar>())); } else if (e.ErrorMsg.StartsWith("Historical Market Data Service error message:No market data permissions")) { //We don't have permission to view this data, return an empty data set RaiseEvent(HistoricalDataArrived, this, new QDMS.HistoricalDataEventArgs( _historicalDataRequests[e.TickerId], new List <OHLCBar>())); } else { //simply a data pacing violation lock (_queueLock) { if (!_historicalRequestQueue.Contains(e.TickerId)) { //same as above _historicalRequestQueue.Enqueue(e.TickerId); } } } } else if ((int)e.ErrorCode == 200) //No security definition has been found for the request. { //Again multiple errors share the same code... if (e.ErrorMsg.Contains("No security definition has been found for the request")) { //this will happen for example when asking for data on expired futures //return an empty data list if (_historicalDataRequests.ContainsKey(e.TickerId)) { HistoricalDataRequestComplete(e.TickerId); } } else //in this case we're handling a "Invalid destination exchange specified" error { //not sure if there's anything else to do, if it's a real time request it just fails... if (_historicalDataRequests.ContainsKey(e.TickerId)) { HistoricalDataRequestComplete(e.TickerId); } } } //different messages depending on the type of request var errorArgs = TWSUtils.ConvertErrorArguments(e); HistoricalDataRequest histReq; RealTimeDataRequest rtReq; var isHistorical = _historicalDataRequests.TryGetValue(e.TickerId, out histReq); if (isHistorical) { errorArgs.ErrorMessage += string.Format(" Historical Req: {0} @ {1} From {2} To {3} - ID {4}", histReq.Instrument.Symbol, histReq.Frequency, histReq.StartingDate, histReq.EndingDate, histReq.RequestID); } else if (_realTimeDataRequests.TryGetValue(e.TickerId, out rtReq)) //it's a real time request { errorArgs.ErrorMessage += string.Format(" RT Req: {0} @ {1}", rtReq.Instrument.Symbol, rtReq.Frequency); } RaiseEvent(Error, this, errorArgs); }