eb1181f5f7
* Adds preliminary universe selection for Future Options
* Fixes scaling issues with Future Options
* Fixes scaling multiplying by 10000x instead of using _scaleFactor
* Fixes scaling for Tick
* Revert changes to Tick since it divides the scaling factor
* Changes stale method name to new method name after rebase
* Fixes selection bugs, adds new methods, and adds unit tests
* Fixes bug where Equity Symbol was created for an underlying
non-equity Symbol, resulting in equity data trying to be loaded
* Adds unit tests covering changes to Tick, QuoteBar, TradeBar and
LeanData
* Adds regression test for AddUniverseOption filter contract selection
for Future Options
* Addresses review - modifies the AddFutureOption signature
* Adds new AddUniverseOptions method overload
* Removes and adds a new unit test
* Misc. modifications to account for new changes
* Fixes bug where futures were loaded using default SID Date
* Refactors and removes unnecessary work
* Fixes regression algorithm, which previously made no trades
* Adds future option data
* Adds the corresponding underlying data, in this case, futures data
to enable usage of future options data
* Replaces data with new data (ES18Z20)
* Improves Future chain filtering and updates regression stats
* Add AddFutureOptionContract API
* Expands regression and unit tests to test in finer detail
* Adds Python regression algorithms for AddFutureOption[Contract] methods
* Adds new unit test for BacktestingOptionChainProvider
* Fixes bug with BacktesingOptionChainProvider where we
attempted to load the Trades option chain first, resulting
in breakage of backwards compatibility and limitation of the
option chain.
* Adds new regression algorithms (Py) to Algorithm.Python project
* Adds FutureOptionMarginBuyingPowerModel
* Modifies code paths used to select margin model
* Adds related unit tests for margin model
* Fixes issue with unit test and MHDB/SPDB lookup for Future Options
* Preliminary regression algorithm testing ITM call/put option buying
* Fixes bug where fee model used did not find non-US market
options fee model. We now use the futures fee model for future
options because IB charges the same commissions per contract
between futures and futures options
* Adds proper regression algorithm for ITM future options expiration
* Pushing broken algorithm for review
* Currently, algorithm does not fill forward, causing
a single future option to not get exercised when it is delisted.
* Adds FutureOptionPutITMExpiryRegressionAlgorithm
* Improves existing regression algorithm for call side
* Fixes bug in existing regression algorithm
* Adds AAPL daily data to advance enumerator for ^^^ fix
* Adds additional future option regression algorithms
* Adds Buy OTM expiration regression algorithms
* Adds Sell ITM/OTM expiration regression algorithms
* Adds missing Python regression algorithms
* Adds remaining Python regression algorithms and fixes issues
* Fixes naming issues and statistics
* Adds short option OTM regression algorithms (Py)
* Add license header and class comments to python algorithms
* Cleans up comments and docstrings
* Create Buy/Sell call intraday regression algo
* Redirects future options symbol properties to futures symbol properties
* Asserts exercise/assignment price and updates stats in regression algos
* Adds new unit test covering changes to SecurityService
* Adds comments and fixes failing test
* Partially fixes future option mis-calculated profit/loss
* Adjusts portfolio model to calculate FOP as a no upfront pay asset class
* Updates regression algorithm statistics
* Begin IB FOP support
* Initial support for FOP IB data streaming, live í¾
* Adds additional functionality to LiveOptionChainProvider
- Allows querying CME API to retrieve option chains for CME products
- Ultimately, it's also the groundwork for the CME
LiveFutureChainProvider
* Edits IDataQueueUniverseProvider interface to provide greater
control to implementors of it
* Misc. bug fixes required to get FOP data streaming through IB
* Adds comments, adds missing rategate call, and cleans up code
* Force exchange for FOP and Futures when no exchange is provided
* Fixes bug with Portfolio modeling across all asset classes
* Adds LiveOptionChainProvider tests for Future Options
* IB brokerage option symbol bug fixes and improvements
* Fixes contract multiplier lookup bug
* Fixes issue where we attempted to subscribe to IB data feed with canonical security
* Adds ES MHDB entry
* Reverts portfolio modeling changes for Futures Options
* Since IB eats into our account's cash balance when
a new FOP contract is purchased, we must model by applying funds
to our cash whenever a new purchase/sell occurs.
If we choose to model FOPs exactly as we do with futures, we
will end up with an invalid TotalPortfolioValue on algorithm
restart. By all means and purposes, FOPs are modeled exactly
the same as equity options with respect to the portfolio.
* Adds comments clarifying portfolio modeling and clarifies
existing portfolio modeling comments with additional context.
* Fixes IB symbol lookup for future options
* Fixes LiveOptionChainProvider looping 5 times per option chain
request, even on success
* Sets OptionChainedUniverseSelectionModel to produce a canonical
future/future option/option Symbol to avoid creating two Symbols
* Adds GLOBEX future option symbol mapping from future -> fop
* Fixes LiveOptionChainProvider loading wrong contract option chains
* Fixes loading of futures options ZIP files when backtesting
* Adds a string -> decimal JSON converter
* Additional fixes/refactoring to the LiveOptionChainProvider
* Adds tests for changes to Symbol and LeanData
* Reverts changes to IB-symbol-map
* Fixes Value for mapped future options tickers
* Fixes Symbol test
* Changes path of future options to future's expiry date
* Extra changes made to remove scaling from writing CSV
* Added method to map from FOP Globex -> FUT Globex
* Fixes MOO and MOC orders for future options
* Note: this order type might not be supported by IB or CME.
* Bug fixes and updates unit tests
* Update regression tests and data format
* Rebase changes
* 1. Multiple bug fixes for LiveOptionChainProvider, reverts IQFeed changes
2. Address review (partial): Code reuse and cleanup
1.
* Modifies check in
`AddFutureOptionShort(Call|Put)ITMExpiryRegressionAlgorithm`
to ensure no buys have negative quantity
* Code reuse changes in IB brokerage
* Bug fix in IB brokerage where we assigned the FOP expiry
as the futures expiry (requires verification)
* Doc changes and adds missing summaries/license banners
* Disposes of HTTP client resources in LiveOptionChainProvider
* Renames classes and adds FutureOption folder in Common/Securities
2.
* We revert back to the quotes API for the option chain,
since the settlement API sometimes had missing strikes.
* Fixes future option expiry being set as future's expiry
in LiveOptionChainProvider
* Fixes bug where wrong option chain was selected because of bad
expiry lookup in the futures expiries returned from CME
* Fixes multiple looping bug in LiveOptionChainProvider
* Adds strike price scaling for LiveOptionChainProvider
* Reverts IQFeed changes and simplifies interface upgrade changes
Some additional challenges we'll have to solve as part of FOPs:
- The `OptionSymbol.IsStandard` method makes the assumption that
weeklies contracts follow the pattern equities follows, which
does not apply to Futures Options
- The Subscription created in:
`OptionChainUniverseSubscriptionEnumeratorFactory`
...adds a Trade config. For illiquid contracts, this
will delay universe selection for the option symbol
until we get a trade. However, if we add a quote config,
the data would instead be loaded based on the first quote
we received from the brokerage.
But since we're currently using a trade config, illiquid
contracts won't start streaming data until it receives a trade.
NOTE: this commit is a WIP to addressing the reviews received in the PR,
but has been committed early for efficiency in the review process
* Fixes regression algorithms and misc. bugs
* Fixes map file lookup for non-equity options
* Adds extra assertion at end of algorithm to ensure no holdings are
left when the algorithm ends.
* Adds FutureOptionSymbol, allowing all contracts through as standard
* Changes SPDB to allow defaulting to underlying future symbol
properties if no entry is found for the given FOP
* Fixes calls to SPDB in SecurityService, IBBrokerage
* Reverts AAPL daily ZIP file to fix majority of regression algorithms
* Adds FOPs symbol properties
* Fixes existing symbol properties for a few futures
* Adds tests for changes to Symbol Properties Database
* Removes string SPDB lookup method
* Updates tests and misc callees of previous method
* Updates all regression tests to use data of already expired contracts
* Adds Futures Options Expiry Functions tests
* Adds required futures data for 2020-01-05
* Address review (partial): Expands test coverage and fixes tests
* Set option chain tests parallelism to fixture only
* Fixes broken test for contract month delta for FuturesOptionsExpiryFunctions
* Changes delisting date logic for Futures Options
* Address review: removes duplicate code, misc code fixes
* Bug fix in MarketHoursDatabase.GetDatabaseSymbolKey() where
we would use the underlying's Symbol for lookup in the MHDB
* Adds missing license banner
* Removes Futures Options entries from MHDB
* Adds new tests
* Adds SecurityType.FutureOption
* Converts any underlying comparisons and uses SecurityType directly
instead for FOP specific behavior
* Extra code modifications to acommodate new SecurityType
* Addresses review: fixes order fee bug on exercise
* Additional bug fixes and adding of SecurityType.FutureOption
* Updates regression algorithms OrderListHash
* Fixes various bugs in IB live implementation
* Fixes bug setting the right contract expiration date for FOP
generated by LiveOptionChainProvider
* Adds new function to FuturesOptionsExpiryFunctions
* Clarifies parameter names better in some functions/methods
* Fixes bugs in IB brokerage for FOPs
* Address review - code cleanup and refactor
* Remove MappingEventProvider, SplitEventProvider, and
DividendEventProvider for Futures Options in
CorporateEventEnumeratorFactory
* Address review: Use MHDB key resolver in SPDB
* Makes regression tests pass and adds comment for expiry issue
* Fixes MHDB lookup on string symbol method
* Adds Futures Options greeks regression algorithm (C# only)
* Adds explanitory comment on MHDB FOP lookup
* Remove python from FutureOptionCallITMGreeksExpiryRegressionAlgorithm
847 lines
34 KiB
C#
847 lines
34 KiB
C#
/*
|
|
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
|
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
using NodaTime;
|
|
using QuantConnect.Configuration;
|
|
using QuantConnect.Data;
|
|
using QuantConnect.Data.Market;
|
|
using QuantConnect.Interfaces;
|
|
using QuantConnect.Logging;
|
|
using QuantConnect.Packets;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using QuantConnect.Util;
|
|
using HistoryRequest = QuantConnect.Data.HistoryRequest;
|
|
using Timer = System.Timers.Timer;
|
|
using System.Threading;
|
|
|
|
namespace QuantConnect.ToolBox.IQFeed
|
|
{
|
|
/// <summary>
|
|
/// IQFeedDataQueueHandler is an implementation of IDataQueueHandler and IHistoryProvider
|
|
/// </summary>
|
|
public class IQFeedDataQueueHandler : HistoryProviderBase, IDataQueueHandler, IDataQueueUniverseProvider
|
|
{
|
|
private bool _isConnected;
|
|
private int _dataPointCount;
|
|
private readonly HashSet<Symbol> _symbols;
|
|
private readonly Dictionary<Symbol, Symbol> _underlyings;
|
|
private readonly object _sync = new object();
|
|
private IQFeedDataQueueUniverseProvider _symbolUniverse;
|
|
|
|
//Socket connections:
|
|
private AdminPort _adminPort;
|
|
private Level1Port _level1Port;
|
|
private HistoryPort _historyPort;
|
|
|
|
private readonly IDataAggregator _aggregator = Composer.Instance.GetExportedValueByTypeName<IDataAggregator>(
|
|
Config.Get("data-aggregator", "QuantConnect.Lean.Engine.DataFeeds.AggregationManager"));
|
|
private readonly EventBasedDataQueueHandlerSubscriptionManager _subscriptionManager;
|
|
|
|
/// <summary>
|
|
/// Gets the total number of data points emitted by this history provider
|
|
/// </summary>
|
|
public override int DataPointCount => _dataPointCount;
|
|
|
|
/// <summary>
|
|
/// IQFeedDataQueueHandler is an implementation of IDataQueueHandler:
|
|
/// </summary>
|
|
public IQFeedDataQueueHandler()
|
|
{
|
|
_symbols = new HashSet<Symbol>();
|
|
_underlyings = new Dictionary<Symbol, Symbol>();
|
|
_subscriptionManager = new EventBasedDataQueueHandlerSubscriptionManager();
|
|
_subscriptionManager.SubscribeImpl += (s, t) =>
|
|
{
|
|
Subscribe(s);
|
|
return true;
|
|
};
|
|
|
|
_subscriptionManager.UnsubscribeImpl += (s, t) =>
|
|
{
|
|
Unsubscribe(s);
|
|
return true;
|
|
};
|
|
|
|
if (!IsConnected) Connect();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribe to the specified configuration
|
|
/// </summary>
|
|
/// <param name="dataConfig">defines the parameters to subscribe to a data feed</param>
|
|
/// <param name="newDataAvailableHandler">handler to be fired on new data available</param>
|
|
/// <returns>The new enumerator for this subscription request</returns>
|
|
public IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
|
|
{
|
|
if (!CanSubscribe(dataConfig.Symbol))
|
|
{
|
|
return Enumerable.Empty<BaseData>().GetEnumerator();
|
|
}
|
|
|
|
var enumerator = _aggregator.Add(dataConfig, newDataAvailableHandler);
|
|
_subscriptionManager.Subscribe(dataConfig);
|
|
|
|
return enumerator;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the specified symbols to the subscription: new IQLevel1WatchItem("IBM", true)
|
|
/// </summary>
|
|
/// <param name="symbols">The symbols to be added keyed by SecurityType</param>
|
|
public void Subscribe(IEnumerable<Symbol> symbols)
|
|
{
|
|
try
|
|
{
|
|
foreach (var symbol in symbols)
|
|
{
|
|
lock (_sync)
|
|
{
|
|
Log.Trace("IQFeed.Subscribe(): Subscribe Request: " + symbol.ToString());
|
|
|
|
if (_symbols.Add(symbol))
|
|
{
|
|
// processing canonical option symbol to subscribe to underlying prices
|
|
var subscribeSymbol = symbol;
|
|
|
|
if (symbol.ID.SecurityType == SecurityType.Option && symbol.IsCanonical())
|
|
{
|
|
subscribeSymbol = symbol.Underlying;
|
|
_underlyings.Add(subscribeSymbol, symbol);
|
|
}
|
|
|
|
if (symbol.ID.SecurityType == SecurityType.Future && symbol.IsCanonical())
|
|
{
|
|
// do nothing for now. Later might add continuous contract symbol.
|
|
return;
|
|
}
|
|
|
|
var ticker = _symbolUniverse.GetBrokerageSymbol(subscribeSymbol);
|
|
|
|
if (!string.IsNullOrEmpty(ticker))
|
|
{
|
|
_level1Port.Subscribe(ticker);
|
|
Log.Trace("IQFeed.Subscribe(): Subscribe Processed: {0} ({1})", symbol.Value, ticker);
|
|
}
|
|
else
|
|
{
|
|
Log.Error("IQFeed.Subscribe(): Symbol {0} was not found in IQFeed symbol universe", symbol.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception err)
|
|
{
|
|
Log.Error("IQFeed.Subscribe(): " + err.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the specified configuration
|
|
/// </summary>
|
|
/// <param name="dataConfig">Subscription config to be removed</param>
|
|
public void Unsubscribe(SubscriptionDataConfig dataConfig)
|
|
{
|
|
_subscriptionManager.Unsubscribe(dataConfig);
|
|
_aggregator.Remove(dataConfig);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the job we're subscribing for
|
|
/// </summary>
|
|
/// <param name="job">Job we're subscribing for</param>
|
|
public void SetJob(LiveNodePacket job)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the specified symbols to the subscription
|
|
/// </summary>
|
|
/// <param name="symbols">The symbols to be removed keyed by SecurityType</param>
|
|
public void Unsubscribe(IEnumerable<Symbol> symbols)
|
|
{
|
|
try
|
|
{
|
|
foreach (var symbol in symbols)
|
|
{
|
|
lock (_sync)
|
|
{
|
|
Log.Trace("IQFeed.Unsubscribe(): " + symbol.ToString());
|
|
|
|
_symbols.Remove(symbol);
|
|
|
|
var subscribeSymbol = symbol;
|
|
|
|
if (symbol.ID.SecurityType == SecurityType.Option && symbol.ID.StrikePrice == 0.0m)
|
|
{
|
|
subscribeSymbol = symbol.Underlying;
|
|
_underlyings.Remove(subscribeSymbol);
|
|
}
|
|
|
|
var ticker = _symbolUniverse.GetBrokerageSymbol(subscribeSymbol);
|
|
if (_level1Port.Contains(ticker))
|
|
{
|
|
_level1Port.Unsubscribe(ticker);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception err)
|
|
{
|
|
Log.Error("IQFeed.Unsubscribe(): " + err.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes this history provider to work for the specified job
|
|
/// </summary>
|
|
/// <param name="parameters">The initialization parameters</param>
|
|
public override void Initialize(HistoryProviderInitializeParameters parameters)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the history for the requested securities
|
|
/// </summary>
|
|
/// <param name="requests">The historical data requests</param>
|
|
/// <param name="sliceTimeZone">The time zone used when time stamping the slice instances</param>
|
|
/// <returns>An enumerable of the slices of data covering the span specified in each request</returns>
|
|
public override IEnumerable<Slice> GetHistory(IEnumerable<HistoryRequest> requests, DateTimeZone sliceTimeZone)
|
|
{
|
|
foreach (var request in requests)
|
|
{
|
|
foreach (var slice in _historyPort.ProcessHistoryRequests(request))
|
|
{
|
|
yield return slice;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates the connection is live.
|
|
/// </summary>
|
|
public bool IsConnected => _isConnected;
|
|
|
|
/// <summary>
|
|
/// Connect to the IQ Feed using supplied username and password information.
|
|
/// </summary>
|
|
private void Connect()
|
|
{
|
|
try
|
|
{
|
|
// Launch the IQ Feed Application:
|
|
Log.Trace("IQFeed.Connect(): Launching client...");
|
|
|
|
if (OS.IsWindows)
|
|
{
|
|
// IQConnect is only supported on Windows
|
|
var connector = new IQConnect(Config.Get("iqfeed-productName"), "1.0");
|
|
connector.Launch();
|
|
}
|
|
|
|
// Initialise one admin port
|
|
Log.Trace("IQFeed.Connect(): Connecting to admin...");
|
|
_adminPort = new AdminPort();
|
|
_adminPort.Connect();
|
|
_adminPort.SetAutoconnect();
|
|
_adminPort.SetClientStats(false);
|
|
_adminPort.SetClientName("Admin");
|
|
|
|
_adminPort.DisconnectedEvent += AdminPortOnDisconnectedEvent;
|
|
_adminPort.ConnectedEvent += AdminPortOnConnectedEvent;
|
|
|
|
_symbolUniverse = new IQFeedDataQueueUniverseProvider();
|
|
|
|
Log.Trace("IQFeed.Connect(): Connecting to L1 data...");
|
|
_level1Port = new Level1Port(_aggregator, _symbolUniverse);
|
|
_level1Port.Connect();
|
|
_level1Port.SetClientName("Level1");
|
|
|
|
Log.Trace("IQFeed.Connect(): Connecting to Historical data...");
|
|
_historyPort = new HistoryPort(_symbolUniverse);
|
|
_historyPort.Connect();
|
|
_historyPort.SetClientName("History");
|
|
|
|
_isConnected = true;
|
|
}
|
|
catch (Exception err)
|
|
{
|
|
Log.Error("IQFeed.Connect(): Error Connecting to IQFeed: " + err.Message);
|
|
_isConnected = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect from all ports we're subscribed to:
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Not being used. IQ automatically disconnect on killing LEAN
|
|
/// </remarks>
|
|
private void Disconnect()
|
|
{
|
|
if (_adminPort != null) _adminPort.Disconnect();
|
|
if (_level1Port != null) _level1Port.Disconnect();
|
|
_isConnected = false;
|
|
Log.Trace("IQFeed.Disconnect(): Disconnected");
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Returns true if this data provide can handle the specified symbol
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to be handled</param>
|
|
/// <returns>True if this data provider can get data for the symbol, false otherwise</returns>
|
|
private static bool CanSubscribe(Symbol symbol)
|
|
{
|
|
var market = symbol.ID.Market;
|
|
var securityType = symbol.ID.SecurityType;
|
|
|
|
if (symbol.Value.IndexOfInvariant("universe", true) != -1) return false;
|
|
|
|
return
|
|
(securityType == SecurityType.Equity && market == Market.USA) ||
|
|
(securityType == SecurityType.Forex && market == Market.FXCM) ||
|
|
(securityType == SecurityType.Option && market == Market.USA) ||
|
|
(securityType == SecurityType.Future);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Admin port is connected.
|
|
/// </summary>
|
|
private void AdminPortOnConnectedEvent(object sender, ConnectedEventArgs connectedEventArgs)
|
|
{
|
|
_isConnected = true;
|
|
Log.Error("IQFeed.AdminPortOnConnectedEvent(): ADMIN PORT CONNECTED!");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Admin port disconnected from the IQFeed server.
|
|
/// </summary>
|
|
private void AdminPortOnDisconnectedEvent(object sender, DisconnectedEventArgs disconnectedEventArgs)
|
|
{
|
|
_isConnected = false;
|
|
Log.Error("IQFeed.AdminPortOnDisconnectedEvent(): ADMIN PORT DISCONNECTED!");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method returns a collection of Symbols that are available at the data source.
|
|
/// </summary>
|
|
/// <param name="lookupName">String representing the name to lookup</param>
|
|
/// <param name="securityType">Expected security type of the returned symbols (if any)</param>
|
|
/// <param name="includeExpired">Include expired contracts</param>
|
|
/// <param name="securityCurrency">Expected security currency(if any)</param>
|
|
/// <param name="securityExchange">Expected security exchange name(if any)</param>
|
|
/// <returns>Symbol results</returns>
|
|
public IEnumerable<Symbol> LookupSymbols(string lookupName, SecurityType securityType, bool includeExpired, string securityCurrency = null, string securityExchange = null)
|
|
{
|
|
return _symbolUniverse.LookupSymbols(lookupName, securityType, includeExpired, securityCurrency, securityExchange);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method returns a collection of Symbols that are available at the data source.
|
|
/// </summary>
|
|
/// <param name="symbol">Symbol to lookup</param>
|
|
/// <param name="includeExpired">Include expired contracts</param>
|
|
/// <param name="securityCurrency">Expected security currency(if any)</param>
|
|
/// <returns>Symbol results</returns>
|
|
public IEnumerable<Symbol> LookupSymbols(Symbol symbol, bool includeExpired, string securityCurrency)
|
|
{
|
|
return LookupSymbols(symbol.ID.Symbol, symbol.SecurityType, includeExpired, securityCurrency);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns whether the time can be advanced or not.
|
|
/// </summary>
|
|
/// <param name="securityType">The security type</param>
|
|
/// <returns>true if the time can be advanced</returns>
|
|
public bool CanAdvanceTime(SecurityType securityType)
|
|
{
|
|
return _symbolUniverse.CanAdvanceTime(securityType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
_symbolUniverse.DisposeSafely();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Admin class type
|
|
/// </summary>
|
|
public class AdminPort : IQAdminSocketClient
|
|
{
|
|
public AdminPort()
|
|
: base(80)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Level 1 Data Request:
|
|
/// </summary>
|
|
public class Level1Port : IQLevel1Client
|
|
{
|
|
private int count;
|
|
private DateTime start;
|
|
private DateTime _feedTime;
|
|
private Stopwatch _stopwatch = new Stopwatch();
|
|
private readonly Timer _timer;
|
|
private readonly ConcurrentDictionary<string, double> _prices;
|
|
private readonly ConcurrentDictionary<string, int> _openInterests;
|
|
private readonly IQFeedDataQueueUniverseProvider _symbolUniverse;
|
|
private readonly IDataAggregator _aggregator;
|
|
private int _dataQueueCount;
|
|
|
|
public DateTime FeedTime
|
|
{
|
|
get
|
|
{
|
|
if (_feedTime == new DateTime()) return DateTime.Now;
|
|
return _feedTime.AddMilliseconds(_stopwatch.ElapsedMilliseconds);
|
|
}
|
|
set
|
|
{
|
|
_feedTime = value;
|
|
_stopwatch = Stopwatch.StartNew();
|
|
}
|
|
}
|
|
|
|
public Level1Port(IDataAggregator aggregator, IQFeedDataQueueUniverseProvider symbolUniverse)
|
|
: base(80)
|
|
{
|
|
start = DateTime.Now;
|
|
_prices = new ConcurrentDictionary<string, double>();
|
|
_openInterests = new ConcurrentDictionary<string, int>();
|
|
|
|
_aggregator = aggregator;
|
|
_symbolUniverse = symbolUniverse;
|
|
Level1SummaryUpdateEvent += OnLevel1SummaryUpdateEvent;
|
|
Level1TimerEvent += OnLevel1TimerEvent;
|
|
Level1ServerDisconnectedEvent += OnLevel1ServerDisconnected;
|
|
Level1ServerReconnectFailed += OnLevel1ServerReconnectFailed;
|
|
Level1UnknownEvent += OnLevel1UnknownEvent;
|
|
Level1FundamentalEvent += OnLevel1FundamentalEvent;
|
|
|
|
_timer = new Timer(1000);
|
|
_timer.Enabled = false;
|
|
_timer.AutoReset = true;
|
|
_timer.Elapsed += (sender, args) =>
|
|
{
|
|
var ticksPerSecond = count / (DateTime.Now - start).TotalSeconds;
|
|
int dataQueueCount = Interlocked.Exchange(ref _dataQueueCount, 0);
|
|
if (ticksPerSecond > 1000 || dataQueueCount > 31)
|
|
{
|
|
Log.Trace($"IQFeed.OnSecond(): Ticks/sec: {ticksPerSecond.ToStringInvariant("0000.00")} " +
|
|
$"Engine.Ticks.Count: {dataQueueCount} CPU%: {OS.CpuUsage.ToStringInvariant("0.0") + "%"}"
|
|
);
|
|
}
|
|
|
|
count = 0;
|
|
start = DateTime.Now;
|
|
};
|
|
|
|
_timer.Enabled = true;
|
|
}
|
|
|
|
private Symbol GetLeanSymbol(string ticker)
|
|
{
|
|
return _symbolUniverse.GetLeanSymbol(ticker, SecurityType.Base, null);
|
|
}
|
|
|
|
private void OnLevel1FundamentalEvent(object sender, Level1FundamentalEventArgs e)
|
|
{
|
|
// handle split data, they're only valid today, they'll show up around 4:45am EST
|
|
if (e.SplitDate1.Date == DateTime.Today && DateTime.Now.TimeOfDay.TotalHours <= 8) // they will always be sent premarket
|
|
{
|
|
// get the last price, if it doesn't exist then we'll just issue the split claiming the price was zero
|
|
// this should (ideally) never happen, but sending this without the price is much better then not sending
|
|
// it at all
|
|
double referencePrice;
|
|
_prices.TryGetValue(e.Symbol, out referencePrice);
|
|
|
|
var symbol = GetLeanSymbol(e.Symbol);
|
|
var split = new Split(symbol, FeedTime, (decimal)referencePrice, (decimal)e.SplitFactor1, SplitType.SplitOccurred);
|
|
Emit(split);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle a new price update packet:
|
|
/// </summary>
|
|
private void OnLevel1SummaryUpdateEvent(object sender, Level1SummaryUpdateEventArgs e)
|
|
{
|
|
// if ticker is not found, unsubscribe
|
|
if (e.NotFound) Unsubscribe(e.Symbol);
|
|
|
|
// only update if we have a value
|
|
if (e.Last == 0) return;
|
|
|
|
// only accept trade and B/A updates
|
|
if (e.TypeOfUpdate != Level1SummaryUpdateEventArgs.UpdateType.ExtendedTrade
|
|
&& e.TypeOfUpdate != Level1SummaryUpdateEventArgs.UpdateType.Trade
|
|
&& e.TypeOfUpdate != Level1SummaryUpdateEventArgs.UpdateType.Bid
|
|
&& e.TypeOfUpdate != Level1SummaryUpdateEventArgs.UpdateType.Ask) return;
|
|
|
|
count++;
|
|
var time = FeedTime;
|
|
var last = (decimal)(e.TypeOfUpdate == Level1SummaryUpdateEventArgs.UpdateType.ExtendedTrade ? e.ExtendedTradingLast : e.Last);
|
|
|
|
var symbol = GetLeanSymbol(e.Symbol);
|
|
|
|
TickType tradeType;
|
|
|
|
switch (symbol.ID.SecurityType)
|
|
{
|
|
// the feed time is in NYC/EDT, convert it into EST
|
|
case SecurityType.Forex:
|
|
|
|
time = FeedTime.ConvertTo(TimeZones.NewYork, TimeZones.EasternStandard);
|
|
// TypeOfUpdate always equal to UpdateType.Trade for FXCM, but the message contains B/A and last data
|
|
tradeType = TickType.Quote;
|
|
|
|
break;
|
|
|
|
// for all other asset classes we leave it as is (NYC/EDT)
|
|
default:
|
|
|
|
time = FeedTime;
|
|
tradeType = e.TypeOfUpdate == Level1SummaryUpdateEventArgs.UpdateType.Bid ||
|
|
e.TypeOfUpdate == Level1SummaryUpdateEventArgs.UpdateType.Ask ?
|
|
TickType.Quote :
|
|
TickType.Trade;
|
|
break;
|
|
}
|
|
|
|
var tick = new Tick(time, symbol, last, (decimal)e.Bid, (decimal)e.Ask)
|
|
{
|
|
AskSize = e.AskSize,
|
|
BidSize = e.BidSize,
|
|
Quantity = e.IncrementalVolume,
|
|
TickType = tradeType,
|
|
DataType = MarketDataType.Tick
|
|
};
|
|
Emit(tick);
|
|
_prices[e.Symbol] = e.Last;
|
|
|
|
if (symbol.ID.SecurityType == SecurityType.Option || symbol.ID.SecurityType == SecurityType.Future)
|
|
{
|
|
if (!_openInterests.ContainsKey(e.Symbol) || _openInterests[e.Symbol] != e.OpenInterest)
|
|
{
|
|
var oi = new OpenInterest(time, symbol, e.OpenInterest);
|
|
Emit(oi);
|
|
|
|
_openInterests[e.Symbol] = e.OpenInterest;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Emit(BaseData tick)
|
|
{
|
|
_aggregator.Update(tick);
|
|
Interlocked.Increment(ref _dataQueueCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the interal clock time.
|
|
/// </summary>
|
|
private void OnLevel1TimerEvent(object sender, Level1TimerEventArgs e)
|
|
{
|
|
//If there was a bad tick and the time didn't set right, skip setting it here and just use our millisecond timer to set the time from last time it was set.
|
|
if (e.DateTimeStamp != DateTime.MinValue)
|
|
{
|
|
FeedTime = e.DateTimeStamp;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Server has disconnected, reconnect.
|
|
/// </summary>
|
|
private void OnLevel1ServerDisconnected(object sender, Level1ServerDisconnectedArgs e)
|
|
{
|
|
Log.Error("IQFeed.OnLevel1ServerDisconnected(): LEVEL 1 PORT DISCONNECTED! " + e.TextLine);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Server has disconnected, reconnect.
|
|
/// </summary>
|
|
private void OnLevel1ServerReconnectFailed(object sender, Level1ServerReconnectFailedArgs e)
|
|
{
|
|
Log.Error("IQFeed.OnLevel1ServerReconnectFailed(): LEVEL 1 PORT DISCONNECT! " + e.TextLine);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Got a message we don't know about, log it for posterity.
|
|
/// </summary>
|
|
private void OnLevel1UnknownEvent(object sender, Level1TextLineEventArgs e)
|
|
{
|
|
Log.Error("IQFeed.OnUnknownEvent(): " + e.TextLine);
|
|
}
|
|
}
|
|
|
|
// this type is expected to be used for exactly one job at a time
|
|
public class HistoryPort : IQLookupHistorySymbolClient
|
|
{
|
|
private bool _inProgress;
|
|
private ConcurrentDictionary<string, HistoryRequest> _requestDataByRequestId;
|
|
private ConcurrentDictionary<string, List<BaseData>> _currentRequest;
|
|
private readonly string DataDirectory = Config.Get("data-directory", "../../../Data");
|
|
private readonly double MaxHistoryRequestMinutes = Config.GetDouble("max-history-minutes", 5);
|
|
private readonly IQFeedDataQueueUniverseProvider _symbolUniverse;
|
|
|
|
/// <summary>
|
|
/// ...
|
|
/// </summary>
|
|
public HistoryPort(IQFeedDataQueueUniverseProvider symbolUniverse)
|
|
: base(80)
|
|
{
|
|
_symbolUniverse = symbolUniverse;
|
|
_requestDataByRequestId = new ConcurrentDictionary<string, HistoryRequest>();
|
|
_currentRequest = new ConcurrentDictionary<string, List<BaseData>>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// ...
|
|
/// </summary>
|
|
public HistoryPort(IQFeedDataQueueUniverseProvider symbolUniverse, int maxDataPoints, int dataPointsPerSend)
|
|
: this(symbolUniverse)
|
|
{
|
|
MaxDataPoints = maxDataPoints;
|
|
DataPointsPerSend = dataPointsPerSend;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populate request data
|
|
/// </summary>
|
|
public IEnumerable<Slice> ProcessHistoryRequests(HistoryRequest request)
|
|
{
|
|
// skipping universe and canonical symbols
|
|
if (!CanHandle(request.Symbol) ||
|
|
(request.Symbol.ID.SecurityType == SecurityType.Option && request.Symbol.IsCanonical()) ||
|
|
(request.Symbol.ID.SecurityType == SecurityType.Future && request.Symbol.IsCanonical()))
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
// Set this process status
|
|
_inProgress = true;
|
|
|
|
var ticker = _symbolUniverse.GetBrokerageSymbol(request.Symbol);
|
|
var start = request.StartTimeUtc.ConvertFromUtc(TimeZones.NewYork);
|
|
DateTime? end = request.EndTimeUtc.ConvertFromUtc(TimeZones.NewYork);
|
|
// if we're within a minute of now, don't set the end time
|
|
if (request.EndTimeUtc >= DateTime.UtcNow.AddMinutes(-1))
|
|
{
|
|
end = null;
|
|
}
|
|
|
|
Log.Trace($"HistoryPort.ProcessHistoryJob(): Submitting request: {request.Symbol.SecurityType.ToStringInvariant()}-{ticker}: " +
|
|
$"{request.Resolution.ToStringInvariant()} {start.ToStringInvariant()}->{(end ?? DateTime.UtcNow.AddMinutes(-1)).ToStringInvariant()}"
|
|
);
|
|
|
|
int id;
|
|
var reqid = string.Empty;
|
|
|
|
switch (request.Resolution)
|
|
{
|
|
case Resolution.Tick:
|
|
id = RequestTickData(ticker, start, end, true);
|
|
reqid = CreateRequestID(LookupType.REQ_HST_TCK, id);
|
|
break;
|
|
case Resolution.Daily:
|
|
id = RequestDailyData(ticker, start, end, true);
|
|
reqid = CreateRequestID(LookupType.REQ_HST_DWM, id);
|
|
break;
|
|
default:
|
|
var interval = new Interval(GetPeriodType(request.Resolution), 1);
|
|
id = RequestIntervalData(ticker, interval, start, end, true);
|
|
reqid = CreateRequestID(LookupType.REQ_HST_INT, id);
|
|
break;
|
|
}
|
|
|
|
_requestDataByRequestId[reqid] = request;
|
|
|
|
while (_inProgress)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// After all data arrive, we pass it to the algorithm through memory and write to a file
|
|
foreach (var key in _currentRequest.Keys)
|
|
{
|
|
List<BaseData> tradeBars;
|
|
if (_currentRequest.TryRemove(key, out tradeBars))
|
|
{
|
|
foreach (var tradeBar in tradeBars)
|
|
{
|
|
// Returns IEnumerable<Slice> object
|
|
yield return new Slice(tradeBar.EndTime, new[] { tradeBar });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if this data provide can handle the specified symbol
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to be handled</param>
|
|
/// <returns>True if this data provider can get data for the symbol, false otherwise</returns>
|
|
private bool CanHandle(Symbol symbol)
|
|
{
|
|
var market = symbol.ID.Market;
|
|
var securityType = symbol.ID.SecurityType;
|
|
return
|
|
(securityType == SecurityType.Equity && market == Market.USA) ||
|
|
(securityType == SecurityType.Forex && market == Market.FXCM) ||
|
|
(securityType == SecurityType.Option && market == Market.USA) ||
|
|
(securityType == SecurityType.Future && IQFeedDataQueueUniverseProvider.FuturesExchanges.Values.Contains(market));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Created new request ID for a given lookup type (tick, intraday bar, daily bar)
|
|
/// </summary>
|
|
/// <param name="lookupType">Lookup type: REQ_HST_TCK (tick), REQ_HST_DWM (daily) or REQ_HST_INT (intraday resolutions)</param>
|
|
/// <param name="id">Sequential identifier</param>
|
|
/// <returns></returns>
|
|
private static string CreateRequestID(LookupType lookupType, int id)
|
|
{
|
|
return lookupType + id.ToStringInvariant("0000000");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method called when a new Lookup event is fired
|
|
/// </summary>
|
|
/// <param name="e">Received data</param>
|
|
protected override void OnLookupEvent(LookupEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
switch (e.Sequence)
|
|
{
|
|
case LookupSequence.MessageStart:
|
|
_currentRequest.AddOrUpdate(e.Id, new List<BaseData>());
|
|
break;
|
|
case LookupSequence.MessageDetail:
|
|
List<BaseData> current;
|
|
if (_currentRequest.TryGetValue(e.Id, out current))
|
|
{
|
|
HandleMessageDetail(e, current);
|
|
}
|
|
break;
|
|
case LookupSequence.MessageEnd:
|
|
_inProgress = false;
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
catch (Exception err)
|
|
{
|
|
Log.Error(err);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Put received data into current list of BaseData object
|
|
/// </summary>
|
|
/// <param name="e">Received data</param>
|
|
/// <param name="current">Current list of BaseData object</param>
|
|
private void HandleMessageDetail(LookupEventArgs e, List<BaseData> current)
|
|
{
|
|
var requestData = _requestDataByRequestId[e.Id];
|
|
var data = GetData(e, requestData);
|
|
if (data != null && data.Time != DateTime.MinValue)
|
|
{
|
|
current.Add(data);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transform received data into BaseData object
|
|
/// </summary>
|
|
/// <param name="e">Received data</param>
|
|
/// <param name="requestData">Request information</param>
|
|
/// <returns>BaseData object</returns>
|
|
private BaseData GetData(LookupEventArgs e, HistoryRequest requestData)
|
|
{
|
|
var isEquity = requestData.Symbol.SecurityType == SecurityType.Equity;
|
|
try
|
|
{
|
|
switch (e.Type)
|
|
{
|
|
case LookupType.REQ_HST_TCK:
|
|
var t = (LookupTickEventArgs)e;
|
|
var time = isEquity ? t.DateTimeStamp : t.DateTimeStamp.ConvertTo(TimeZones.NewYork, TimeZones.EasternStandard);
|
|
return new Tick(time, requestData.Symbol, (decimal)t.Last, (decimal)t.Bid, (decimal)t.Ask) { Quantity = t.LastSize };
|
|
case LookupType.REQ_HST_INT:
|
|
var i = (LookupIntervalEventArgs)e;
|
|
if (i.DateTimeStamp == DateTime.MinValue) return null;
|
|
var istartTime = i.DateTimeStamp - requestData.Resolution.ToTimeSpan();
|
|
if (!isEquity) istartTime = istartTime.ConvertTo(TimeZones.NewYork, TimeZones.EasternStandard);
|
|
return new TradeBar(istartTime, requestData.Symbol, (decimal)i.Open, (decimal)i.High, (decimal)i.Low, (decimal)i.Close, i.PeriodVolume);
|
|
case LookupType.REQ_HST_DWM:
|
|
var d = (LookupDayWeekMonthEventArgs)e;
|
|
if (d.DateTimeStamp == DateTime.MinValue) return null;
|
|
var dstartTime = d.DateTimeStamp.Date;
|
|
if (!isEquity) dstartTime = dstartTime.ConvertTo(TimeZones.NewYork, TimeZones.EasternStandard);
|
|
return new TradeBar(dstartTime, requestData.Symbol, (decimal)d.Open, (decimal)d.High, (decimal)d.Low, (decimal)d.Close, d.PeriodVolume, requestData.Resolution.ToTimeSpan());
|
|
|
|
// we don't need to handle these other types
|
|
case LookupType.REQ_SYM_SYM:
|
|
case LookupType.REQ_SYM_SIC:
|
|
case LookupType.REQ_SYM_NAC:
|
|
case LookupType.REQ_TAB_MKT:
|
|
case LookupType.REQ_TAB_SEC:
|
|
case LookupType.REQ_TAB_MKC:
|
|
case LookupType.REQ_TAB_SIC:
|
|
case LookupType.REQ_TAB_NAC:
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
catch (Exception err)
|
|
{
|
|
Log.Error("Encountered error while processing request: " + e.Id);
|
|
Log.Error(err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static PeriodType GetPeriodType(Resolution resolution)
|
|
{
|
|
switch (resolution)
|
|
{
|
|
case Resolution.Second:
|
|
return PeriodType.Second;
|
|
case Resolution.Minute:
|
|
return PeriodType.Minute;
|
|
case Resolution.Hour:
|
|
return PeriodType.Hour;
|
|
case Resolution.Tick:
|
|
case Resolution.Daily:
|
|
default:
|
|
throw new ArgumentOutOfRangeException("resolution", resolution, null);
|
|
}
|
|
}
|
|
}
|
|
}
|