Compare commits
41 Commits
9879
...
feature-py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be6ead75e9 | ||
|
|
25f7ccf0c2 | ||
|
|
afb0a86291 | ||
|
|
f5c5faa3d4 | ||
|
|
c471d1ced0 | ||
|
|
5c345faea7 | ||
|
|
11bed1f0c3 | ||
|
|
ec57799a5d | ||
|
|
4c8aa638eb | ||
|
|
7c3caec07f | ||
|
|
45f71543bd | ||
|
|
13b9d91ecb | ||
|
|
f239018e77 | ||
|
|
f1c98b848a | ||
|
|
2c7bde422a | ||
|
|
9e95bfea90 | ||
|
|
b820ff4de8 | ||
|
|
8218a2be99 | ||
|
|
9e3031c562 | ||
|
|
2cf9239b6e | ||
|
|
4c63c60a89 | ||
|
|
9a2e47b05c | ||
|
|
711d46d6e2 | ||
|
|
5d6625c1ea | ||
|
|
0a70611d81 | ||
|
|
43c6d5cc5a | ||
|
|
f39acceadc | ||
|
|
921ddced04 | ||
|
|
ff8563244f | ||
|
|
041f1d9db5 | ||
|
|
eb1181f5f7 | ||
|
|
9d4014b7db | ||
|
|
a4f66628fd | ||
|
|
b9974e6f54 | ||
|
|
474c5cd890 | ||
|
|
0dfb368cb4 | ||
|
|
fbf5300bb6 | ||
|
|
c67845bd45 | ||
|
|
54ddbfbe24 | ||
|
|
bd7be31ede | ||
|
|
041a111b92 |
@@ -16,6 +16,7 @@ before_install:
|
||||
- conda install -y cython=0.29.15
|
||||
- conda install -y scipy=1.4.1
|
||||
- conda install -y wrapt=1.12.1
|
||||
- pip install pyarrow==1.0.1
|
||||
install:
|
||||
- nuget restore QuantConnect.Lean.sln
|
||||
- nuget install NUnit.Runners -Version 3.11.1 -OutputDirectory testrunner
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests that we receive the expected data when
|
||||
/// we add future option contracts individually using <see cref="AddFutureOptionContract"/>
|
||||
/// </summary>
|
||||
public class AddFutureOptionContractDataStreamingRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private bool _onDataReached;
|
||||
private bool _invested;
|
||||
private Symbol _es20h20;
|
||||
private Symbol _es19m20;
|
||||
|
||||
private readonly HashSet<Symbol> _symbolsReceived = new HashSet<Symbol>();
|
||||
private readonly HashSet<Symbol> _expectedSymbolsReceived = new HashSet<Symbol>();
|
||||
private readonly Dictionary<Symbol, List<QuoteBar>> _dataReceived = new Dictionary<Symbol, List<QuoteBar>>();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 1, 6);
|
||||
|
||||
_es20h20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 3, 20)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
var optionChains = OptionChainProvider.GetOptionContractList(_es20h20, Time)
|
||||
.Concat(OptionChainProvider.GetOptionContractList(_es19m20, Time));
|
||||
|
||||
foreach (var optionContract in optionChains)
|
||||
{
|
||||
_expectedSymbolsReceived.Add(AddFutureOptionContract(optionContract, Resolution.Minute).Symbol);
|
||||
}
|
||||
|
||||
if (_expectedSymbolsReceived.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Expected Symbols receive count is 0, expected >0");
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (!data.HasData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_onDataReached = true;
|
||||
|
||||
var hasOptionQuoteBars = false;
|
||||
foreach (var qb in data.QuoteBars.Values)
|
||||
{
|
||||
if (qb.Symbol.SecurityType != SecurityType.FutureOption)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
hasOptionQuoteBars = true;
|
||||
|
||||
_symbolsReceived.Add(qb.Symbol);
|
||||
if (!_dataReceived.ContainsKey(qb.Symbol))
|
||||
{
|
||||
_dataReceived[qb.Symbol] = new List<QuoteBar>();
|
||||
}
|
||||
|
||||
_dataReceived[qb.Symbol].Add(qb);
|
||||
}
|
||||
|
||||
if (_invested || !hasOptionQuoteBars)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.ContainsKey(_es20h20) && data.ContainsKey(_es19m20))
|
||||
{
|
||||
SetHoldings(_es20h20, 0.2);
|
||||
SetHoldings(_es19m20, 0.2);
|
||||
|
||||
_invested = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
base.OnEndOfAlgorithm();
|
||||
|
||||
if (!_onDataReached)
|
||||
{
|
||||
throw new Exception("OnData() was never called.");
|
||||
}
|
||||
if (_symbolsReceived.Count != _expectedSymbolsReceived.Count)
|
||||
{
|
||||
throw new AggregateException($"Expected {_expectedSymbolsReceived.Count} option contracts Symbols, found {_symbolsReceived.Count}");
|
||||
}
|
||||
|
||||
var missingSymbols = new List<Symbol>();
|
||||
foreach (var expectedSymbol in _expectedSymbolsReceived)
|
||||
{
|
||||
if (!_symbolsReceived.Contains(expectedSymbol))
|
||||
{
|
||||
missingSymbols.Add(expectedSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingSymbols.Count > 0)
|
||||
{
|
||||
throw new Exception($"Symbols: \"{string.Join(", ", missingSymbols)}\" were not found in OnData");
|
||||
}
|
||||
|
||||
foreach (var expectedSymbol in _expectedSymbolsReceived)
|
||||
{
|
||||
var data = _dataReceived[expectedSymbol];
|
||||
var nonDupeDataCount = data.Select(x =>
|
||||
{
|
||||
x.EndTime = default(DateTime);
|
||||
return x;
|
||||
}).Distinct().Count();
|
||||
|
||||
if (nonDupeDataCount < 1000)
|
||||
{
|
||||
throw new Exception($"Received too few data points. Expected >=1000, found {nonDupeDataCount} for {expectedSymbol}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "217.585%"},
|
||||
{"Drawdown", "0.600%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "0.635%"},
|
||||
{"Sharpe Ratio", "0"},
|
||||
{"Probabilistic Sharpe Ratio", "0%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0"},
|
||||
{"Beta", "0"},
|
||||
{"Annual Standard Deviation", "0"},
|
||||
{"Annual Variance", "0"},
|
||||
{"Information Ratio", "-14.395"},
|
||||
{"Tracking Error", "0.043"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$7.40"},
|
||||
{"Fitness Score", "1"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "79228162514264337593543950335"},
|
||||
{"Portfolio Turnover", "3.199"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1074366800"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Future;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests that we only receive the option chain for a single future contract
|
||||
/// in the option universe filter.
|
||||
/// </summary>
|
||||
public class AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private bool _invested;
|
||||
private bool _onDataReached;
|
||||
private bool _optionFilterRan;
|
||||
private readonly HashSet<Symbol> _symbolsReceived = new HashSet<Symbol>();
|
||||
private readonly HashSet<Symbol> _expectedSymbolsReceived = new HashSet<Symbol>();
|
||||
private readonly Dictionary<Symbol, List<QuoteBar>> _dataReceived = new Dictionary<Symbol, List<QuoteBar>>();
|
||||
|
||||
private Future _es;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 1, 6);
|
||||
|
||||
_es = AddFuture(Futures.Indices.SP500EMini, Resolution.Minute, Market.CME);
|
||||
_es.SetFilter((futureFilter) =>
|
||||
{
|
||||
return futureFilter.Expiration(0, 365).ExpirationCycle(new[] { 3, 6 });
|
||||
});
|
||||
|
||||
AddFutureOption(_es.Symbol, optionContracts =>
|
||||
{
|
||||
_optionFilterRan = true;
|
||||
|
||||
var expiry = new HashSet<DateTime>(optionContracts.Select(x => x.Underlying.ID.Date)).SingleOrDefault();
|
||||
// Cast to IEnumerable<Symbol> because OptionFilterContract overrides some LINQ operators like `Select` and `Where`
|
||||
// and cause it to mutate the underlying Symbol collection when using those operators.
|
||||
var symbol = new HashSet<Symbol>(((IEnumerable<Symbol>)optionContracts).Select(x => x.Underlying)).SingleOrDefault();
|
||||
|
||||
if (expiry == null || symbol == null)
|
||||
{
|
||||
throw new InvalidOperationException("Expected a single Option contract in the chain, found 0 contracts");
|
||||
}
|
||||
|
||||
var enumerator = optionContracts.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
_expectedSymbolsReceived.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
return optionContracts;
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (!data.HasData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_onDataReached = true;
|
||||
|
||||
var hasOptionQuoteBars = false;
|
||||
foreach (var qb in data.QuoteBars.Values)
|
||||
{
|
||||
if (qb.Symbol.SecurityType != SecurityType.FutureOption)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
hasOptionQuoteBars = true;
|
||||
|
||||
_symbolsReceived.Add(qb.Symbol);
|
||||
if (!_dataReceived.ContainsKey(qb.Symbol))
|
||||
{
|
||||
_dataReceived[qb.Symbol] = new List<QuoteBar>();
|
||||
}
|
||||
|
||||
_dataReceived[qb.Symbol].Add(qb);
|
||||
}
|
||||
|
||||
if (_invested || !hasOptionQuoteBars)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var chain in data.OptionChains.Values)
|
||||
{
|
||||
var futureInvested = false;
|
||||
var optionInvested = false;
|
||||
|
||||
foreach (var option in chain.Contracts.Keys)
|
||||
{
|
||||
if (futureInvested && optionInvested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var future = option.Underlying;
|
||||
|
||||
if (!optionInvested && data.ContainsKey(option))
|
||||
{
|
||||
MarketOrder(option, 1);
|
||||
_invested = true;
|
||||
optionInvested = true;
|
||||
}
|
||||
if (!futureInvested && data.ContainsKey(future))
|
||||
{
|
||||
MarketOrder(future, 1);
|
||||
_invested = true;
|
||||
futureInvested = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
base.OnEndOfAlgorithm();
|
||||
|
||||
if (!_optionFilterRan)
|
||||
{
|
||||
throw new InvalidOperationException("Option chain filter was never ran");
|
||||
}
|
||||
if (!_onDataReached)
|
||||
{
|
||||
throw new Exception("OnData() was never called.");
|
||||
}
|
||||
if (_symbolsReceived.Count != _expectedSymbolsReceived.Count)
|
||||
{
|
||||
throw new AggregateException($"Expected {_expectedSymbolsReceived.Count} option contracts Symbols, found {_symbolsReceived.Count}");
|
||||
}
|
||||
|
||||
var missingSymbols = new List<Symbol>();
|
||||
foreach (var expectedSymbol in _expectedSymbolsReceived)
|
||||
{
|
||||
if (!_symbolsReceived.Contains(expectedSymbol))
|
||||
{
|
||||
missingSymbols.Add(expectedSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingSymbols.Count > 0)
|
||||
{
|
||||
throw new Exception($"Symbols: \"{string.Join(", ", missingSymbols)}\" were not found in OnData");
|
||||
}
|
||||
|
||||
foreach (var expectedSymbol in _expectedSymbolsReceived)
|
||||
{
|
||||
var data = _dataReceived[expectedSymbol];
|
||||
var nonDupeDataCount = data.Select(x =>
|
||||
{
|
||||
x.EndTime = default(DateTime);
|
||||
return x;
|
||||
}).Distinct().Count();
|
||||
|
||||
if (nonDupeDataCount < 1000)
|
||||
{
|
||||
throw new Exception($"Received too few data points. Expected >=1000, found {nonDupeDataCount} for {expectedSymbol}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "-15.625%"},
|
||||
{"Drawdown", "0.200%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "-0.093%"},
|
||||
{"Sharpe Ratio", "-11.181"},
|
||||
{"Probabilistic Sharpe Ratio", "0%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0.002"},
|
||||
{"Beta", "-0.016"},
|
||||
{"Annual Standard Deviation", "0.001"},
|
||||
{"Annual Variance", "0"},
|
||||
{"Information Ratio", "-14.343"},
|
||||
{"Tracking Error", "0.044"},
|
||||
{"Treynor Ratio", "0.479"},
|
||||
{"Total Fees", "$3.70"},
|
||||
{"Fitness Score", "0.41"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-185.654"},
|
||||
{"Portfolio Turnover", "0.821"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1532330301"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
private readonly Dictionary<Symbol, int> _dataPointsPerSymbol = new Dictionary<Symbol, int>();
|
||||
private bool _added;
|
||||
private Symbol _eurusd;
|
||||
private DateTime lastDataTime = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
|
||||
@@ -51,6 +52,13 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (lastDataTime == data.Time)
|
||||
{
|
||||
throw new Exception("Duplicate time for current data and last data slice");
|
||||
}
|
||||
|
||||
lastDataTime = data.Time;
|
||||
|
||||
if (_added)
|
||||
{
|
||||
var eurUsdSubscription = SubscriptionManager.SubscriptionDataConfigService
|
||||
@@ -94,7 +102,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
var expectedDataPointsPerSymbol = new Dictionary<string, int>
|
||||
{
|
||||
{ "EURGBP", 3 },
|
||||
{ "EURUSD", 29 }
|
||||
{ "EURUSD", 28 }
|
||||
};
|
||||
|
||||
foreach (var kvp in _dataPointsPerSymbol)
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
private readonly Dictionary<Symbol, int> _dataPointsPerSymbol = new Dictionary<Symbol, int>();
|
||||
private bool _added;
|
||||
private Symbol _eurusd;
|
||||
private DateTime lastDataTime = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
|
||||
@@ -51,6 +52,13 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (lastDataTime == data.Time)
|
||||
{
|
||||
throw new Exception("Duplicate time for current data and last data slice");
|
||||
}
|
||||
|
||||
lastDataTime = data.Time;
|
||||
|
||||
if (_added)
|
||||
{
|
||||
var eurUsdSubscription = SubscriptionManager.SubscriptionDataConfigService
|
||||
@@ -96,7 +104,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
// normal feed
|
||||
{ "EURGBP", 3 },
|
||||
// internal feed on the first day, normal feed on the other two days
|
||||
{ "EURUSD", 3 },
|
||||
{ "EURUSD", 2 },
|
||||
// internal feed only
|
||||
{ "GBPUSD", 0 }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests In The Money (ITM) future option calls across different strike prices.
|
||||
/// We expect 6 orders from the algorithm, which are:
|
||||
///
|
||||
/// * (1) Initial entry, buy ES Call Option (ES19M20 expiring ITM)
|
||||
/// * (2) Initial entry, sell ES Call Option at different strike (ES20H20 expiring ITM)
|
||||
/// * [2] Option assignment, opens a position in the underlying (ES20H20, Qty: -1)
|
||||
/// * [2] Future contract liquidation, due to impending expiry
|
||||
/// * [1] Option exercise, receive 1 ES19M20 future contract
|
||||
/// * [1] Liquidate ES19M20 contract, due to expiry
|
||||
///
|
||||
/// Additionally, we test delistings for future options and assert that our
|
||||
/// portfolio holdings reflect the orders the algorithm has submitted.
|
||||
/// </summary>
|
||||
public class FutureOptionBuySellCallIntradayRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
var es20h20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 3, 20)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
var es20m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option expiring ITM, and adds it to the algorithm.
|
||||
var esOptions = OptionChainProvider.GetOptionContractList(es20m20, Time)
|
||||
.Concat(OptionChainProvider.GetOptionContractList(es20h20, Time))
|
||||
.Where(x => x.ID.StrikePrice == 3200m && x.ID.OptionRight == OptionRight.Call)
|
||||
.Select(x => AddFutureOptionContract(x, Resolution.Minute).Symbol)
|
||||
.ToList();
|
||||
|
||||
var expectedContracts = new[]
|
||||
{
|
||||
QuantConnect.Symbol.CreateOption(es20h20, Market.CME, OptionStyle.American, OptionRight.Call, 3200m,
|
||||
new DateTime(2020, 3, 20)),
|
||||
QuantConnect.Symbol.CreateOption(es20m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200m,
|
||||
new DateTime(2020, 6, 19))
|
||||
};
|
||||
|
||||
foreach (var esOption in esOptions)
|
||||
{
|
||||
if (!expectedContracts.Contains(esOption))
|
||||
{
|
||||
throw new Exception($"Contract {esOption} was not found in the chain");
|
||||
}
|
||||
}
|
||||
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(es20m20, 1), () =>
|
||||
{
|
||||
MarketOrder(esOptions[0], 1);
|
||||
MarketOrder(esOptions[1], -1);
|
||||
});
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.Noon, () =>
|
||||
{
|
||||
Liquidate();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "6"},
|
||||
{"Average Win", "2.94%"},
|
||||
{"Average Loss", "-4.15%"},
|
||||
{"Compounding Annual Return", "-5.601%"},
|
||||
{"Drawdown", "5.600%"},
|
||||
{"Expectancy", "-0.146"},
|
||||
{"Net Profit", "-2.771%"},
|
||||
{"Sharpe Ratio", "-0.49"},
|
||||
{"Probabilistic Sharpe Ratio", "10.583%"},
|
||||
{"Loss Rate", "50%"},
|
||||
{"Win Rate", "50%"},
|
||||
{"Profit-Loss Ratio", "0.71"},
|
||||
{"Alpha", "-0.043"},
|
||||
{"Beta", "-0.001"},
|
||||
{"Annual Standard Deviation", "0.087"},
|
||||
{"Annual Variance", "0.008"},
|
||||
{"Information Ratio", "0.96"},
|
||||
{"Tracking Error", "0.192"},
|
||||
{"Treynor Ratio", "58.394"},
|
||||
{"Total Fees", "$14.80"},
|
||||
{"Fitness Score", "0.018"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-0.096"},
|
||||
{"Return Over Maximum Drawdown", "-0.993"},
|
||||
{"Portfolio Turnover", "0.043"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-290004562"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
259
Algorithm.CSharp/FutureOptionCallITMExpiryRegressionAlgorithm.cs
Normal file
259
Algorithm.CSharp/FutureOptionCallITMExpiryRegressionAlgorithm.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests In The Money (ITM) future option expiry for calls.
|
||||
/// We expect 3 orders from the algorithm, which are:
|
||||
///
|
||||
/// * Initial entry, buy ES Call Option (expiring ITM)
|
||||
/// * Option exercise, receiving ES future contracts
|
||||
/// * Future contract liquidation, due to impending expiry
|
||||
///
|
||||
/// Additionally, we test delistings for future options and assert that our
|
||||
/// portfolio holdings reflect the orders the algorithm has submitted.
|
||||
/// </summary>
|
||||
public class FutureOptionCallITMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _es19m20;
|
||||
private Symbol _esOption;
|
||||
private Symbol _expectedOptionContract;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option expiring ITM, and adds it to the algorithm.
|
||||
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
|
||||
.Where(x => x.ID.StrikePrice <= 3200m && x.ID.OptionRight == OptionRight.Call)
|
||||
.OrderByDescending(x => x.ID.StrikePrice)
|
||||
.Take(1)
|
||||
.Single(), Resolution.Minute).Symbol;
|
||||
|
||||
_expectedOptionContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200m, new DateTime(2020, 6, 19));
|
||||
if (_esOption != _expectedOptionContract)
|
||||
{
|
||||
throw new Exception($"Contract {_expectedOptionContract} was not found in the chain");
|
||||
}
|
||||
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
|
||||
{
|
||||
MarketOrder(_esOption, 1);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
// the expected time. These assertions detect bug #4872
|
||||
foreach (var delisting in data.Delistings.Values)
|
||||
{
|
||||
if (delisting.Type == DelistingType.Warning)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 19))
|
||||
{
|
||||
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
if (delisting.Type == DelistingType.Delisted)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 20))
|
||||
{
|
||||
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
if (orderEvent.Status != OrderStatus.Filled)
|
||||
{
|
||||
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Securities.ContainsKey(orderEvent.Symbol))
|
||||
{
|
||||
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
var security = Securities[orderEvent.Symbol];
|
||||
if (security.Symbol == _es19m20)
|
||||
{
|
||||
AssertFutureOptionOrderExercise(orderEvent, security, Securities[_expectedOptionContract]);
|
||||
}
|
||||
else if (security.Symbol == _expectedOptionContract)
|
||||
{
|
||||
AssertFutureOptionContractOrder(orderEvent, security);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
Log($"{Time:yyyy-MM-dd HH:mm:ss} -- {orderEvent.Symbol} :: Price: {Securities[orderEvent.Symbol].Holdings.Price} Qty: {Securities[orderEvent.Symbol].Holdings.Quantity} Direction: {orderEvent.Direction} Msg: {orderEvent.Message}");
|
||||
}
|
||||
|
||||
private void AssertFutureOptionOrderExercise(OrderEvent orderEvent, Security future, Security optionContract)
|
||||
{
|
||||
// We expect the liquidation to occur on the day of the delisting (while the market is open),
|
||||
// but currently we liquidate at the next market open (AAPL open) which happens to be
|
||||
// at 9:30:00 Eastern Time. For unknown reasons, the delisting happens two minutes after the
|
||||
// market open.
|
||||
// Read more about the issue affecting this test here: https://github.com/QuantConnect/Lean/issues/4980
|
||||
var expectedLiquidationTimeUtc = new DateTime(2020, 6, 22, 13, 32, 0);
|
||||
|
||||
if (orderEvent.Direction == OrderDirection.Sell && future.Holdings.Quantity != 0)
|
||||
{
|
||||
// We expect the contract to have been liquidated immediately
|
||||
throw new Exception($"Did not liquidate existing holdings for Symbol {future.Symbol}");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Sell && orderEvent.UtcTime != expectedLiquidationTimeUtc)
|
||||
{
|
||||
throw new Exception($"Liquidated future contract, but not at the expected time. Expected: {expectedLiquidationTimeUtc:yyyy-MM-dd HH:mm:ss} - found {orderEvent.UtcTime:yyyy-MM-dd HH:mm:ss}");
|
||||
}
|
||||
|
||||
// No way to detect option exercise orders or any other kind of special orders
|
||||
// other than matching strings, for now.
|
||||
if (orderEvent.Message.Contains("Option Exercise"))
|
||||
{
|
||||
if (orderEvent.FillPrice != 3200m)
|
||||
{
|
||||
throw new Exception("Option did not exercise at expected strike price (3200)");
|
||||
}
|
||||
if (future.Holdings.Quantity != 1)
|
||||
{
|
||||
// Here, we expect to have some holdings in the underlying, but not in the future option anymore.
|
||||
throw new Exception($"Exercised option contract, but we have no holdings for Future {future.Symbol}");
|
||||
}
|
||||
|
||||
if (optionContract.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception($"Exercised option contract, but we have holdings for Option contract {optionContract.Symbol}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
|
||||
{
|
||||
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 1)
|
||||
{
|
||||
throw new Exception($"No holdings were created for option contract {option.Symbol}");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception($"Holdings were found after a filled option exercise");
|
||||
}
|
||||
if (orderEvent.Message.Contains("Exercise") && option.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception($"Holdings were found after exercising option contract {option.Symbol}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "3"},
|
||||
{"Average Win", "1.25%"},
|
||||
{"Average Loss", "-7.42%"},
|
||||
{"Compounding Annual Return", "-12.413%"},
|
||||
{"Drawdown", "6.300%"},
|
||||
{"Expectancy", "-0.416"},
|
||||
{"Net Profit", "-6.257%"},
|
||||
{"Sharpe Ratio", "-1.325"},
|
||||
{"Probabilistic Sharpe Ratio", "0.004%"},
|
||||
{"Loss Rate", "50%"},
|
||||
{"Win Rate", "50%"},
|
||||
{"Profit-Loss Ratio", "0.17"},
|
||||
{"Alpha", "-0.102"},
|
||||
{"Beta", "-0.003"},
|
||||
{"Annual Standard Deviation", "0.076"},
|
||||
{"Annual Variance", "0.006"},
|
||||
{"Information Ratio", "0.673"},
|
||||
{"Tracking Error", "0.188"},
|
||||
{"Treynor Ratio", "33.559"},
|
||||
{"Total Fees", "$7.40"},
|
||||
{"Fitness Score", "0.008"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-0.205"},
|
||||
{"Return Over Maximum Drawdown", "-1.983"},
|
||||
{"Portfolio Turnover", "0.023"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "23301049"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Option;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests In The Money (ITM) future option expiry for calls.
|
||||
/// We test to make sure that FOPs have greeks enabled, same as equity options.
|
||||
/// </summary>
|
||||
public class FutureOptionCallITMGreeksExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private bool _invested;
|
||||
private int _onDataCalls;
|
||||
private Symbol _es19m20;
|
||||
private Option _esOption;
|
||||
private Symbol _expectedOptionContract;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option expiring ITM, and adds it to the algorithm.
|
||||
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, new DateTime(2020, 1, 5))
|
||||
.Where(x => x.ID.StrikePrice <= 3200m && x.ID.OptionRight == OptionRight.Call)
|
||||
.OrderByDescending(x => x.ID.StrikePrice)
|
||||
.Take(1)
|
||||
.Single(), Resolution.Minute);
|
||||
|
||||
_esOption.PriceModel = OptionPriceModels.BjerksundStensland();
|
||||
|
||||
_expectedOptionContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200m, new DateTime(2020, 6, 19));
|
||||
if (_esOption.Symbol != _expectedOptionContract)
|
||||
{
|
||||
throw new Exception($"Contract {_expectedOptionContract} was not found in the chain");
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// Let the algo warmup, but without using SetWarmup. Otherwise, we get
|
||||
// no contracts in the option chain
|
||||
if (_invested || _onDataCalls++ < 40)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.OptionChains.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (data.OptionChains.Values.All(o => o.Contracts.Values.Any(c => !data.ContainsKey(c.Symbol))))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (data.OptionChains.Values.First().Contracts.Count == 0)
|
||||
{
|
||||
throw new Exception($"No contracts found in the option {data.OptionChains.Keys.First()}");
|
||||
}
|
||||
|
||||
var deltas = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Delta).ToList();
|
||||
var gammas = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Gamma).ToList();
|
||||
var lambda = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Lambda).ToList();
|
||||
var rho = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Rho).ToList();
|
||||
var theta = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Theta).ToList();
|
||||
var vega = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Vega).ToList();
|
||||
|
||||
// The commented out test cases all return zero.
|
||||
// This is because of failure to evaluate the greeks in the option pricing model.
|
||||
// For now, let's skip those.
|
||||
if (deltas.Any(d => d == 0))
|
||||
{
|
||||
throw new AggregateException("Option contract Delta was equal to zero");
|
||||
}
|
||||
if (gammas.Any(g => g == 0))
|
||||
{
|
||||
throw new AggregateException("Option contract Gamma was equal to zero");
|
||||
}
|
||||
//if (lambda.Any(l => l == 0))
|
||||
//{
|
||||
// throw new AggregateException("Option contract Lambda was equal to zero");
|
||||
//}
|
||||
if (rho.Any(r => r == 0))
|
||||
{
|
||||
throw new AggregateException("Option contract Rho was equal to zero");
|
||||
}
|
||||
//if (theta.Any(t => t == 0))
|
||||
//{
|
||||
// throw new AggregateException("Option contract Theta was equal to zero");
|
||||
//}
|
||||
//if (vega.Any(v => v == 0))
|
||||
//{
|
||||
// throw new AggregateException("Option contract Vega was equal to zero");
|
||||
//}
|
||||
|
||||
if (!_invested)
|
||||
{
|
||||
SetHoldings(data.OptionChains.Values.First().Contracts.Values.First().Symbol, 1);
|
||||
_invested = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
if (!_invested)
|
||||
{
|
||||
throw new Exception($"Never checked greeks, maybe we have no option data?");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "3"},
|
||||
{"Average Win", "28.04%"},
|
||||
{"Average Loss", "-62.81%"},
|
||||
{"Compounding Annual Return", "-78.165%"},
|
||||
{"Drawdown", "52.400%"},
|
||||
{"Expectancy", "-0.277"},
|
||||
{"Net Profit", "-52.379%"},
|
||||
{"Sharpe Ratio", "-0.865"},
|
||||
{"Probabilistic Sharpe Ratio", "0.019%"},
|
||||
{"Loss Rate", "50%"},
|
||||
{"Win Rate", "50%"},
|
||||
{"Profit-Loss Ratio", "0.45"},
|
||||
{"Alpha", "-0.596"},
|
||||
{"Beta", "-0.031"},
|
||||
{"Annual Standard Deviation", "0.681"},
|
||||
{"Annual Variance", "0.463"},
|
||||
{"Information Ratio", "-0.514"},
|
||||
{"Tracking Error", "0.703"},
|
||||
{"Treynor Ratio", "18.748"},
|
||||
{"Total Fees", "$66.60"},
|
||||
{"Fitness Score", "0.157"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-0.133"},
|
||||
{"Return Over Maximum Drawdown", "-1.492"},
|
||||
{"Portfolio Turnover", "0.411"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "151392833"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
226
Algorithm.CSharp/FutureOptionCallOTMExpiryRegressionAlgorithm.cs
Normal file
226
Algorithm.CSharp/FutureOptionCallOTMExpiryRegressionAlgorithm.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests Out of The Money (OTM) future option expiry for calls.
|
||||
/// We expect 1 order from the algorithm, which are:
|
||||
///
|
||||
/// * Initial entry, buy ES Call Option (expiring OTM)
|
||||
/// - contract expires worthless, not exercised, so never opened a position in the underlying
|
||||
///
|
||||
/// Additionally, we test delistings for future options and assert that our
|
||||
/// portfolio holdings reflect the orders the algorithm has submitted.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Total Trades in regression algorithm should be 1, but expiration is counted as a trade.
|
||||
/// See related issue: https://github.com/QuantConnect/Lean/issues/4854
|
||||
/// </remarks>
|
||||
public class FutureOptionCallOTMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _es19m20;
|
||||
private Symbol _esOption;
|
||||
private Symbol _expectedContract;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option call expiring OTM, and adds it to the algorithm.
|
||||
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
|
||||
.Where(x => x.ID.StrikePrice >= 3300m && x.ID.OptionRight == OptionRight.Call)
|
||||
.OrderBy(x => x.ID.StrikePrice)
|
||||
.Take(1)
|
||||
.Single(), Resolution.Minute).Symbol;
|
||||
|
||||
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3300m, new DateTime(2020, 6, 19));
|
||||
if (_esOption != _expectedContract)
|
||||
{
|
||||
throw new Exception($"Contract {_expectedContract} was not found in the chain");
|
||||
}
|
||||
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
|
||||
{
|
||||
MarketOrder(_esOption, 1);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
// the expected time. These assertions detect bug #4872
|
||||
foreach (var delisting in data.Delistings.Values)
|
||||
{
|
||||
if (delisting.Type == DelistingType.Warning)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 19))
|
||||
{
|
||||
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
if (delisting.Type == DelistingType.Delisted)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 20))
|
||||
{
|
||||
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
if (orderEvent.Status != OrderStatus.Filled)
|
||||
{
|
||||
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Securities.ContainsKey(orderEvent.Symbol))
|
||||
{
|
||||
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
var security = Securities[orderEvent.Symbol];
|
||||
if (security.Symbol == _es19m20)
|
||||
{
|
||||
throw new Exception("Invalid state: did not expect a position for the underlying to be opened, since this contract expires OTM");
|
||||
}
|
||||
if (security.Symbol == _expectedContract)
|
||||
{
|
||||
AssertFutureOptionContractOrder(orderEvent, security);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
Log($"{orderEvent}");
|
||||
}
|
||||
|
||||
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
|
||||
{
|
||||
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 1)
|
||||
{
|
||||
throw new Exception($"No holdings were created for option contract {option.Symbol}");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception("Holdings were found after a filled option exercise");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Sell && !orderEvent.Message.Contains("OTM"))
|
||||
{
|
||||
throw new Exception("Contract did not expire OTM");
|
||||
}
|
||||
if (orderEvent.Message.Contains("Exercise"))
|
||||
{
|
||||
throw new Exception("Exercised option, even though it expires OTM");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "-4.03%"},
|
||||
{"Compounding Annual Return", "-8.088%"},
|
||||
{"Drawdown", "4.000%"},
|
||||
{"Expectancy", "-1"},
|
||||
{"Net Profit", "-4.029%"},
|
||||
{"Sharpe Ratio", "-1.274"},
|
||||
{"Probabilistic Sharpe Ratio", "0.015%"},
|
||||
{"Loss Rate", "100%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "-0.066"},
|
||||
{"Beta", "-0.002"},
|
||||
{"Annual Standard Deviation", "0.052"},
|
||||
{"Annual Variance", "0.003"},
|
||||
{"Information Ratio", "0.9"},
|
||||
{"Tracking Error", "0.179"},
|
||||
{"Treynor Ratio", "28.537"},
|
||||
{"Total Fees", "$3.70"},
|
||||
{"Fitness Score", "0"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-0.183"},
|
||||
{"Return Over Maximum Drawdown", "-2.007"},
|
||||
{"Portfolio Turnover", "0"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1116221764"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
259
Algorithm.CSharp/FutureOptionPutITMExpiryRegressionAlgorithm.cs
Normal file
259
Algorithm.CSharp/FutureOptionPutITMExpiryRegressionAlgorithm.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests In The Money (ITM) future option expiry for puts.
|
||||
/// We expect 3 orders from the algorithm, which are:
|
||||
///
|
||||
/// * Initial entry, buy ES Put Option (expiring ITM) (buy, qty 1)
|
||||
/// * Option exercise, receiving short ES future contracts (sell, qty -1)
|
||||
/// * Future contract liquidation, due to impending expiry (buy qty 1)
|
||||
///
|
||||
/// Additionally, we test delistings for future options and assert that our
|
||||
/// portfolio holdings reflect the orders the algorithm has submitted.
|
||||
/// </summary>
|
||||
public class FutureOptionPutITMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _es19m20;
|
||||
private Symbol _esOption;
|
||||
private Symbol _expectedContract;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option expiring ITM, and adds it to the algorithm.
|
||||
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
|
||||
.Where(x => x.ID.StrikePrice >= 3300m && x.ID.OptionRight == OptionRight.Put)
|
||||
.OrderBy(x => x.ID.StrikePrice)
|
||||
.Take(1)
|
||||
.Single(), Resolution.Minute).Symbol;
|
||||
|
||||
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3300m, new DateTime(2020, 6, 19));
|
||||
if (_esOption != _expectedContract)
|
||||
{
|
||||
throw new Exception($"Contract {_expectedContract} was not found in the chain");
|
||||
}
|
||||
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
|
||||
{
|
||||
MarketOrder(_esOption, 1);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
// the expected time. These assertions detect bug #4872
|
||||
foreach (var delisting in data.Delistings.Values)
|
||||
{
|
||||
if (delisting.Type == DelistingType.Warning)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 19))
|
||||
{
|
||||
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
if (delisting.Type == DelistingType.Delisted)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 20))
|
||||
{
|
||||
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
if (orderEvent.Status != OrderStatus.Filled)
|
||||
{
|
||||
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Securities.ContainsKey(orderEvent.Symbol))
|
||||
{
|
||||
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
var security = Securities[orderEvent.Symbol];
|
||||
if (security.Symbol == _es19m20)
|
||||
{
|
||||
AssertFutureOptionOrderExercise(orderEvent, security, Securities[_expectedContract]);
|
||||
}
|
||||
else if (security.Symbol == _expectedContract)
|
||||
{
|
||||
AssertFutureOptionContractOrder(orderEvent, security);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
Log($"{Time:yyyy-MM-dd HH:mm:ss} -- {orderEvent.Symbol} :: Price: {Securities[orderEvent.Symbol].Holdings.Price} Qty: {Securities[orderEvent.Symbol].Holdings.Quantity} Direction: {orderEvent.Direction} Msg: {orderEvent.Message}");
|
||||
}
|
||||
|
||||
private void AssertFutureOptionOrderExercise(OrderEvent orderEvent, Security future, Security optionContract)
|
||||
{
|
||||
// We expect the liquidation to occur on the day of the delisting (while the market is open),
|
||||
// but currently we liquidate at the next market open (AAPL open) which happens to be
|
||||
// at 9:30:00 Eastern Time. For unknown reasons, the delisting happens two minutes after the
|
||||
// market open.
|
||||
// Read more about the issue affecting this test here: https://github.com/QuantConnect/Lean/issues/4980
|
||||
var expectedLiquidationTimeUtc = new DateTime(2020, 6, 22, 13, 32, 0);
|
||||
|
||||
if (orderEvent.Direction == OrderDirection.Buy && future.Holdings.Quantity != 0)
|
||||
{
|
||||
// We expect the contract to have been liquidated immediately
|
||||
throw new Exception($"Did not liquidate existing holdings for Symbol {future.Symbol}");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Buy && orderEvent.UtcTime != expectedLiquidationTimeUtc)
|
||||
{
|
||||
throw new Exception($"Liquidated future contract, but not at the expected time. Expected: {expectedLiquidationTimeUtc:yyyy-MM-dd HH:mm:ss} - found {orderEvent.UtcTime:yyyy-MM-dd HH:mm:ss}");
|
||||
}
|
||||
|
||||
// No way to detect option exercise orders or any other kind of special orders
|
||||
// other than matching strings, for now.
|
||||
if (orderEvent.Message.Contains("Option Exercise"))
|
||||
{
|
||||
if (orderEvent.FillPrice != 3300m)
|
||||
{
|
||||
throw new Exception("Option did not exercise at expected strike price (3300)");
|
||||
}
|
||||
if (future.Holdings.Quantity != -1)
|
||||
{
|
||||
// Here, we expect to have some holdings in the underlying, but not in the future option anymore.
|
||||
throw new Exception($"Exercised option contract, but we have no holdings for Future {future.Symbol}");
|
||||
}
|
||||
|
||||
if (optionContract.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception($"Exercised option contract, but we have holdings for Option contract {optionContract.Symbol}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
|
||||
{
|
||||
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 1)
|
||||
{
|
||||
throw new Exception($"No holdings were created for option contract {option.Symbol}");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception($"Holdings were found after a filled option exercise");
|
||||
}
|
||||
if (orderEvent.Message.Contains("Exercise") && option.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception($"Holdings were found after exercising option contract {option.Symbol}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "3"},
|
||||
{"Average Win", "4.18%"},
|
||||
{"Average Loss", "-8.27%"},
|
||||
{"Compounding Annual Return", "-8.879%"},
|
||||
{"Drawdown", "4.400%"},
|
||||
{"Expectancy", "-0.247"},
|
||||
{"Net Profit", "-4.432%"},
|
||||
{"Sharpe Ratio", "-1.391"},
|
||||
{"Probabilistic Sharpe Ratio", "0.002%"},
|
||||
{"Loss Rate", "50%"},
|
||||
{"Win Rate", "50%"},
|
||||
{"Profit-Loss Ratio", "0.51"},
|
||||
{"Alpha", "-0.073"},
|
||||
{"Beta", "-0.002"},
|
||||
{"Annual Standard Deviation", "0.052"},
|
||||
{"Annual Variance", "0.003"},
|
||||
{"Information Ratio", "0.863"},
|
||||
{"Tracking Error", "0.179"},
|
||||
{"Treynor Ratio", "38.46"},
|
||||
{"Total Fees", "$7.40"},
|
||||
{"Fitness Score", "0.008"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-0.224"},
|
||||
{"Return Over Maximum Drawdown", "-2.003"},
|
||||
{"Portfolio Turnover", "0.023"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-675079082"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
225
Algorithm.CSharp/FutureOptionPutOTMExpiryRegressionAlgorithm.cs
Normal file
225
Algorithm.CSharp/FutureOptionPutOTMExpiryRegressionAlgorithm.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests Out of The Money (OTM) future option expiry for puts.
|
||||
/// We expect 1 order from the algorithm, which are:
|
||||
///
|
||||
/// * Initial entry, buy ES Put Option (expiring OTM)
|
||||
/// - contract expires worthless, not exercised, so never opened a position in the underlying
|
||||
///
|
||||
/// Additionally, we test delistings for future options and assert that our
|
||||
/// portfolio holdings reflect the orders the algorithm has submitted.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Total Trades in regression algorithm should be 1, but expiration is counted as a trade.
|
||||
/// </remarks>
|
||||
public class FutureOptionPutOTMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _es19m20;
|
||||
private Symbol _esOption;
|
||||
private Symbol _expectedContract;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option expiring ITM, and adds it to the algorithm.
|
||||
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
|
||||
.Where(x => x.ID.StrikePrice <= 3150m && x.ID.OptionRight == OptionRight.Put)
|
||||
.OrderByDescending(x => x.ID.StrikePrice)
|
||||
.Take(1)
|
||||
.Single(), Resolution.Minute).Symbol;
|
||||
|
||||
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3150m, new DateTime(2020, 6, 19));
|
||||
if (_esOption != _expectedContract)
|
||||
{
|
||||
throw new Exception($"Contract {_expectedContract} was not found in the chain");
|
||||
}
|
||||
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
|
||||
{
|
||||
MarketOrder(_esOption, 1);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
// the expected time. These assertions detect bug #4872
|
||||
foreach (var delisting in data.Delistings.Values)
|
||||
{
|
||||
if (delisting.Type == DelistingType.Warning)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 19))
|
||||
{
|
||||
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
if (delisting.Type == DelistingType.Delisted)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 20))
|
||||
{
|
||||
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
if (orderEvent.Status != OrderStatus.Filled)
|
||||
{
|
||||
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Securities.ContainsKey(orderEvent.Symbol))
|
||||
{
|
||||
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
var security = Securities[orderEvent.Symbol];
|
||||
if (security.Symbol == _es19m20)
|
||||
{
|
||||
throw new Exception("Invalid state: did not expect a position for the underlying to be opened, since this contract expires OTM");
|
||||
}
|
||||
if (security.Symbol == _expectedContract)
|
||||
{
|
||||
AssertFutureOptionContractOrder(orderEvent, security);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
Log($"{orderEvent}");
|
||||
}
|
||||
|
||||
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
|
||||
{
|
||||
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 1)
|
||||
{
|
||||
throw new Exception($"No holdings were created for option contract {option.Symbol}");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception("Holdings were found after a filled option exercise");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Sell && !orderEvent.Message.Contains("OTM"))
|
||||
{
|
||||
throw new Exception("Contract did not expire OTM");
|
||||
}
|
||||
if (orderEvent.Message.Contains("Exercise"))
|
||||
{
|
||||
throw new Exception("Exercised option, even though it expires OTM");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "-5.12%"},
|
||||
{"Compounding Annual Return", "-10.212%"},
|
||||
{"Drawdown", "5.100%"},
|
||||
{"Expectancy", "-1"},
|
||||
{"Net Profit", "-5.116%"},
|
||||
{"Sharpe Ratio", "-1.26"},
|
||||
{"Probabilistic Sharpe Ratio", "0.016%"},
|
||||
{"Loss Rate", "100%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "-0.084"},
|
||||
{"Beta", "-0.003"},
|
||||
{"Annual Standard Deviation", "0.066"},
|
||||
{"Annual Variance", "0.004"},
|
||||
{"Information Ratio", "0.785"},
|
||||
{"Tracking Error", "0.184"},
|
||||
{"Treynor Ratio", "28.158"},
|
||||
{"Total Fees", "$3.70"},
|
||||
{"Fitness Score", "0"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-0.181"},
|
||||
{"Return Over Maximum Drawdown", "-1.995"},
|
||||
{"Portfolio Turnover", "0"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "515984318"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests In The Money (ITM) future option expiry for short calls.
|
||||
/// We expect 3 orders from the algorithm, which are:
|
||||
///
|
||||
/// * Initial entry, sell ES Call Option (expiring ITM)
|
||||
/// * Option assignment, sell 1 contract of the underlying (ES)
|
||||
/// * Future contract expiry, liquidation (buy 1 ES future)
|
||||
///
|
||||
/// Additionally, we test delistings for future options and assert that our
|
||||
/// portfolio holdings reflect the orders the algorithm has submitted.
|
||||
/// </summary>
|
||||
public class FutureOptionShortCallITMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _es19m20;
|
||||
private Symbol _esOption;
|
||||
private Symbol _expectedContract;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option expiring ITM, and adds it to the algorithm.
|
||||
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
|
||||
.Where(x => x.ID.StrikePrice <= 3100m && x.ID.OptionRight == OptionRight.Call)
|
||||
.OrderByDescending(x => x.ID.StrikePrice)
|
||||
.Take(1)
|
||||
.Single(), Resolution.Minute).Symbol;
|
||||
|
||||
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3100m, new DateTime(2020, 6, 19));
|
||||
if (_esOption != _expectedContract)
|
||||
{
|
||||
throw new Exception($"Contract {_expectedContract} was not found in the chain");
|
||||
}
|
||||
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
|
||||
{
|
||||
MarketOrder(_esOption, -1);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
// the expected time. These assertions detect bug #4872
|
||||
foreach (var delisting in data.Delistings.Values)
|
||||
{
|
||||
if (delisting.Type == DelistingType.Warning)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 19))
|
||||
{
|
||||
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
if (delisting.Type == DelistingType.Delisted)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 20))
|
||||
{
|
||||
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
if (orderEvent.Status != OrderStatus.Filled)
|
||||
{
|
||||
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Securities.ContainsKey(orderEvent.Symbol))
|
||||
{
|
||||
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
var security = Securities[orderEvent.Symbol];
|
||||
if (security.Symbol == _es19m20)
|
||||
{
|
||||
AssertFutureOptionOrderExercise(orderEvent, security, Securities[_expectedContract]);
|
||||
}
|
||||
else if (security.Symbol == _expectedContract)
|
||||
{
|
||||
AssertFutureOptionContractOrder(orderEvent, security);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
Log($"{orderEvent}");
|
||||
}
|
||||
|
||||
private void AssertFutureOptionOrderExercise(OrderEvent orderEvent, Security future, Security optionContract)
|
||||
{
|
||||
if (orderEvent.Message.Contains("Assignment"))
|
||||
{
|
||||
if (orderEvent.FillPrice != 3100m)
|
||||
{
|
||||
throw new Exception("Option was not assigned at expected strike price (3100)");
|
||||
}
|
||||
if (orderEvent.Direction != OrderDirection.Sell || future.Holdings.Quantity != -1)
|
||||
{
|
||||
throw new Exception($"Expected Qty: -1 futures holdings for assigned future {future.Symbol}, found {future.Holdings.Quantity}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (orderEvent.Direction == OrderDirection.Buy && future.Holdings.Quantity != 0)
|
||||
{
|
||||
// We buy back the underlying at expiration, so we expect a neutral position then
|
||||
throw new Exception($"Expected no holdings when liquidating future contract {future.Symbol}");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
|
||||
{
|
||||
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != -1)
|
||||
{
|
||||
throw new Exception($"No holdings were created for option contract {option.Symbol}");
|
||||
}
|
||||
if (orderEvent.IsAssignment && option.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception($"Holdings were found after option contract was assigned: {option.Symbol}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "3"},
|
||||
{"Average Win", "10.05%"},
|
||||
{"Average Loss", "-5.60%"},
|
||||
{"Compounding Annual Return", "8.121%"},
|
||||
{"Drawdown", "0.500%"},
|
||||
{"Expectancy", "0.396"},
|
||||
{"Net Profit", "3.880%"},
|
||||
{"Sharpe Ratio", "1.192"},
|
||||
{"Probabilistic Sharpe Ratio", "58.203%"},
|
||||
{"Loss Rate", "50%"},
|
||||
{"Win Rate", "50%"},
|
||||
{"Profit-Loss Ratio", "1.79"},
|
||||
{"Alpha", "0.069"},
|
||||
{"Beta", "0.003"},
|
||||
{"Annual Standard Deviation", "0.057"},
|
||||
{"Annual Variance", "0.003"},
|
||||
{"Information Ratio", "1.641"},
|
||||
{"Tracking Error", "0.18"},
|
||||
{"Treynor Ratio", "22.101"},
|
||||
{"Total Fees", "$7.40"},
|
||||
{"Fitness Score", "0.021"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "17.255"},
|
||||
{"Portfolio Turnover", "0.021"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1118389718"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests Out of The Money (OTM) future option expiry for short calls.
|
||||
/// We expect 1 order from the algorithm, which are:
|
||||
///
|
||||
/// * Initial entry, sell ES Call Option (expiring OTM)
|
||||
/// - Profit the option premium, since the option was not assigned.
|
||||
///
|
||||
/// Additionally, we test delistings for future options and assert that our
|
||||
/// portfolio holdings reflect the orders the algorithm has submitted.
|
||||
/// </summary>
|
||||
public class FutureOptionShortCallOTMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _es19m20;
|
||||
private Symbol _esOption;
|
||||
private Symbol _expectedContract;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option expiring ITM, and adds it to the algorithm.
|
||||
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
|
||||
.Where(x => x.ID.StrikePrice >= 3400m && x.ID.OptionRight == OptionRight.Call)
|
||||
.OrderBy(x => x.ID.StrikePrice)
|
||||
.Take(1)
|
||||
.Single(), Resolution.Minute).Symbol;
|
||||
|
||||
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3400m, new DateTime(2020, 6, 19));
|
||||
if (_esOption != _expectedContract)
|
||||
{
|
||||
throw new Exception($"Contract {_expectedContract} was not found in the chain");
|
||||
}
|
||||
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
|
||||
{
|
||||
MarketOrder(_esOption, -1);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
// the expected time. These assertions detect bug #4872
|
||||
foreach (var delisting in data.Delistings.Values)
|
||||
{
|
||||
if (delisting.Type == DelistingType.Warning)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 19))
|
||||
{
|
||||
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
if (delisting.Type == DelistingType.Delisted)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 20))
|
||||
{
|
||||
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
if (orderEvent.Status != OrderStatus.Filled)
|
||||
{
|
||||
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Securities.ContainsKey(orderEvent.Symbol))
|
||||
{
|
||||
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
var security = Securities[orderEvent.Symbol];
|
||||
if (security.Symbol == _es19m20)
|
||||
{
|
||||
throw new Exception($"Expected no order events for underlying Symbol {security.Symbol}");
|
||||
}
|
||||
|
||||
if (security.Symbol == _expectedContract)
|
||||
{
|
||||
AssertFutureOptionContractOrder(orderEvent, security);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
Log($"{orderEvent}");
|
||||
}
|
||||
|
||||
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security optionContract)
|
||||
{
|
||||
if (orderEvent.Direction == OrderDirection.Sell && optionContract.Holdings.Quantity != -1)
|
||||
{
|
||||
throw new Exception($"No holdings were created for option contract {optionContract.Symbol}");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Buy && optionContract.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception("Expected no options holdings after closing position");
|
||||
}
|
||||
if (orderEvent.IsAssignment)
|
||||
{
|
||||
throw new Exception($"Assignment was not expected for {orderEvent.Symbol}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "2"},
|
||||
{"Average Win", "1.81%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "3.745%"},
|
||||
{"Drawdown", "0.000%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "1.809%"},
|
||||
{"Sharpe Ratio", "1.292"},
|
||||
{"Probabilistic Sharpe Ratio", "65.890%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "100%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0.031"},
|
||||
{"Beta", "0.001"},
|
||||
{"Annual Standard Deviation", "0.024"},
|
||||
{"Annual Variance", "0.001"},
|
||||
{"Information Ratio", "1.496"},
|
||||
{"Tracking Error", "0.173"},
|
||||
{"Treynor Ratio", "27.281"},
|
||||
{"Total Fees", "$3.70"},
|
||||
{"Fitness Score", "0"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "95.176"},
|
||||
{"Portfolio Turnover", "0"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1364902860"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests In The Money (ITM) future option expiry for short puts.
|
||||
/// We expect 3 orders from the algorithm, which are:
|
||||
///
|
||||
/// * Initial entry, sell ES Put Option (expiring ITM)
|
||||
/// * Option assignment, buy 1 contract of the underlying (ES)
|
||||
/// * Future contract expiry, liquidation (sell 1 ES future)
|
||||
///
|
||||
/// Additionally, we test delistings for future options and assert that our
|
||||
/// portfolio holdings reflect the orders the algorithm has submitted.
|
||||
/// </summary>
|
||||
public class FutureOptionShortPutITMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _es19m20;
|
||||
private Symbol _esOption;
|
||||
private Symbol _expectedContract;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option expiring ITM, and adds it to the algorithm.
|
||||
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
|
||||
.Where(x => x.ID.StrikePrice <= 3400m && x.ID.OptionRight == OptionRight.Put)
|
||||
.OrderByDescending(x => x.ID.StrikePrice)
|
||||
.Take(1)
|
||||
.Single(), Resolution.Minute).Symbol;
|
||||
|
||||
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3400m, new DateTime(2020, 6, 19));
|
||||
if (_esOption != _expectedContract)
|
||||
{
|
||||
throw new Exception($"Contract {_expectedContract} was not found in the chain");
|
||||
}
|
||||
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
|
||||
{
|
||||
MarketOrder(_esOption, -1);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
// the expected time. These assertions detect bug #4872
|
||||
foreach (var delisting in data.Delistings.Values)
|
||||
{
|
||||
if (delisting.Type == DelistingType.Warning)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 19))
|
||||
{
|
||||
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
if (delisting.Type == DelistingType.Delisted)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 20))
|
||||
{
|
||||
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
if (orderEvent.Status != OrderStatus.Filled)
|
||||
{
|
||||
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Securities.ContainsKey(orderEvent.Symbol))
|
||||
{
|
||||
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
var security = Securities[orderEvent.Symbol];
|
||||
if (security.Symbol == _es19m20)
|
||||
{
|
||||
AssertFutureOptionOrderExercise(orderEvent, security, Securities[_expectedContract]);
|
||||
}
|
||||
else if (security.Symbol == _expectedContract)
|
||||
{
|
||||
AssertFutureOptionContractOrder(orderEvent, security);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
Log($"{orderEvent}");
|
||||
}
|
||||
|
||||
private void AssertFutureOptionOrderExercise(OrderEvent orderEvent, Security future, Security optionContract)
|
||||
{
|
||||
if (orderEvent.Message.Contains("Assignment"))
|
||||
{
|
||||
if (orderEvent.FillPrice != 3400)
|
||||
{
|
||||
throw new Exception("Option was not assigned at expected strike price (3400)");
|
||||
}
|
||||
if (orderEvent.Direction != OrderDirection.Buy || future.Holdings.Quantity != 1)
|
||||
{
|
||||
throw new Exception($"Expected Qty: 1 futures holdings for assigned future {future.Symbol}, found {future.Holdings.Quantity}");
|
||||
}
|
||||
}
|
||||
if (!orderEvent.Message.Contains("Assignment") && orderEvent.Direction == OrderDirection.Sell && future.Holdings.Quantity != 0)
|
||||
{
|
||||
// We buy back the underlying at expiration, so we expect a neutral position then
|
||||
throw new Exception($"Expected no holdings when liquidating future contract {future.Symbol}");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
|
||||
{
|
||||
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != -1)
|
||||
{
|
||||
throw new Exception($"No holdings were created for option contract {option.Symbol}");
|
||||
}
|
||||
if (orderEvent.IsAssignment && option.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception($"Holdings were found after option contract was assigned: {option.Symbol}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "3"},
|
||||
{"Average Win", "10.18%"},
|
||||
{"Average Loss", "-8.02%"},
|
||||
{"Compounding Annual Return", "2.773%"},
|
||||
{"Drawdown", "0.500%"},
|
||||
{"Expectancy", "0.135"},
|
||||
{"Net Profit", "1.343%"},
|
||||
{"Sharpe Ratio", "0.939"},
|
||||
{"Probabilistic Sharpe Ratio", "46.842%"},
|
||||
{"Loss Rate", "50%"},
|
||||
{"Win Rate", "50%"},
|
||||
{"Profit-Loss Ratio", "1.27"},
|
||||
{"Alpha", "0.023"},
|
||||
{"Beta", "0.002"},
|
||||
{"Annual Standard Deviation", "0.025"},
|
||||
{"Annual Variance", "0.001"},
|
||||
{"Information Ratio", "1.45"},
|
||||
{"Tracking Error", "0.173"},
|
||||
{"Treynor Ratio", "14.62"},
|
||||
{"Total Fees", "$7.40"},
|
||||
{"Fitness Score", "0.021"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "5.815"},
|
||||
{"Portfolio Turnover", "0.022"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "980293281"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm tests Out of The Money (OTM) future option expiry for short puts.
|
||||
/// We expect 1 order from the algorithm, which are:
|
||||
///
|
||||
/// * Initial entry, sell ES Put Option (expiring OTM)
|
||||
/// - Profit the option premium, since the option was not assigned.
|
||||
///
|
||||
/// Additionally, we test delistings for future options and assert that our
|
||||
/// portfolio holdings reflect the orders the algorithm has submitted.
|
||||
/// </summary>
|
||||
public class FutureOptionShortPutOTMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _es19m20;
|
||||
private Symbol _esOption;
|
||||
private Symbol _expectedContract;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2020, 1, 5);
|
||||
SetEndDate(2020, 6, 30);
|
||||
|
||||
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
// which causes delisting events to never be processed, thus leading to options that might never
|
||||
// be exercised until the next data point arrives.
|
||||
AddEquity("AAPL", Resolution.Daily);
|
||||
|
||||
_es19m20 = AddFutureContract(
|
||||
QuantConnect.Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
new DateTime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol;
|
||||
|
||||
// Select a future option expiring ITM, and adds it to the algorithm.
|
||||
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
|
||||
.Where(x => x.ID.StrikePrice <= 3000m && x.ID.OptionRight == OptionRight.Put)
|
||||
.OrderByDescending(x => x.ID.StrikePrice)
|
||||
.Take(1)
|
||||
.Single(), Resolution.Minute).Symbol;
|
||||
|
||||
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3000m, new DateTime(2020, 6, 19));
|
||||
if (_esOption != _expectedContract)
|
||||
{
|
||||
throw new Exception($"Contract {_expectedContract} was not found in the chain");
|
||||
}
|
||||
|
||||
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
|
||||
{
|
||||
MarketOrder(_esOption, -1);
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
// the expected time. These assertions detect bug #4872
|
||||
foreach (var delisting in data.Delistings.Values)
|
||||
{
|
||||
if (delisting.Type == DelistingType.Warning)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 19))
|
||||
{
|
||||
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
if (delisting.Type == DelistingType.Delisted)
|
||||
{
|
||||
if (delisting.Time != new DateTime(2020, 6, 20))
|
||||
{
|
||||
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
if (orderEvent.Status != OrderStatus.Filled)
|
||||
{
|
||||
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Securities.ContainsKey(orderEvent.Symbol))
|
||||
{
|
||||
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
var security = Securities[orderEvent.Symbol];
|
||||
if (security.Symbol == _es19m20)
|
||||
{
|
||||
throw new Exception($"Expected no order events for underlying Symbol {security.Symbol}");
|
||||
}
|
||||
if (security.Symbol == _expectedContract)
|
||||
{
|
||||
AssertFutureOptionContractOrder(orderEvent, security);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
|
||||
}
|
||||
|
||||
Log($"{orderEvent}");
|
||||
}
|
||||
|
||||
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
|
||||
{
|
||||
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != -1)
|
||||
{
|
||||
throw new Exception($"No holdings were created for option contract {option.Symbol}");
|
||||
}
|
||||
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 0)
|
||||
{
|
||||
throw new Exception("Expected no options holdings after closing position");
|
||||
}
|
||||
if (orderEvent.IsAssignment)
|
||||
{
|
||||
throw new Exception($"Assignment was not expected for {orderEvent.Symbol}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">The algorithm has holdings</exception>
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "2"},
|
||||
{"Average Win", "3.28%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "6.852%"},
|
||||
{"Drawdown", "0.000%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "3.284%"},
|
||||
{"Sharpe Ratio", "1.319"},
|
||||
{"Probabilistic Sharpe Ratio", "66.574%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "100%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0.058"},
|
||||
{"Beta", "0.002"},
|
||||
{"Annual Standard Deviation", "0.043"},
|
||||
{"Annual Variance", "0.002"},
|
||||
{"Information Ratio", "1.614"},
|
||||
{"Tracking Error", "0.176"},
|
||||
{"Treynor Ratio", "28.2"},
|
||||
{"Total Fees", "$3.70"},
|
||||
{"Fitness Score", "0"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "150.252"},
|
||||
{"Portfolio Turnover", "0"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-418839052"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
82
Algorithm.CSharp/OnOrderEventExceptionRegression.cs
Normal file
82
Algorithm.CSharp/OnOrderEventExceptionRegression.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression Algorithm for testing engine behavior with throwing errors in OnOrderEvent
|
||||
/// Should result in a RunTimeError status.
|
||||
/// Reference GH Issue #4947
|
||||
/// </summary>
|
||||
public class OnOrderEventExceptionRegression : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _spy = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2013, 10, 07);
|
||||
SetEndDate(2013, 10, 11);
|
||||
AddEquity("SPY", Resolution.Minute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
|
||||
/// </summary>
|
||||
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
SetHoldings(_spy, 1);
|
||||
Debug("Purchased Stock");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OnOrderEvent is called whenever an order is updated
|
||||
/// </summary>
|
||||
/// <param name="orderEvent">Order Event</param>
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
throw new Exception("OnOrderEvent exception");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -143,8 +143,21 @@
|
||||
</Compile>
|
||||
<Compile Include="AddAlphaModelAlgorithm.cs" />
|
||||
<Compile Include="CustomBuyingPowerModelAlgorithm.cs" />
|
||||
<Compile Include="AddFutureOptionContractDataStreamingRegressionAlgorithm.cs" />
|
||||
<Compile Include="AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm.cs" />
|
||||
<Compile Include="AddOptionContractExpiresRegressionAlgorithm.cs" />
|
||||
<Compile Include="AltData\QuiverWallStreetBetsDataAlgorithm.cs" />
|
||||
<Compile Include="FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs" />
|
||||
<Compile Include="OnOrderEventExceptionRegression.cs" />
|
||||
<Compile Include="FutureOptionCallITMExpiryRegressionAlgorithm.cs" />
|
||||
<Compile Include="FutureOptionCallOTMExpiryRegressionAlgorithm.cs" />
|
||||
<Compile Include="FutureOptionPutITMExpiryRegressionAlgorithm.cs" />
|
||||
<Compile Include="FutureOptionPutOTMExpiryRegressionAlgorithm.cs" />
|
||||
<Compile Include="FutureOptionBuySellCallIntradayRegressionAlgorithm.cs" />
|
||||
<Compile Include="FutureOptionShortCallITMExpiryRegressionAlgorithm.cs" />
|
||||
<Compile Include="FutureOptionShortCallOTMExpiryRegressionAlgorithm.cs" />
|
||||
<Compile Include="FutureOptionShortPutOTMExpiryRegressionAlgorithm.cs" />
|
||||
<Compile Include="FutureOptionShortPutITMExpiryRegressionAlgorithm.cs" />
|
||||
<Compile Include="ScaledFillForwardDataRegressionAlgorithm.cs" />
|
||||
<Compile Include="DailyHistoryForDailyResolutionRegressionAlgorithm.cs" />
|
||||
<Compile Include="DailyHistoryForMinuteResolutionRegressionAlgorithm.cs" />
|
||||
@@ -368,6 +381,7 @@
|
||||
<Compile Include="USEnergyInformationAdministrationAlgorithm.cs" />
|
||||
<Compile Include="UserDefinedUniverseAlgorithm.cs" />
|
||||
<Compile Include="VolumeWeightedAveragePriceExecutionModelRegressionAlgorithm.cs" />
|
||||
<Compile Include="WarmUpAfterIntializeRegression.cs" />
|
||||
<Compile Include="WarmupAlgorithm.cs" />
|
||||
<Compile Include="WarmupConversionRatesRegressionAlgorithm.cs" />
|
||||
<Compile Include="WarmupHistoryAlgorithm.cs" />
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
private Symbol _spy;
|
||||
private int _reselectedSpy = -1;
|
||||
private DateTime lastDataTime = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
|
||||
@@ -57,6 +58,13 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (lastDataTime == data.Time)
|
||||
{
|
||||
throw new Exception("Duplicate time for current data and last data slice");
|
||||
}
|
||||
|
||||
lastDataTime = data.Time;
|
||||
|
||||
if (_reselectedSpy == 0)
|
||||
{
|
||||
if (!Securities[_spy].IsTradable)
|
||||
@@ -111,29 +119,29 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Total Trades", "1"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "75.079%"},
|
||||
{"Drawdown", "2.200%"},
|
||||
{"Compounding Annual Return", "69.904%"},
|
||||
{"Drawdown", "2.000%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "4.711%"},
|
||||
{"Sharpe Ratio", "5.067"},
|
||||
{"Probabilistic Sharpe Ratio", "84.391%"},
|
||||
{"Net Profit", "4.453%"},
|
||||
{"Sharpe Ratio", "4.805"},
|
||||
{"Probabilistic Sharpe Ratio", "83.459%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0.562"},
|
||||
{"Beta", "0.02"},
|
||||
{"Annual Standard Deviation", "0.113"},
|
||||
{"Annual Variance", "0.013"},
|
||||
{"Information Ratio", "0.511"},
|
||||
{"Tracking Error", "0.159"},
|
||||
{"Treynor Ratio", "28.945"},
|
||||
{"Total Fees", "$3.22"},
|
||||
{"Fitness Score", "0.037"},
|
||||
{"Alpha", "0.501"},
|
||||
{"Beta", "0.068"},
|
||||
{"Annual Standard Deviation", "0.111"},
|
||||
{"Annual Variance", "0.012"},
|
||||
{"Information Ratio", "0.284"},
|
||||
{"Tracking Error", "0.153"},
|
||||
{"Treynor Ratio", "7.844"},
|
||||
{"Total Fees", "$3.23"},
|
||||
{"Fitness Score", "0.038"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "17.868"},
|
||||
{"Return Over Maximum Drawdown", "34.832"},
|
||||
{"Portfolio Turnover", "0.037"},
|
||||
{"Sortino Ratio", "16.857"},
|
||||
{"Return Over Maximum Drawdown", "34.897"},
|
||||
{"Portfolio Turnover", "0.038"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
@@ -147,7 +155,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "836605283"}
|
||||
{"OrderListHash", "1664042885"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
61
Algorithm.CSharp/WarmUpAfterIntializeRegression.cs
Normal file
61
Algorithm.CSharp/WarmUpAfterIntializeRegression.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression algorithm to test warming up after initialize behavior, should throw if used outside of initialize
|
||||
/// Reference GH Issue #4939
|
||||
/// </summary>
|
||||
public class WarmUpAfterIntializeRegression : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2013, 10, 07); //Set Start Date
|
||||
SetEndDate(2013, 10, 11); //Set End Date
|
||||
SetCash(100000);
|
||||
var equity = AddEquity("SPY");
|
||||
}
|
||||
|
||||
public override void OnData(Slice slice)
|
||||
{
|
||||
// Should throw and set Algorithm status to be runtime error
|
||||
SetWarmUp(TimeSpan.FromDays(2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -96,9 +96,9 @@ namespace QuantConnect.Algorithm.Framework.Selection
|
||||
var uniqueUnderlyingSymbols = new HashSet<Symbol>();
|
||||
foreach (var optionSymbol in _optionChainSymbolSelector(algorithm.UtcTime))
|
||||
{
|
||||
if (optionSymbol.SecurityType != SecurityType.Option)
|
||||
if (optionSymbol.SecurityType != SecurityType.Option && optionSymbol.SecurityType != SecurityType.FutureOption)
|
||||
{
|
||||
throw new ArgumentException("optionChainSymbolSelector must return option symbols.");
|
||||
throw new ArgumentException("optionChainSymbolSelector must return option or futures options symbols.");
|
||||
}
|
||||
|
||||
// prevent creating duplicate option chains -- one per underlying
|
||||
@@ -118,4 +118,4 @@ namespace QuantConnect.Algorithm.Framework.Selection
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ class OptionUniverseSelectionModel(UniverseSelectionModel):
|
||||
|
||||
uniqueUnderlyingSymbols = set()
|
||||
for optionSymbol in self.optionChainSymbolSelector(algorithm.UtcTime):
|
||||
if optionSymbol.SecurityType != SecurityType.Option:
|
||||
raise ValueError("optionChainSymbolSelector must return option symbols.")
|
||||
if optionSymbol.SecurityType != SecurityType.Option and optionSymbol.SecurityType != SecurityType.FutureOption:
|
||||
raise ValueError("optionChainSymbolSelector must return option or futures options symbols.")
|
||||
|
||||
# prevent creating duplicate option chains -- one per underlying
|
||||
if optionSymbol.Underlying not in uniqueUnderlyingSymbols:
|
||||
@@ -73,7 +73,7 @@ class OptionUniverseSelectionModel(UniverseSelectionModel):
|
||||
symbol: Symbol of the option
|
||||
Returns:
|
||||
OptionChainUniverse for the given symbol'''
|
||||
if symbol.SecurityType != SecurityType.Option:
|
||||
if symbol.SecurityType != SecurityType.Option and symbol.SecurityType != SecurityType.FutureOption:
|
||||
raise ValueError("CreateOptionChain requires an option symbol.")
|
||||
|
||||
# rewrite non-canonical symbols to be canonical
|
||||
@@ -122,4 +122,4 @@ class OptionUniverseSelectionModel(UniverseSelectionModel):
|
||||
def Filter(self, filter):
|
||||
'''Defines the option chain universe filter'''
|
||||
# NOP
|
||||
return filter
|
||||
return filter
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# 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.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests that we receive the expected data when
|
||||
### we add future option contracts individually using <see cref="AddFutureOptionContract"/>
|
||||
### </summary>
|
||||
class AddFutureOptionContractDataStreamingRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.onDataReached = False
|
||||
self.invested = False
|
||||
self.symbolsReceived = []
|
||||
self.expectedSymbolsReceived = []
|
||||
self.dataReceived = {}
|
||||
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 1, 6)
|
||||
|
||||
self.es20h20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, datetime(2020, 3, 20)),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, datetime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
optionChains = self.OptionChainProvider.GetOptionContractList(self.es20h20, self.Time)
|
||||
optionChains += self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time)
|
||||
|
||||
for optionContract in optionChains:
|
||||
self.expectedSymbolsReceived.append(self.AddFutureOptionContract(optionContract, Resolution.Minute).Symbol)
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
if not data.HasData:
|
||||
return
|
||||
|
||||
self.onDataReached = True
|
||||
hasOptionQuoteBars = False
|
||||
|
||||
for qb in data.QuoteBars.Values:
|
||||
if qb.Symbol.SecurityType != SecurityType.FutureOption:
|
||||
continue
|
||||
|
||||
hasOptionQuoteBars = True
|
||||
|
||||
self.symbolsReceived.append(qb.Symbol)
|
||||
if qb.Symbol not in self.dataReceived:
|
||||
self.dataReceived[qb.Symbol] = []
|
||||
|
||||
self.dataReceived[qb.Symbol].append(qb)
|
||||
|
||||
if self.invested or not hasOptionQuoteBars:
|
||||
return
|
||||
|
||||
if data.ContainsKey(self.es20h20) and data.ContainsKey(self.es19m20):
|
||||
self.SetHoldings(self.es20h20, 0.2)
|
||||
self.SetHoldings(self.es19m20, 0.2)
|
||||
|
||||
self.invested = True
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
super().OnEndOfAlgorithm()
|
||||
|
||||
self.symbolsReceived = list(set(self.symbolsReceived))
|
||||
self.expectedSymbolsReceived = list(set(self.expectedSymbolsReceived))
|
||||
|
||||
if not self.onDataReached:
|
||||
raise AssertionError("OnData() was never called.")
|
||||
if len(self.symbolsReceived) != len(self.expectedSymbolsReceived):
|
||||
raise AssertionError(f"Expected {len(self.expectedSymbolsReceived)} option contracts Symbols, found {len(self.symbolsReceived)}")
|
||||
|
||||
missingSymbols = [expectedSymbol for expectedSymbol in self.expectedSymbolsReceived if expectedSymbol not in self.symbolsReceived]
|
||||
if any(missingSymbols):
|
||||
raise AssertionError(f'Symbols: "{", ".join(missingSymbols)}" were not found in OnData')
|
||||
|
||||
for expectedSymbol in self.expectedSymbolsReceived:
|
||||
data = self.dataReceived[expectedSymbol]
|
||||
for dataPoint in data:
|
||||
dataPoint.EndTime = datetime(1970, 1, 1)
|
||||
|
||||
nonDupeDataCount = len(set(data))
|
||||
if nonDupeDataCount < 1000:
|
||||
raise AssertionError(f"Received too few data points. Expected >=1000, found {nonDupeDataCount} for {expectedSymbol}")
|
||||
@@ -0,0 +1,127 @@
|
||||
# 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.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import *
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests that we only receive the option chain for a single future contract
|
||||
### in the option universe filter.
|
||||
### </summary>
|
||||
class AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.invested = False
|
||||
self.onDataReached = False
|
||||
self.optionFilterRan = False
|
||||
self.symbolsReceived = []
|
||||
self.expectedSymbolsReceived = []
|
||||
self.dataReceived = {}
|
||||
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 1, 6)
|
||||
|
||||
self.es = self.AddFuture(Futures.Indices.SP500EMini, Resolution.Minute, Market.CME)
|
||||
self.es.SetFilter(lambda futureFilter: futureFilter.Expiration(0, 365).ExpirationCycle([3, 6]))
|
||||
|
||||
self.AddFutureOption(self.es.Symbol, self.OptionContractUniverseFilterFunction)
|
||||
|
||||
def OptionContractUniverseFilterFunction(self, optionContracts: OptionFilterUniverse) -> OptionFilterUniverse:
|
||||
self.optionFilterRan = True
|
||||
|
||||
expiry = list(set([x.Underlying.ID.Date for x in optionContracts]))
|
||||
expiry = None if not any(expiry) else expiry[0]
|
||||
|
||||
symbol = [x.Underlying for x in optionContracts]
|
||||
symbol = None if not any(symbol) else symbol[0]
|
||||
|
||||
if expiry is None or symbol is None:
|
||||
raise AssertionError("Expected a single Option contract in the chain, found 0 contracts")
|
||||
|
||||
enumerator = optionContracts.GetEnumerator()
|
||||
while enumerator.MoveNext():
|
||||
self.expectedSymbolsReceived.append(enumerator.Current)
|
||||
|
||||
return optionContracts
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
if not data.HasData:
|
||||
return
|
||||
|
||||
self.onDataReached = True
|
||||
hasOptionQuoteBars = False
|
||||
|
||||
for qb in data.QuoteBars.Values:
|
||||
if qb.Symbol.SecurityType != SecurityType.FutureOption:
|
||||
continue
|
||||
|
||||
hasOptionQuoteBars = True
|
||||
|
||||
self.symbolsReceived.append(qb.Symbol)
|
||||
if qb.Symbol not in self.dataReceived:
|
||||
self.dataReceived[qb.Symbol] = []
|
||||
|
||||
self.dataReceived[qb.Symbol].append(qb)
|
||||
|
||||
if self.invested or not hasOptionQuoteBars:
|
||||
return
|
||||
|
||||
for chain in data.OptionChains.Values:
|
||||
futureInvested = False
|
||||
optionInvested = False
|
||||
|
||||
for option in chain.Contracts.Keys:
|
||||
if futureInvested and optionInvested:
|
||||
return
|
||||
|
||||
future = option.Underlying
|
||||
|
||||
if not optionInvested and data.ContainsKey(option):
|
||||
self.MarketOrder(option, 1)
|
||||
self.invested = True
|
||||
optionInvested = True
|
||||
|
||||
if not futureInvested and data.ContainsKey(future):
|
||||
self.MarketOrder(future, 1)
|
||||
self.invested = True
|
||||
futureInvested = True
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
super().OnEndOfAlgorithm()
|
||||
self.symbolsReceived = list(set(self.symbolsReceived))
|
||||
self.expectedSymbolsReceived = list(set(self.expectedSymbolsReceived))
|
||||
|
||||
if not self.optionFilterRan:
|
||||
raise AssertionError("Option chain filter was never ran")
|
||||
if not self.onDataReached:
|
||||
raise AssertionError("OnData() was never called.")
|
||||
if len(self.symbolsReceived) != len(self.expectedSymbolsReceived):
|
||||
raise AssertionError(f"Expected {len(self.expectedSymbolsReceived)} option contracts Symbols, found {len(self.symbolsReceived)}")
|
||||
|
||||
missingSymbols = [expectedSymbol for expectedSymbol in self.expectedSymbolsReceived if expectedSymbol not in self.symbolsReceived]
|
||||
if any(missingSymbols):
|
||||
raise AssertionError(f'Symbols: "{", ".join(missingSymbols)}" were not found in OnData')
|
||||
|
||||
for expectedSymbol in self.expectedSymbolsReceived:
|
||||
data = self.dataReceived[expectedSymbol]
|
||||
for dataPoint in data:
|
||||
dataPoint.EndTime = datetime(1970, 1, 1)
|
||||
|
||||
nonDupeDataCount = len(set(data))
|
||||
if nonDupeDataCount < 1000:
|
||||
raise AssertionError(f"Received too few data points. Expected >=1000, found {nonDupeDataCount} for {expectedSymbol}")
|
||||
@@ -0,0 +1,97 @@
|
||||
# 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
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import clr
|
||||
from System import *
|
||||
from System.Reflection import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Orders import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests In The Money (ITM) future option calls across different strike prices.
|
||||
### We expect 6 orders from the algorithm, which are:
|
||||
###
|
||||
### * (1) Initial entry, buy ES Call Option (ES19M20 expiring ITM)
|
||||
### * (2) Initial entry, sell ES Call Option at different strike (ES20H20 expiring ITM)
|
||||
### * [2] Option assignment, opens a position in the underlying (ES20H20, Qty: -1)
|
||||
### * [2] Future contract liquidation, due to impending expiry
|
||||
### * [1] Option exercise, receive 1 ES19M20 future contract
|
||||
### * [1] Liquidate ES19M20 contract, due to expiry
|
||||
###
|
||||
### Additionally, we test delistings for future options and assert that our
|
||||
### portfolio holdings reflect the orders the algorithm has submitted.
|
||||
### </summary>
|
||||
class FutureOptionBuySellCallIntradayRegressionAlgorithm(QCAlgorithm):
|
||||
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 6, 30)
|
||||
|
||||
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
# which causes delisting events to never be processed, thus leading to options that might never
|
||||
# be exercised until the next data point arrives.
|
||||
self.AddEquity("AAPL", Resolution.Daily)
|
||||
|
||||
self.es20h20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 3, 20)
|
||||
),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 6, 19)
|
||||
),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
# Select a future option expiring ITM, and adds it to the algorithm.
|
||||
self.esOptions = [
|
||||
self.AddFutureOptionContract(i, Resolution.Minute).Symbol for i in (self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) + self.OptionChainProvider.GetOptionContractList(self.es20h20, self.Time)) if i.ID.StrikePrice == 3200.0 and i.ID.OptionRight == OptionRight.Call
|
||||
]
|
||||
|
||||
self.expectedContracts = [
|
||||
Symbol.CreateOption(self.es20h20, Market.CME, OptionStyle.American, OptionRight.Call, 3200.0, datetime(2020, 3, 20)),
|
||||
Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200.0, datetime(2020, 6, 19))
|
||||
]
|
||||
|
||||
for esOption in self.esOptions:
|
||||
if esOption not in self.expectedContracts:
|
||||
raise AssertionError(f"Contract {esOption} was not found in the chain")
|
||||
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduleCallbackBuy)
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.Noon, self.ScheduleCallbackLiquidate)
|
||||
|
||||
def ScheduleCallbackBuy(self):
|
||||
self.MarketOrder(self.esOptions[0], 1)
|
||||
self.MarketOrder(self.esOptions[1], -1)
|
||||
|
||||
def ScheduleCallbackLiquidate(self):
|
||||
self.Liquidate()
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self.Portfolio.Invested:
|
||||
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")
|
||||
|
||||
143
Algorithm.Python/FutureOptionCallITMExpiryRegressionAlgorithm.py
Normal file
143
Algorithm.Python/FutureOptionCallITMExpiryRegressionAlgorithm.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# 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
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import clr
|
||||
from System import *
|
||||
from System.Reflection import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Orders import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests In The Money (ITM) future option expiry for calls.
|
||||
### We expect 3 orders from the algorithm, which are:
|
||||
###
|
||||
### * Initial entry, buy ES Call Option (expiring ITM)
|
||||
### * Option exercise, receiving ES future contracts
|
||||
### * Future contract liquidation, due to impending expiry
|
||||
###
|
||||
### Additionally, we test delistings for future options and assert that our
|
||||
### portfolio holdings reflect the orders the algorithm has submitted.
|
||||
### </summary>
|
||||
class FutureOptionCallITMExpiryRegressionAlgorithm(QCAlgorithm):
|
||||
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 6, 30)
|
||||
|
||||
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
# which causes delisting events to never be processed, thus leading to options that might never
|
||||
# be exercised until the next data point arrives.
|
||||
self.AddEquity("AAPL", Resolution.Daily)
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 6, 19)
|
||||
),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
# Select a future option expiring ITM, and adds it to the algorithm.
|
||||
self.esOption = self.AddFutureOptionContract(
|
||||
list(
|
||||
sorted([x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3200.0 and x.ID.OptionRight == OptionRight.Call], key=lambda x: x.ID.StrikePrice, reverse=True)
|
||||
)[0], Resolution.Minute).Symbol
|
||||
|
||||
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200.0, datetime(2020, 6, 19))
|
||||
if self.esOption != self.expectedContract:
|
||||
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain")
|
||||
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduleCallback)
|
||||
|
||||
def ScheduleCallback(self):
|
||||
self.MarketOrder(self.esOption, 1)
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
# Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
# the expected time. These assertions detect bug #4872
|
||||
for delisting in data.Delistings.Values:
|
||||
if delisting.Type == DelistingType.Warning:
|
||||
if delisting.Time != datetime(2020, 6, 19):
|
||||
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}")
|
||||
elif delisting.Type == DelistingType.Delisted:
|
||||
if delisting.Time != datetime(2020, 6, 20):
|
||||
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}")
|
||||
|
||||
def OnOrderEvent(self, orderEvent: OrderEvent):
|
||||
if orderEvent.Status != OrderStatus.Filled:
|
||||
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return
|
||||
|
||||
if not self.Securities.ContainsKey(orderEvent.Symbol):
|
||||
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
|
||||
|
||||
security = self.Securities[orderEvent.Symbol]
|
||||
if security.Symbol == self.es19m20:
|
||||
self.AssertFutureOptionOrderExercise(orderEvent, security, self.Securities[self.expectedContract])
|
||||
elif security.Symbol == self.expectedContract:
|
||||
# Expected contract is ES19H21 Call Option expiring ITM @ 3250
|
||||
self.AssertFutureOptionContractOrder(orderEvent, security)
|
||||
else:
|
||||
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
|
||||
|
||||
self.Log(f"{self.Time} -- {orderEvent.Symbol} :: Price: {self.Securities[orderEvent.Symbol].Holdings.Price} Qty: {self.Securities[orderEvent.Symbol].Holdings.Quantity} Direction: {orderEvent.Direction} Msg: {orderEvent.Message}")
|
||||
|
||||
def AssertFutureOptionOrderExercise(self, orderEvent: OrderEvent, future: Security, optionContract: Security):
|
||||
# We expect the liquidation to occur on the day of the delisting (while the market is open),
|
||||
# but currently we liquidate at the next market open (AAPL open) which happens to be
|
||||
# at 9:30:00 Eastern Time. For unknown reasons, the delisting happens two minutes after the
|
||||
# market open.
|
||||
# Read more about the issue affecting this test here: https://github.com/QuantConnect/Lean/issues/4980
|
||||
expectedLiquidationTimeUtc = datetime(2020, 6, 22, 13, 32, 0)
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Sell and future.Holdings.Quantity != 0:
|
||||
# We expect the contract to have been liquidated immediately
|
||||
raise AssertionError(f"Did not liquidate existing holdings for Symbol {future.Symbol}")
|
||||
if orderEvent.Direction == OrderDirection.Sell and orderEvent.UtcTime.replace(tzinfo=None) != expectedLiquidationTimeUtc:
|
||||
raise AssertionError(f"Liquidated future contract, but not at the expected time. Expected: {expectedLiquidationTimeUtc} - found {orderEvent.UtcTime.replace(tzinfo=None)}");
|
||||
|
||||
# No way to detect option exercise orders or any other kind of special orders
|
||||
# other than matching strings, for now.
|
||||
if "Option Exercise" in orderEvent.Message:
|
||||
if orderEvent.FillPrice != 3200.0:
|
||||
raise AssertionError("Option did not exercise at expected strike price (3200)")
|
||||
|
||||
if future.Holdings.Quantity != 1:
|
||||
# Here, we expect to have some holdings in the underlying, but not in the future option anymore.
|
||||
raise AssertionError(f"Exercised option contract, but we have no holdings for Future {future.Symbol}")
|
||||
|
||||
if optionContract.Holdings.Quantity != 0:
|
||||
raise AssertionError(f"Exercised option contract, but we have holdings for Option contract {optionContract.Symbol}")
|
||||
|
||||
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
|
||||
if orderEvent.Direction == OrderDirection.Buy and option.Holdings.Quantity != 1:
|
||||
raise AssertionError(f"No holdings were created for option contract {option.Symbol}")
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != 0:
|
||||
raise AssertionError(f"Holdings were found after a filled option exercise")
|
||||
|
||||
if "Exercise" in orderEvent.Message and option.Holdings.Quantity != 0:
|
||||
raise AssertionError(f"Holdings were found after exercising option contract {option.Symbol}")
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self.Portfolio.Invested:
|
||||
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")
|
||||
127
Algorithm.Python/FutureOptionCallOTMExpiryRegressionAlgorithm.py
Normal file
127
Algorithm.Python/FutureOptionCallOTMExpiryRegressionAlgorithm.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# 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
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import clr
|
||||
from System import *
|
||||
from System.Reflection import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Orders import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests Out of The Money (OTM) future option expiry for calls.
|
||||
### We expect 1 order from the algorithm, which are:
|
||||
###
|
||||
### * Initial entry, buy ES Call Option (expiring OTM)
|
||||
### - contract expires worthless, not exercised, so never opened a position in the underlying
|
||||
###
|
||||
### Additionally, we test delistings for future options and assert that our
|
||||
### portfolio holdings reflect the orders the algorithm has submitted.
|
||||
### </summary>
|
||||
### <remarks>
|
||||
### Total Trades in regression algorithm should be 1, but expiration is counted as a trade.
|
||||
### See related issue: https://github.com/QuantConnect/Lean/issues/4854
|
||||
### </remarks>
|
||||
class FutureOptionCallOTMExpiryRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 6, 30)
|
||||
|
||||
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
# which causes delisting events to never be processed, thus leading to options that might never
|
||||
# be exercised until the next data point arrives.
|
||||
self.AddEquity("AAPL", Resolution.Daily)
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
# Select a future option expiring ITM, and adds it to the algorithm.
|
||||
self.esOption = self.AddFutureOptionContract(
|
||||
list(
|
||||
sorted(
|
||||
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice >= 3300.0 and x.ID.OptionRight == OptionRight.Call],
|
||||
key=lambda x: x.ID.StrikePrice
|
||||
)
|
||||
)[0], Resolution.Minute).Symbol
|
||||
|
||||
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3300.0, datetime(2020, 6, 19))
|
||||
if self.esOption != self.expectedContract:
|
||||
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
|
||||
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
|
||||
|
||||
def ScheduledMarketOrder(self):
|
||||
self.MarketOrder(self.esOption, 1)
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
# Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
# the expected time. These assertions detect bug #4872
|
||||
for delisting in data.Delistings.Values:
|
||||
if delisting.Type == DelistingType.Warning:
|
||||
if delisting.Time != datetime(2020, 6, 19):
|
||||
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
|
||||
if delisting.Type == DelistingType.Delisted:
|
||||
if delisting.Time != datetime(2020, 6, 20):
|
||||
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
|
||||
|
||||
|
||||
def OnOrderEvent(self, orderEvent: OrderEvent):
|
||||
if orderEvent.Status != OrderStatus.Filled:
|
||||
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return
|
||||
|
||||
if not self.Securities.ContainsKey(orderEvent.Symbol):
|
||||
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
|
||||
|
||||
security = self.Securities[orderEvent.Symbol]
|
||||
if security.Symbol == self.es19m20:
|
||||
raise AssertionError("Invalid state: did not expect a position for the underlying to be opened, since this contract expires OTM")
|
||||
|
||||
# Expected contract is ES19M20 Call Option expiring OTM @ 3300
|
||||
if (security.Symbol == self.expectedContract):
|
||||
self.AssertFutureOptionContractOrder(orderEvent, security)
|
||||
else:
|
||||
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
|
||||
|
||||
self.Log(f"{orderEvent}");
|
||||
|
||||
|
||||
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
|
||||
if orderEvent.Direction == OrderDirection.Buy and option.Holdings.Quantity != 1:
|
||||
raise AssertionError(f"No holdings were created for option contract {option.Symbol}");
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != 0:
|
||||
raise AssertionError("Holdings were found after a filled option exercise");
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Sell and "OTM" not in orderEvent.Message:
|
||||
raise AssertionError("Contract did not expire OTM");
|
||||
|
||||
if "Exercise" in orderEvent.Message:
|
||||
raise AssertionError("Exercised option, even though it expires OTM");
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self.Portfolio.Invested:
|
||||
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")
|
||||
142
Algorithm.Python/FutureOptionPutITMExpiryRegressionAlgorithm.py
Normal file
142
Algorithm.Python/FutureOptionPutITMExpiryRegressionAlgorithm.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# 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
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import clr
|
||||
from System import *
|
||||
from System.Reflection import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Orders import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests In The Money (ITM) future option expiry for puts.
|
||||
### We expect 3 orders from the algorithm, which are:
|
||||
###
|
||||
### * Initial entry, buy ES Put Option (expiring ITM) (buy, qty 1)
|
||||
### * Option exercise, receiving short ES future contracts (sell, qty -1)
|
||||
### * Future contract liquidation, due to impending expiry (buy qty 1)
|
||||
###
|
||||
### Additionally, we test delistings for future options and assert that our
|
||||
### portfolio holdings reflect the orders the algorithm has submitted.
|
||||
### </summary>
|
||||
class FutureOptionPutITMExpiryRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 6, 30)
|
||||
|
||||
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
# which causes delisting events to never be processed, thus leading to options that might never
|
||||
# be exercised until the next data point arrives.
|
||||
self.AddEquity("AAPL", Resolution.Daily)
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 6, 19)
|
||||
),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
# Select a future option expiring ITM, and adds it to the algorithm.
|
||||
self.esOption = self.AddFutureOptionContract(
|
||||
list(
|
||||
sorted([x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice >= 3300.0 and x.ID.OptionRight == OptionRight.Put], key=lambda x: x.ID.StrikePrice)
|
||||
)[0], Resolution.Minute).Symbol
|
||||
|
||||
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3300.0, datetime(2020, 6, 19))
|
||||
if self.esOption != self.expectedContract:
|
||||
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain")
|
||||
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduleCallback)
|
||||
|
||||
def ScheduleCallback(self):
|
||||
self.MarketOrder(self.esOption, 1)
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
# Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
# the expected time. These assertions detect bug #4872
|
||||
for delisting in data.Delistings.Values:
|
||||
if delisting.Type == DelistingType.Warning:
|
||||
if delisting.Time != datetime(2020, 6, 19):
|
||||
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}")
|
||||
elif delisting.Type == DelistingType.Delisted:
|
||||
if delisting.Time != datetime(2020, 6, 20):
|
||||
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}")
|
||||
|
||||
def OnOrderEvent(self, orderEvent: OrderEvent):
|
||||
if orderEvent.Status != OrderStatus.Filled:
|
||||
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return
|
||||
|
||||
if not self.Securities.ContainsKey(orderEvent.Symbol):
|
||||
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
|
||||
|
||||
security = self.Securities[orderEvent.Symbol]
|
||||
if security.Symbol == self.es19m20:
|
||||
self.AssertFutureOptionOrderExercise(orderEvent, security, self.Securities[self.expectedContract])
|
||||
elif security.Symbol == self.expectedContract:
|
||||
# Expected contract is ES19M20 Call Option expiring ITM @ 3250
|
||||
self.AssertFutureOptionContractOrder(orderEvent, security)
|
||||
else:
|
||||
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
|
||||
|
||||
self.Log(f"{self.Time} -- {orderEvent.Symbol} :: Price: {self.Securities[orderEvent.Symbol].Holdings.Price} Qty: {self.Securities[orderEvent.Symbol].Holdings.Quantity} Direction: {orderEvent.Direction} Msg: {orderEvent.Message}")
|
||||
|
||||
def AssertFutureOptionOrderExercise(self, orderEvent: OrderEvent, future: Security, optionContract: Security):
|
||||
# We expect the liquidation to occur on the day of the delisting (while the market is open),
|
||||
# but currently we liquidate at the next market open (AAPL open) which happens to be
|
||||
# at 9:30:00 Eastern Time. For unknown reasons, the delisting happens two minutes after the
|
||||
# market open.
|
||||
# Read more about the issue affecting this test here: https://github.com/QuantConnect/Lean/issues/4980
|
||||
expectedLiquidationTimeUtc = datetime(2020, 6, 22, 13, 32, 0)
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Buy and future.Holdings.Quantity != 0:
|
||||
# We expect the contract to have been liquidated immediately
|
||||
raise AssertionError(f"Did not liquidate existing holdings for Symbol {future.Symbol}")
|
||||
if orderEvent.Direction == OrderDirection.Buy and orderEvent.UtcTime.replace(tzinfo=None) != expectedLiquidationTimeUtc:
|
||||
raise AssertionError(f"Liquidated future contract, but not at the expected time. Expected: {expectedLiquidationTimeUtc} - found {orderEvent.UtcTime.replace(tzinfo=None)}");
|
||||
|
||||
# No way to detect option exercise orders or any other kind of special orders
|
||||
# other than matching strings, for now.
|
||||
if "Option Exercise" in orderEvent.Message:
|
||||
if orderEvent.FillPrice != 3300.0:
|
||||
raise AssertionError("Option did not exercise at expected strike price (3300)")
|
||||
|
||||
if future.Holdings.Quantity != -1:
|
||||
# Here, we expect to have some holdings in the underlying, but not in the future option anymore.
|
||||
raise AssertionError(f"Exercised option contract, but we have no holdings for Future {future.Symbol}")
|
||||
|
||||
if optionContract.Holdings.Quantity != 0:
|
||||
raise AssertionError(f"Exercised option contract, but we have holdings for Option contract {optionContract.Symbol}")
|
||||
|
||||
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
|
||||
if orderEvent.Direction == OrderDirection.Buy and option.Holdings.Quantity != 1:
|
||||
raise AssertionError(f"No holdings were created for option contract {option.Symbol}")
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != 0:
|
||||
raise AssertionError(f"Holdings were found after a filled option exercise")
|
||||
|
||||
if "Exercise" in orderEvent.Message and option.Holdings.Quantity != 0:
|
||||
raise AssertionError(f"Holdings were found after exercising option contract {option.Symbol}")
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self.Portfolio.Invested:
|
||||
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")
|
||||
127
Algorithm.Python/FutureOptionPutOTMExpiryRegressionAlgorithm.py
Normal file
127
Algorithm.Python/FutureOptionPutOTMExpiryRegressionAlgorithm.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# 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
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import clr
|
||||
from System import *
|
||||
from System.Reflection import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Orders import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests Out of The Money (OTM) future option expiry for puts.
|
||||
### We expect 1 order from the algorithm, which are:
|
||||
###
|
||||
### * Initial entry, buy ES Put Option (expiring OTM)
|
||||
### - contract expires worthless, not exercised, so never opened a position in the underlying
|
||||
###
|
||||
### Additionally, we test delistings for future options and assert that our
|
||||
### portfolio holdings reflect the orders the algorithm has submitted.
|
||||
### </summary>
|
||||
### <remarks>
|
||||
### Total Trades in regression algorithm should be 1, but expiration is counted as a trade.
|
||||
### </remarks>
|
||||
class FutureOptionPutOTMExpiryRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 6, 30)
|
||||
|
||||
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
# which causes delisting events to never be processed, thus leading to options that might never
|
||||
# be exercised until the next data point arrives.
|
||||
self.AddEquity("AAPL", Resolution.Daily)
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
# Select a future option expiring ITM, and adds it to the algorithm.
|
||||
self.esOption = self.AddFutureOptionContract(
|
||||
list(
|
||||
sorted(
|
||||
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3150.0 and x.ID.OptionRight == OptionRight.Put],
|
||||
key=lambda x: x.ID.StrikePrice,
|
||||
reverse=True
|
||||
)
|
||||
)[0], Resolution.Minute).Symbol
|
||||
|
||||
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3150.0, datetime(2020, 6, 19))
|
||||
if self.esOption != self.expectedContract:
|
||||
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
|
||||
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
|
||||
|
||||
def ScheduledMarketOrder(self):
|
||||
self.MarketOrder(self.esOption, 1)
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
# Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
# the expected time. These assertions detect bug #4872
|
||||
for delisting in data.Delistings.Values:
|
||||
if delisting.Type == DelistingType.Warning:
|
||||
if delisting.Time != datetime(2020, 6, 19):
|
||||
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
|
||||
if delisting.Type == DelistingType.Delisted:
|
||||
if delisting.Time != datetime(2020, 6, 20):
|
||||
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
|
||||
|
||||
|
||||
def OnOrderEvent(self, orderEvent: OrderEvent):
|
||||
if orderEvent.Status != OrderStatus.Filled:
|
||||
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return
|
||||
|
||||
if not self.Securities.ContainsKey(orderEvent.Symbol):
|
||||
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
|
||||
|
||||
security = self.Securities[orderEvent.Symbol]
|
||||
if security.Symbol == self.es19m20:
|
||||
raise AssertionError("Invalid state: did not expect a position for the underlying to be opened, since this contract expires OTM")
|
||||
|
||||
# Expected contract is ES19M20 Put Option expiring OTM @ 3200
|
||||
if (security.Symbol == self.expectedContract):
|
||||
self.AssertFutureOptionContractOrder(orderEvent, security)
|
||||
else:
|
||||
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
|
||||
|
||||
self.Log(f"{orderEvent}");
|
||||
|
||||
|
||||
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
|
||||
if orderEvent.Direction == OrderDirection.Buy and option.Holdings.Quantity != 1:
|
||||
raise AssertionError(f"No holdings were created for option contract {option.Symbol}");
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != 0:
|
||||
raise AssertionError("Holdings were found after a filled option exercise");
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Sell and "OTM" not in orderEvent.Message:
|
||||
raise AssertionError("Contract did not expire OTM");
|
||||
|
||||
if "Exercise" in orderEvent.Message:
|
||||
raise AssertionError("Exercised option, even though it expires OTM");
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self.Portfolio.Invested:
|
||||
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")
|
||||
@@ -0,0 +1,132 @@
|
||||
# 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
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import clr
|
||||
from System import *
|
||||
from System.Reflection import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Orders import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests In The Money (ITM) future option expiry for short calls.
|
||||
### We expect 3 orders from the algorithm, which are:
|
||||
###
|
||||
### * Initial entry, sell ES Call Option (expiring ITM)
|
||||
### * Option assignment, sell 1 contract of the underlying (ES)
|
||||
### * Future contract expiry, liquidation (buy 1 ES future)
|
||||
###
|
||||
### Additionally, we test delistings for future options and assert that our
|
||||
### portfolio holdings reflect the orders the algorithm has submitted.
|
||||
### </summary>
|
||||
class FutureOptionShortCallITMExpiryRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 6, 30)
|
||||
|
||||
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
# which causes delisting events to never be processed, thus leading to options that might never
|
||||
# be exercised until the next data point arrives.
|
||||
self.AddEquity("AAPL", Resolution.Daily)
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
# Select a future option expiring ITM, and adds it to the algorithm.
|
||||
self.esOption = self.AddFutureOptionContract(
|
||||
list(
|
||||
sorted(
|
||||
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3100.0 and x.ID.OptionRight == OptionRight.Call],
|
||||
key=lambda x: x.ID.StrikePrice,
|
||||
reverse=True
|
||||
)
|
||||
)[0], Resolution.Minute).Symbol
|
||||
|
||||
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3100.0, datetime(2020, 6, 19))
|
||||
if self.esOption != self.expectedContract:
|
||||
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
|
||||
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
|
||||
|
||||
def ScheduledMarketOrder(self):
|
||||
self.MarketOrder(self.esOption, -1)
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
# Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
# the expected time. These assertions detect bug #4872
|
||||
for delisting in data.Delistings.Values:
|
||||
if delisting.Type == DelistingType.Warning:
|
||||
if delisting.Time != datetime(2020, 6, 19):
|
||||
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
|
||||
if delisting.Type == DelistingType.Delisted:
|
||||
if delisting.Time != datetime(2020, 6, 20):
|
||||
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
|
||||
|
||||
|
||||
def OnOrderEvent(self, orderEvent: OrderEvent):
|
||||
if orderEvent.Status != OrderStatus.Filled:
|
||||
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return
|
||||
|
||||
if not self.Securities.ContainsKey(orderEvent.Symbol):
|
||||
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
|
||||
|
||||
security = self.Securities[orderEvent.Symbol]
|
||||
if security.Symbol == self.es19m20:
|
||||
self.AssertFutureOptionOrderExercise(orderEvent, security, self.Securities[self.expectedContract])
|
||||
|
||||
elif security.Symbol == self.expectedContract:
|
||||
self.AssertFutureOptionContractOrder(orderEvent, security)
|
||||
|
||||
else:
|
||||
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
|
||||
|
||||
self.Log(f"{orderEvent}");
|
||||
|
||||
def AssertFutureOptionOrderExercise(self, orderEvent: OrderEvent, future: Security, optionContract: Security):
|
||||
if "Assignment" in orderEvent.Message:
|
||||
if orderEvent.FillPrice != 3100.0:
|
||||
raise AssertionError("Option was not assigned at expected strike price (3100)")
|
||||
|
||||
if orderEvent.Direction != OrderDirection.Sell or future.Holdings.Quantity != -1:
|
||||
raise AssertionError(f"Expected Qty: -1 futures holdings for assigned future {future.Symbol}, found {future.Holdings.Quantity}")
|
||||
|
||||
return
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Buy and future.Holdings.Quantity != 0:
|
||||
# We buy back the underlying at expiration, so we expect a neutral position then
|
||||
raise AssertionError(f"Expected no holdings when liquidating future contract {future.Symbol}")
|
||||
|
||||
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
|
||||
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != -1:
|
||||
raise AssertionError(f"No holdings were created for option contract {option.Symbol}");
|
||||
|
||||
if orderEvent.IsAssignment and option.Holdings.Quantity != 0:
|
||||
raise AssertionError(f"Holdings were found after option contract was assigned: {option.Symbol}")
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self.Portfolio.Invested:
|
||||
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")
|
||||
@@ -0,0 +1,119 @@
|
||||
# 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
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import clr
|
||||
from System import *
|
||||
from System.Reflection import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Orders import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests Out of The Money (OTM) future option expiry for short calls.
|
||||
### We expect 1 order from the algorithm, which are:
|
||||
###
|
||||
### * Initial entry, sell ES Call Option (expiring OTM)
|
||||
### - Profit the option premium, since the option was not assigned.
|
||||
###
|
||||
### Additionally, we test delistings for future options and assert that our
|
||||
### portfolio holdings reflect the orders the algorithm has submitted.
|
||||
### </summary>
|
||||
class FutureOptionShortCallOTMExpiryRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 6, 30)
|
||||
|
||||
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
# which causes delisting events to never be processed, thus leading to options that might never
|
||||
# be exercised until the next data point arrives.
|
||||
self.AddEquity("AAPL", Resolution.Daily)
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
# Select a future option expiring ITM, and adds it to the algorithm.
|
||||
self.esOption = self.AddFutureOptionContract(
|
||||
list(
|
||||
sorted(
|
||||
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice >= 3400.0 and x.ID.OptionRight == OptionRight.Call],
|
||||
key=lambda x: x.ID.StrikePrice
|
||||
)
|
||||
)[0], Resolution.Minute).Symbol
|
||||
|
||||
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3400.0, datetime(2020, 6, 19))
|
||||
if self.esOption != self.expectedContract:
|
||||
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
|
||||
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
|
||||
|
||||
def ScheduledMarketOrder(self):
|
||||
self.MarketOrder(self.esOption, -1)
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
# Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
# the expected time. These assertions detect bug #4872
|
||||
for delisting in data.Delistings.Values:
|
||||
if delisting.Type == DelistingType.Warning:
|
||||
if delisting.Time != datetime(2020, 6, 19):
|
||||
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
|
||||
if delisting.Type == DelistingType.Delisted:
|
||||
if delisting.Time != datetime(2020, 6, 20):
|
||||
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
|
||||
|
||||
|
||||
def OnOrderEvent(self, orderEvent: OrderEvent):
|
||||
if orderEvent.Status != OrderStatus.Filled:
|
||||
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return
|
||||
|
||||
if not self.Securities.ContainsKey(orderEvent.Symbol):
|
||||
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
|
||||
|
||||
security = self.Securities[orderEvent.Symbol]
|
||||
if security.Symbol == self.es19m20:
|
||||
raise AssertionError(f"Expected no order events for underlying Symbol {security.Symbol}")
|
||||
|
||||
if security.Symbol == self.expectedContract:
|
||||
self.AssertFutureOptionContractOrder(orderEvent, security)
|
||||
|
||||
else:
|
||||
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
|
||||
|
||||
self.Log(f"{orderEvent}");
|
||||
|
||||
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, optionContract: Security):
|
||||
if orderEvent.Direction == OrderDirection.Sell and optionContract.Holdings.Quantity != -1:
|
||||
raise AssertionError(f"No holdings were created for option contract {optionContract.Symbol}")
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Buy and optionContract.Holdings.Quantity != 0:
|
||||
raise AssertionError("Expected no options holdings after closing position")
|
||||
|
||||
if orderEvent.IsAssignment:
|
||||
raise AssertionError(f"Assignment was not expected for {orderEvent.Symbol}")
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self.Portfolio.Invested:
|
||||
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")
|
||||
@@ -0,0 +1,132 @@
|
||||
# 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
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import clr
|
||||
from System import *
|
||||
from System.Reflection import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Orders import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests In The Money (ITM) future option expiry for short puts.
|
||||
### We expect 3 orders from the algorithm, which are:
|
||||
###
|
||||
### * Initial entry, sell ES Put Option (expiring ITM)
|
||||
### * Option assignment, buy 1 contract of the underlying (ES)
|
||||
### * Future contract expiry, liquidation (sell 1 ES future)
|
||||
###
|
||||
### Additionally, we test delistings for future options and assert that our
|
||||
### portfolio holdings reflect the orders the algorithm has submitted.
|
||||
### </summary>
|
||||
class FutureOptionShortPutITMExpiryRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 6, 30)
|
||||
|
||||
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
# which causes delisting events to never be processed, thus leading to options that might never
|
||||
# be exercised until the next data point arrives.
|
||||
self.AddEquity("AAPL", Resolution.Daily)
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
# Select a future option expiring ITM, and adds it to the algorithm.
|
||||
self.esOption = self.AddFutureOptionContract(
|
||||
list(
|
||||
sorted(
|
||||
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3400.0 and x.ID.OptionRight == OptionRight.Put],
|
||||
key=lambda x: x.ID.StrikePrice,
|
||||
reverse=True
|
||||
)
|
||||
)[0], Resolution.Minute).Symbol
|
||||
|
||||
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3400.0, datetime(2020, 6, 19))
|
||||
if self.esOption != self.expectedContract:
|
||||
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
|
||||
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
|
||||
|
||||
def ScheduledMarketOrder(self):
|
||||
self.MarketOrder(self.esOption, -1)
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
# Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
# the expected time. These assertions detect bug #4872
|
||||
for delisting in data.Delistings.Values:
|
||||
if delisting.Type == DelistingType.Warning:
|
||||
if delisting.Time != datetime(2020, 6, 19):
|
||||
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
|
||||
if delisting.Type == DelistingType.Delisted:
|
||||
if delisting.Time != datetime(2020, 6, 20):
|
||||
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
|
||||
|
||||
|
||||
def OnOrderEvent(self, orderEvent: OrderEvent):
|
||||
if orderEvent.Status != OrderStatus.Filled:
|
||||
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return
|
||||
|
||||
if not self.Securities.ContainsKey(orderEvent.Symbol):
|
||||
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
|
||||
|
||||
security = self.Securities[orderEvent.Symbol]
|
||||
if security.Symbol == self.es19m20:
|
||||
self.AssertFutureOptionOrderExercise(orderEvent, security, self.Securities[self.expectedContract])
|
||||
|
||||
elif security.Symbol == self.expectedContract:
|
||||
self.AssertFutureOptionContractOrder(orderEvent, security)
|
||||
|
||||
else:
|
||||
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
|
||||
|
||||
self.Log(f"{orderEvent}");
|
||||
|
||||
def AssertFutureOptionOrderExercise(self, orderEvent: OrderEvent, future: Security, optionContract: Security):
|
||||
if "Assignment" in orderEvent.Message:
|
||||
if orderEvent.FillPrice != 3400.0:
|
||||
raise AssertionError("Option was not assigned at expected strike price (3400)")
|
||||
|
||||
if orderEvent.Direction != OrderDirection.Buy or future.Holdings.Quantity != 1:
|
||||
raise AssertionError(f"Expected Qty: 1 futures holdings for assigned future {future.Symbol}, found {future.Holdings.Quantity}")
|
||||
|
||||
return
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Sell and future.Holdings.Quantity != 0:
|
||||
# We buy back the underlying at expiration, so we expect a neutral position then
|
||||
raise AssertionError(f"Expected no holdings when liquidating future contract {future.Symbol}")
|
||||
|
||||
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
|
||||
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != -1:
|
||||
raise AssertionError(f"No holdings were created for option contract {option.Symbol}");
|
||||
|
||||
if orderEvent.IsAssignment and option.Holdings.Quantity != 0:
|
||||
raise AssertionError(f"Holdings were found after option contract was assigned: {option.Symbol}")
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self.Portfolio.Invested:
|
||||
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")
|
||||
@@ -0,0 +1,120 @@
|
||||
# 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
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import clr
|
||||
from System import *
|
||||
from System.Reflection import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Orders import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Securities.Future import *
|
||||
from QuantConnect import Market
|
||||
|
||||
|
||||
### <summary>
|
||||
### This regression algorithm tests Out of The Money (OTM) future option expiry for short puts.
|
||||
### We expect 1 order from the algorithm, which are:
|
||||
###
|
||||
### * Initial entry, sell ES Put Option (expiring OTM)
|
||||
### - Profit the option premium, since the option was not assigned.
|
||||
###
|
||||
### Additionally, we test delistings for future options and assert that our
|
||||
### portfolio holdings reflect the orders the algorithm has submitted.
|
||||
### </summary>
|
||||
class FutureOptionShortPutOTMExpiryRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2020, 1, 5)
|
||||
self.SetEndDate(2020, 6, 30)
|
||||
|
||||
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
|
||||
# which causes delisting events to never be processed, thus leading to options that might never
|
||||
# be exercised until the next data point arrives.
|
||||
self.AddEquity("AAPL", Resolution.Daily)
|
||||
|
||||
self.es19m20 = self.AddFutureContract(
|
||||
Symbol.CreateFuture(
|
||||
Futures.Indices.SP500EMini,
|
||||
Market.CME,
|
||||
datetime(2020, 6, 19)),
|
||||
Resolution.Minute).Symbol
|
||||
|
||||
# Select a future option expiring ITM, and adds it to the algorithm.
|
||||
self.esOption = self.AddFutureOptionContract(
|
||||
list(
|
||||
sorted(
|
||||
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3000.0 and x.ID.OptionRight == OptionRight.Put],
|
||||
key=lambda x: x.ID.StrikePrice,
|
||||
reverse=True
|
||||
)
|
||||
)[0], Resolution.Minute).Symbol
|
||||
|
||||
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3000.0, datetime(2020, 6, 19))
|
||||
if self.esOption != self.expectedContract:
|
||||
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
|
||||
|
||||
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
|
||||
|
||||
def ScheduledMarketOrder(self):
|
||||
self.MarketOrder(self.esOption, -1)
|
||||
|
||||
def OnData(self, data: Slice):
|
||||
# Assert delistings, so that we can make sure that we receive the delisting warnings at
|
||||
# the expected time. These assertions detect bug #4872
|
||||
for delisting in data.Delistings.Values:
|
||||
if delisting.Type == DelistingType.Warning:
|
||||
if delisting.Time != datetime(2020, 6, 19):
|
||||
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
|
||||
|
||||
if delisting.Type == DelistingType.Delisted:
|
||||
if delisting.Time != datetime(2020, 6, 20):
|
||||
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
|
||||
|
||||
|
||||
def OnOrderEvent(self, orderEvent: OrderEvent):
|
||||
if orderEvent.Status != OrderStatus.Filled:
|
||||
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
|
||||
return
|
||||
|
||||
if not self.Securities.ContainsKey(orderEvent.Symbol):
|
||||
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
|
||||
|
||||
security = self.Securities[orderEvent.Symbol]
|
||||
if security.Symbol == self.es19m20:
|
||||
raise AssertionError(f"Expected no order events for underlying Symbol {security.Symbol}")
|
||||
|
||||
if security.Symbol == self.expectedContract:
|
||||
self.AssertFutureOptionContractOrder(orderEvent, security)
|
||||
|
||||
else:
|
||||
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
|
||||
|
||||
self.Log(f"{orderEvent}");
|
||||
|
||||
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, optionContract: Security):
|
||||
if orderEvent.Direction == OrderDirection.Sell and optionContract.Holdings.Quantity != -1:
|
||||
raise AssertionError(f"No holdings were created for option contract {optionContract.Symbol}")
|
||||
|
||||
if orderEvent.Direction == OrderDirection.Buy and optionContract.Holdings.Quantity != 0:
|
||||
raise AssertionError("Expected no options holdings after closing position")
|
||||
|
||||
if orderEvent.IsAssignment:
|
||||
raise AssertionError(f"Assignment was not expected for {orderEvent.Symbol}")
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self.Portfolio.Invested:
|
||||
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")
|
||||
@@ -46,6 +46,8 @@
|
||||
<ItemGroup>
|
||||
<Content Include="AccumulativeInsightPortfolioRegressionAlgorithm.py" />
|
||||
<Content Include="AddAlphaModelAlgorithm.py" />
|
||||
<Content Include="AddFutureOptionContractDataStreamingRegressionAlgorithm.py" />
|
||||
<Content Include="AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm.py" />
|
||||
<Content Include="AddOptionContractExpiresRegressionAlgorithm.py" />
|
||||
<Content Include="AddOptionContractFromUniverseRegressionAlgorithm.py" />
|
||||
<Content Include="AddRiskManagementAlgorithm.py" />
|
||||
@@ -98,6 +100,15 @@
|
||||
<Content Include="ExtendedMarketTradingRegressionAlgorithm.py" />
|
||||
<Content Include="FilterUniverseRegressionAlgorithm.py" />
|
||||
<Content Include="FineFundamentalFilteredUniverseRegressionAlgorithm.py" />
|
||||
<Content Include="FutureOptionBuySellCallIntradayRegressionAlgorithm.py" />
|
||||
<Content Include="FutureOptionCallITMExpiryRegressionAlgorithm.py" />
|
||||
<Content Include="FutureOptionCallOTMExpiryRegressionAlgorithm.py" />
|
||||
<Content Include="FutureOptionPutITMExpiryRegressionAlgorithm.py" />
|
||||
<Content Include="FutureOptionPutOTMExpiryRegressionAlgorithm.py" />
|
||||
<Content Include="FutureOptionShortCallITMExpiryRegressionAlgorithm.py" />
|
||||
<Content Include="FutureOptionShortCallOTMExpiryRegressionAlgorithm.py" />
|
||||
<Content Include="FutureOptionShortPutITMExpiryRegressionAlgorithm.py" />
|
||||
<Content Include="FutureOptionShortPutOTMExpiryRegressionAlgorithm.py" />
|
||||
<Content Include="KerasNeuralNetworkAlgorithm.py" />
|
||||
<Content Include="CustomDataUsingMapFileRegressionAlgorithm.py" />
|
||||
<Content Include="LiquidETFUniverseFrameworkAlgorithm.py" />
|
||||
|
||||
@@ -33,13 +33,15 @@ Before we enable python support, follow the [installation instructions](https://
|
||||
- Value of the variable: python installation path.
|
||||
4. Install [pandas=0.25.3](https://pandas.pydata.org/) and its [dependencies](https://pandas.pydata.org/pandas-docs/stable/install.html#dependencies).
|
||||
5. Install [wrapt=1.11.2](https://pypi.org/project/wrapt/) module.
|
||||
6. Reboot computer to ensure changes are propogated.
|
||||
6. Install [pyarrow=1.0.1](https://arrow.apache.org/install/) module.
|
||||
7. Reboot computer to ensure changes are propagated.
|
||||
|
||||
#### [macOS](https://github.com/QuantConnect/Lean#macos)
|
||||
|
||||
1. Use the macOS x86-64 package installer from [Anaconda](https://repo.anaconda.com/archive/Anaconda3-5.2.0-MacOSX-x86_64.pkg) and follow "[Installing on macOS](https://docs.anaconda.com/anaconda/install/mac-os)" instructions from Anaconda documentation page.
|
||||
2. Install [pandas=0.25.3](https://pandas.pydata.org/) and its [dependencies](https://pandas.pydata.org/pandas-docs/stable/install.html#dependencies).
|
||||
3. Install [wrapt=1.11.2](https://pypi.org/project/wrapt/) module.
|
||||
4. Install [pyarrow=1.0.1](https://arrow.apache.org/install/) module.
|
||||
|
||||
*Note:* If you encounter the "System.DllNotFoundException: python3.6m" runtime error when running Python algorithms on macOS:
|
||||
1. Find `libpython3.6m.dylib` in your Python installation folder. If you installed Python with Anaconda, it may be found at
|
||||
@@ -64,6 +66,7 @@ conda update -y python conda pip
|
||||
conda install -y cython=0.29.11
|
||||
conda install -y pandas=0.25.3
|
||||
conda install -y wrapt=1.11.2
|
||||
pip install pyarrow==1.0.1
|
||||
```
|
||||
|
||||
*Note 1:* There is a [known issue](https://github.com/pythonnet/pythonnet/issues/609) with python 3.6.5 that prevents pythonnet installation, please upgrade python to version 3.6.8:
|
||||
|
||||
@@ -51,9 +51,7 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
|
||||
public void SetWarmup(TimeSpan timeSpan)
|
||||
{
|
||||
_warmupBarCount = null;
|
||||
_warmupTimeSpan = timeSpan;
|
||||
_warmupResolution = null;
|
||||
SetWarmUp(timeSpan, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,8 +68,13 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
|
||||
/// <param name="resolution">The resolution to request</param>
|
||||
public void SetWarmup(TimeSpan timeSpan, Resolution resolution)
|
||||
public void SetWarmup(TimeSpan timeSpan, Resolution? resolution)
|
||||
{
|
||||
if (_locked)
|
||||
{
|
||||
throw new InvalidOperationException("QCAlgorithm.SetWarmup(): This method cannot be used after algorithm initialized");
|
||||
}
|
||||
|
||||
_warmupBarCount = null;
|
||||
_warmupTimeSpan = timeSpan;
|
||||
_warmupResolution = resolution;
|
||||
@@ -82,7 +85,7 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
|
||||
/// <param name="resolution">The resolution to request</param>
|
||||
public void SetWarmUp(TimeSpan timeSpan, Resolution resolution)
|
||||
public void SetWarmUp(TimeSpan timeSpan, Resolution? resolution)
|
||||
{
|
||||
SetWarmup(timeSpan, resolution);
|
||||
}
|
||||
@@ -96,9 +99,7 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="barCount">The number of data points requested for warm up</param>
|
||||
public void SetWarmup(int barCount)
|
||||
{
|
||||
_warmupTimeSpan = null;
|
||||
_warmupBarCount = barCount;
|
||||
_warmupResolution = null;
|
||||
SetWarmUp(barCount, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,8 +120,13 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
/// <param name="barCount">The number of data points requested for warm up</param>
|
||||
/// <param name="resolution">The resolution to request</param>
|
||||
public void SetWarmup(int barCount, Resolution resolution)
|
||||
public void SetWarmup(int barCount, Resolution? resolution)
|
||||
{
|
||||
if (_locked)
|
||||
{
|
||||
throw new InvalidOperationException("QCAlgorithm.SetWarmup(): This method cannot be used after algorithm initialized");
|
||||
}
|
||||
|
||||
_warmupTimeSpan = null;
|
||||
_warmupBarCount = barCount;
|
||||
_warmupResolution = resolution;
|
||||
@@ -132,7 +138,7 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
/// <param name="barCount">The number of data points requested for warm up</param>
|
||||
/// <param name="resolution">The resolution to request</param>
|
||||
public void SetWarmUp(int barCount, Resolution resolution)
|
||||
public void SetWarmUp(int barCount, Resolution? resolution)
|
||||
{
|
||||
SetWarmup(barCount, resolution);
|
||||
}
|
||||
@@ -492,9 +498,9 @@ namespace QuantConnect.Algorithm
|
||||
var resolution = (Resolution)Math.Max((int)Resolution.Minute, (int)configs.GetHighestResolution());
|
||||
var isExtendedMarketHours = configs.IsExtendedMarketHours();
|
||||
|
||||
// request QuoteBar for Options and Futures
|
||||
// request QuoteBar for Options, Futures, and Futures Options
|
||||
var dataType = typeof(BaseData);
|
||||
if (security.Type == SecurityType.Option || security.Type == SecurityType.Future)
|
||||
if (security.Type == SecurityType.Option || security.Type == SecurityType.Future || security.Type == SecurityType.FutureOption)
|
||||
{
|
||||
dataType = LeanData.GetDataType(resolution, TickType.Quote);
|
||||
}
|
||||
@@ -728,4 +734,4 @@ namespace QuantConnect.Algorithm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,24 @@ namespace QuantConnect.Algorithm
|
||||
return AddDataImpl(dataType, symbol, resolution, timeZone, fillDataForward, leverage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and adds a new Future Option contract to the algorithm.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The <see cref="Future"/> canonical symbol (i.e. Symbol returned from <see cref="AddFuture"/>)</param>
|
||||
/// <param name="optionFilter">Filter to apply to option contracts loaded as part of the universe</param>
|
||||
/// <returns>The new <see cref="Option"/> security, containing a <see cref="Future"/> as its underlying.</returns>
|
||||
/// <exception cref="ArgumentException">The symbol provided is not canonical.</exception>
|
||||
public void AddFutureOption(Symbol futureSymbol, PyObject optionFilter)
|
||||
{
|
||||
Func<OptionFilterUniverse, OptionFilterUniverse> optionFilterUniverse;
|
||||
if (!optionFilter.TryConvertToDelegate(out optionFilterUniverse))
|
||||
{
|
||||
throw new ArgumentException("Option contract universe filter provided is not a function");
|
||||
}
|
||||
|
||||
AddFutureOption(futureSymbol, optionFilterUniverse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the provided final Symbol with/without underlying set to the algorithm.
|
||||
/// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
|
||||
@@ -517,7 +535,7 @@ namespace QuantConnect.Algorithm
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, since above didn't work, just try it as a timespan
|
||||
// Issue #4668 Fix
|
||||
@@ -532,7 +550,7 @@ namespace QuantConnect.Algorithm
|
||||
RegisterIndicator(symbol, indicator, timeSpan, selector);
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch
|
||||
{
|
||||
throw new ArgumentException("Invalid third argument, should be either a valid consolidator or timedelta object");
|
||||
}
|
||||
@@ -1216,4 +1234,4 @@ namespace QuantConnect.Algorithm
|
||||
return pythonIndicator;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,39 +559,43 @@ namespace QuantConnect.Algorithm
|
||||
var orders = new List<OrderTicket>();
|
||||
|
||||
// setting up the tag text for all orders of one strategy
|
||||
var strategyTag = $"{strategy.Name} ({strategyQuantity.ToStringInvariant()})";
|
||||
var tag = $"{strategy.Name} ({strategyQuantity.ToStringInvariant()})";
|
||||
|
||||
// walking through all option legs and issuing orders
|
||||
if (strategy.OptionLegs != null)
|
||||
{
|
||||
var underlying = strategy.Underlying;
|
||||
foreach (var optionLeg in strategy.OptionLegs)
|
||||
{
|
||||
var optionSeq = Securities.Where(kv => kv.Key.Underlying == strategy.Underlying &&
|
||||
kv.Key.ID.OptionRight == optionLeg.Right &&
|
||||
kv.Key.ID.Date == optionLeg.Expiration &&
|
||||
kv.Key.ID.StrikePrice == optionLeg.Strike);
|
||||
// search for both american/european style -- much better than looping through all securities
|
||||
var american = QuantConnect.Symbol.CreateOption(underlying, underlying.ID.Market,
|
||||
OptionStyle.American, optionLeg.Right, optionLeg.Strike, optionLeg.Expiration);
|
||||
|
||||
if (optionSeq.Count() != 1)
|
||||
var european = QuantConnect.Symbol.CreateOption(underlying, underlying.ID.Market,
|
||||
OptionStyle.European, optionLeg.Right, optionLeg.Strike, optionLeg.Expiration);
|
||||
|
||||
Security contract;
|
||||
if (!Securities.TryGetValue(american, out contract) && !Securities.TryGetValue(european, out contract))
|
||||
{
|
||||
throw new InvalidOperationException("Couldn't find the option contract in algorithm securities list. " +
|
||||
Invariant($"Underlying: {strategy.Underlying}, option {optionLeg.Right}, strike {optionLeg.Strike}, ") +
|
||||
Invariant($"expiration: {optionLeg.Expiration}"));
|
||||
Invariant($"expiration: {optionLeg.Expiration}")
|
||||
);
|
||||
}
|
||||
|
||||
var option = optionSeq.First().Key;
|
||||
|
||||
var orderQuantity = optionLeg.Quantity * strategyQuantity;
|
||||
switch (optionLeg.OrderType)
|
||||
{
|
||||
case OrderType.Market:
|
||||
var marketOrder = MarketOrder(option, optionLeg.Quantity * strategyQuantity, tag: strategyTag);
|
||||
orders.Add(marketOrder);
|
||||
orders.Add(MarketOrder(contract.Symbol, orderQuantity, tag: tag));
|
||||
break;
|
||||
|
||||
case OrderType.Limit:
|
||||
var limitOrder = LimitOrder(option, optionLeg.Quantity * strategyQuantity, optionLeg.OrderPrice, tag: strategyTag);
|
||||
orders.Add(limitOrder);
|
||||
orders.Add(LimitOrder(contract.Symbol, orderQuantity, optionLeg.OrderPrice, tag));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Order type is not supported in option strategy: " + optionLeg.OrderType.ToString());
|
||||
throw new InvalidOperationException(Invariant($"Order type is not supported in option strategy: {optionLeg.OrderType}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -603,25 +607,28 @@ namespace QuantConnect.Algorithm
|
||||
{
|
||||
if (!Securities.ContainsKey(strategy.Underlying))
|
||||
{
|
||||
var error = $"Couldn't find the option contract underlying in algorithm securities list. Underlying: {strategy.Underlying}";
|
||||
throw new InvalidOperationException(error);
|
||||
throw new InvalidOperationException(
|
||||
$"Couldn't find the option contract underlying in algorithm securities list. Underlying: {strategy.Underlying}"
|
||||
);
|
||||
}
|
||||
|
||||
var orderQuantity = underlyingLeg.Quantity * strategyQuantity;
|
||||
switch (underlyingLeg.OrderType)
|
||||
{
|
||||
case OrderType.Market:
|
||||
var marketOrder = MarketOrder(strategy.Underlying, underlyingLeg.Quantity * strategyQuantity, tag: strategyTag);
|
||||
orders.Add(marketOrder);
|
||||
orders.Add(MarketOrder(strategy.Underlying, orderQuantity, tag: tag));
|
||||
break;
|
||||
|
||||
case OrderType.Limit:
|
||||
var limitOrder = LimitOrder(strategy.Underlying, underlyingLeg.Quantity * strategyQuantity, underlyingLeg.OrderPrice, tag: strategyTag);
|
||||
orders.Add(limitOrder);
|
||||
orders.Add(LimitOrder(strategy.Underlying, orderQuantity, underlyingLeg.OrderPrice, tag));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Order type is not supported in option strategy: " + underlyingLeg.OrderType.ToString());
|
||||
throw new InvalidOperationException(Invariant($"Order type is not supported in option strategy: {underlyingLeg.OrderType}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return orders;
|
||||
}
|
||||
|
||||
@@ -763,7 +770,7 @@ namespace QuantConnect.Algorithm
|
||||
|
||||
if (request.OrderType == OrderType.OptionExercise)
|
||||
{
|
||||
if (security.Type != SecurityType.Option)
|
||||
if (security.Type != SecurityType.Option && security.Type != SecurityType.FutureOption)
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.NonExercisableSecurity,
|
||||
$"The security with symbol '{request.Symbol}' is not exercisable."
|
||||
|
||||
@@ -21,6 +21,7 @@ using QuantConnect.Data;
|
||||
using QuantConnect.Data.Fundamental;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Future;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Algorithm
|
||||
@@ -451,6 +452,42 @@ namespace QuantConnect.Algorithm
|
||||
return AddUniverse(new UserDefinedUniverse(config, universeSettings, resolution.ToTimeSpan(), selector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new universe that creates options of the security by monitoring any changes in the Universe the provided security is in.
|
||||
/// Additionally, a filter can be applied to the options generated when the universe of the security changes.
|
||||
/// </summary>
|
||||
/// <param name="underlyingSymbol">Underlying Symbol to add as an option. For Futures, the option chain constructed will be per-contract, as long as a canonical Symbol is provided.</param>
|
||||
/// <param name="optionFilter">User-defined filter used to select the options we want out of the option chain provided.</param>
|
||||
/// <exception cref="InvalidOperationException">The underlying Symbol's universe is not found.</exception>
|
||||
public void AddUniverseOptions(Symbol underlyingSymbol, Func<OptionFilterUniverse, OptionFilterUniverse> optionFilter)
|
||||
{
|
||||
// We need to load the universe associated with the provided Symbol and provide that universe to the option filter universe.
|
||||
// The option filter universe will subscribe to any changes in the universe of the underlying Symbol,
|
||||
// ensuring that we load the option chain for every asset found in the underlying's Universe.
|
||||
Universe universe;
|
||||
if (!UniverseManager.TryGetValue(underlyingSymbol, out universe))
|
||||
{
|
||||
// The universe might be already added, but not registered with the UniverseManager.
|
||||
universe = _pendingUniverseAdditions.SingleOrDefault(u => u.Configuration.Symbol == underlyingSymbol);
|
||||
if (universe == null)
|
||||
{
|
||||
underlyingSymbol = AddSecurity(underlyingSymbol).Symbol;
|
||||
}
|
||||
|
||||
// Recheck again, we should have a universe addition pending for the provided Symbol
|
||||
universe = _pendingUniverseAdditions.SingleOrDefault(u => u.Configuration.Symbol == underlyingSymbol);
|
||||
if (universe == null)
|
||||
{
|
||||
// Should never happen, but it could be that the subscription
|
||||
// created with AddSecurity is not aligned with the Symbol we're using.
|
||||
throw new InvalidOperationException($"Universe not found for underlying Symbol: {underlyingSymbol}.");
|
||||
}
|
||||
}
|
||||
|
||||
// Allow all option contracts through without filtering if we're provided a null filter.
|
||||
AddUniverseOptions(universe, optionFilter ?? (_ => _));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new universe selection model and adds it to the algorithm. This universe selection model will chain to the security
|
||||
/// changes of a given <see cref="Universe"/> selection output and create a new <see cref="OptionChainUniverse"/> for each of them
|
||||
|
||||
@@ -1475,13 +1475,13 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="fillDataForward">If true, returns the last available data even if none in that timeslice.</param>
|
||||
/// <param name="leverage">leverage for this security</param>
|
||||
/// <param name="extendedMarketHours">ExtendedMarketHours send in data from 4am - 8pm, not used for FOREX</param>
|
||||
/// <returns></returns>
|
||||
/// <returns>The new Security that was added to the algorithm</returns>
|
||||
public Security AddSecurity(Symbol symbol, Resolution? resolution = null, bool fillDataForward = true, decimal leverage = Security.NullLeverage, bool extendedMarketHours = false)
|
||||
{
|
||||
var isCanonical = symbol.IsCanonical();
|
||||
|
||||
// Short-circuit to AddOptionContract because it will add the underlying if required
|
||||
if (!isCanonical && symbol.SecurityType == SecurityType.Option)
|
||||
if (!isCanonical && (symbol.SecurityType == SecurityType.Option || symbol.SecurityType == SecurityType.FutureOption))
|
||||
{
|
||||
return AddOptionContract(symbol, resolution, fillDataForward, leverage);
|
||||
}
|
||||
@@ -1504,7 +1504,7 @@ namespace QuantConnect.Algorithm
|
||||
if (!UniverseManager.TryGetValue(symbol, out universe) && _pendingUniverseAdditions.All(u => u.Configuration.Symbol != symbol))
|
||||
{
|
||||
var settings = new UniverseSettings(configs.First().Resolution, leverage, true, false, TimeSpan.Zero);
|
||||
if (symbol.SecurityType == SecurityType.Option)
|
||||
if (symbol.SecurityType == SecurityType.Option || symbol.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
universe = new OptionChainUniverse((Option)security, settings, LiveMode);
|
||||
}
|
||||
@@ -1556,13 +1556,53 @@ namespace QuantConnect.Algorithm
|
||||
}
|
||||
}
|
||||
|
||||
var underlyingSymbol = QuantConnect.Symbol.Create(underlying, SecurityType.Equity, market);
|
||||
return AddOption(underlyingSymbol, resolution, market, fillDataForward, leverage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and adds a new <see cref="Option"/> security to the algorithm.
|
||||
/// This method can be used to add options with non-equity asset classes
|
||||
/// to the algorithm (e.g. Future Options).
|
||||
/// </summary>
|
||||
/// <param name="underlying">Underlying asset Symbol to use as the option's underlying</param>
|
||||
/// <param name="resolution">The <see cref="Resolution"/> of market data, Tick, Second, Minute, Hour, or Daily. Default is <see cref="Resolution.Minute"/></param>
|
||||
/// <param name="market">The option's market, <seealso cref="Market"/>. Default value is null, but will be resolved using BrokerageModel.DefaultMarkets in <see cref="AddSecurity{T}"/></param>
|
||||
/// <param name="fillDataForward">If true, data will be provided to the algorithm every Second, Minute, Hour, or Day, while the asset is open and depending on the Resolution this option was configured to use.</param>
|
||||
/// <param name="leverage">The requested leverage for the </param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="KeyNotFoundException"></exception>
|
||||
public Option AddOption(Symbol underlying, Resolution? resolution = null, string market = null, bool fillDataForward = true, decimal leverage = Security.NullLeverage)
|
||||
{
|
||||
var optionType = SecurityType.Option;
|
||||
if (underlying.SecurityType == SecurityType.Future)
|
||||
{
|
||||
optionType = SecurityType.FutureOption;
|
||||
}
|
||||
|
||||
if (market == null)
|
||||
{
|
||||
if (!BrokerageModel.DefaultMarkets.TryGetValue(optionType, out market))
|
||||
{
|
||||
throw new KeyNotFoundException($"No default market set for security type: {optionType}");
|
||||
}
|
||||
}
|
||||
|
||||
Symbol canonicalSymbol;
|
||||
var alias = "?" + underlying;
|
||||
var alias = "?" + underlying.Value;
|
||||
if (!SymbolCache.TryGetSymbol(alias, out canonicalSymbol) ||
|
||||
canonicalSymbol.ID.Market != market ||
|
||||
canonicalSymbol.SecurityType != SecurityType.Option)
|
||||
(canonicalSymbol.SecurityType != SecurityType.Option &&
|
||||
canonicalSymbol.SecurityType != SecurityType.FutureOption))
|
||||
{
|
||||
canonicalSymbol = QuantConnect.Symbol.Create(underlying, SecurityType.Option, market, alias);
|
||||
canonicalSymbol = QuantConnect.Symbol.CreateOption(
|
||||
underlying,
|
||||
underlying.ID.Market,
|
||||
default(OptionStyle),
|
||||
default(OptionRight),
|
||||
0,
|
||||
SecurityIdentifier.DefaultDate,
|
||||
alias);
|
||||
}
|
||||
|
||||
return (Option)AddSecurity(canonicalSymbol, resolution, fillDataForward, leverage);
|
||||
@@ -1613,6 +1653,42 @@ namespace QuantConnect.Algorithm
|
||||
return (Future)AddSecurity(symbol, resolution, fillDataForward, leverage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and adds a new Future Option contract to the algorithm.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The <see cref="Future"/> canonical symbol (i.e. Symbol returned from <see cref="AddFuture"/>)</param>
|
||||
/// <param name="optionFilter">Filter to apply to option contracts loaded as part of the universe</param>
|
||||
/// <returns>The new <see cref="Option"/> security, containing a <see cref="Future"/> as its underlying.</returns>
|
||||
/// <exception cref="ArgumentException">The symbol provided is not canonical.</exception>
|
||||
public void AddFutureOption(Symbol symbol, Func<OptionFilterUniverse, OptionFilterUniverse> optionFilter = null)
|
||||
{
|
||||
if (!symbol.IsCanonical())
|
||||
{
|
||||
throw new ArgumentException("Symbol provided must be canonical (i.e. the Symbol returned from AddFuture(), not AddFutureContract().");
|
||||
}
|
||||
|
||||
AddUniverseOptions(symbol, optionFilter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a future option contract to the algorithm.
|
||||
/// </summary>
|
||||
/// <param name="symbol">Option contract Symbol</param>
|
||||
/// <param name="resolution">Resolution of the option contract, i.e. the granularity of the data</param>
|
||||
/// <param name="fillDataForward">If true, this will fill in missing data points with the previous data point</param>
|
||||
/// <param name="leverage">The leverage to apply to the option contract</param>
|
||||
/// <returns>Option security</returns>
|
||||
/// <exception cref="ArgumentException">Symbol is canonical (i.e. a generic Symbol returned from <see cref="AddFuture"/> or <see cref="AddOption"/>)</exception>
|
||||
public Option AddFutureOptionContract(Symbol symbol, Resolution? resolution = null, bool fillDataForward = true, decimal leverage = Security.NullLeverage)
|
||||
{
|
||||
if (symbol.IsCanonical())
|
||||
{
|
||||
throw new ArgumentException("Expected non-canonical Symbol (i.e. a Symbol representing a specific Future contract");
|
||||
}
|
||||
|
||||
return AddOptionContract(symbol, resolution, fillDataForward, leverage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and adds a new single <see cref="Option"/> contract to the algorithm
|
||||
/// </summary>
|
||||
@@ -1625,14 +1701,13 @@ namespace QuantConnect.Algorithm
|
||||
{
|
||||
var configs = SubscriptionManager.SubscriptionDataConfigService.Add(symbol, resolution, fillDataForward, dataNormalizationMode:DataNormalizationMode.Raw);
|
||||
var option = (Option)Securities.CreateSecurity(symbol, configs, leverage);
|
||||
|
||||
// add underlying if not present
|
||||
var underlying = option.Symbol.Underlying;
|
||||
Security equity;
|
||||
Security underlyingSecurity;
|
||||
List<SubscriptionDataConfig> underlyingConfigs;
|
||||
if (!Securities.TryGetValue(underlying, out equity))
|
||||
if (!Securities.TryGetValue(underlying, out underlyingSecurity))
|
||||
{
|
||||
equity = AddEquity(underlying.Value, resolution, underlying.ID.Market, false);
|
||||
underlyingSecurity = AddSecurity(underlying, resolution, fillDataForward, leverage);
|
||||
underlyingConfigs = SubscriptionManager.SubscriptionDataConfigService
|
||||
.GetSubscriptionDataConfigs(underlying);
|
||||
}
|
||||
@@ -1652,11 +1727,12 @@ namespace QuantConnect.Algorithm
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
underlyingConfigs.SetDataNormalizationMode(DataNormalizationMode.Raw);
|
||||
// For backward compatibility we need to refresh the security DataNormalizationMode Property
|
||||
equity.RefreshDataNormalizationModeProperty();
|
||||
underlyingSecurity.RefreshDataNormalizationModeProperty();
|
||||
|
||||
option.Underlying = equity;
|
||||
option.Underlying = underlyingSecurity;
|
||||
Securities.Add(option);
|
||||
|
||||
// get or create the universe
|
||||
|
||||
@@ -20,6 +20,7 @@ using QuantConnect.Securities;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Securities.Future;
|
||||
|
||||
namespace QuantConnect.Algorithm.Selection
|
||||
{
|
||||
@@ -59,8 +60,17 @@ namespace QuantConnect.Algorithm.Selection
|
||||
// the universe we were watching changed, this will trigger a call to CreateUniverses
|
||||
_nextRefreshTimeUtc = DateTime.MinValue;
|
||||
|
||||
// We must create the new option Symbol using the CreateOption(Symbol, ...) overload.
|
||||
// Otherwise, we'll end up loading equity data for the selected Symbol, which won't
|
||||
// work whenever we're loading options data for any non-equity underlying asset class.
|
||||
_currentSymbols = ((Universe.SelectionEventArgs)args).CurrentSelection
|
||||
.Select(symbol => Symbol.Create(symbol.Value, SecurityType.Option, symbol.ID.Market, $"?{symbol.Value}"))
|
||||
.Select(symbol => Symbol.CreateOption(
|
||||
symbol,
|
||||
symbol.ID.Market,
|
||||
default(OptionStyle),
|
||||
default(OptionRight),
|
||||
0m,
|
||||
SecurityIdentifier.DefaultDate))
|
||||
.ToList();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace QuantConnect.Algorithm.Selection
|
||||
_symbols.Remove(removedSymbol);
|
||||
|
||||
// the option has been removed! This can happen when the user manually removed the option contract we remove the underlying
|
||||
if (removedSymbol.SecurityType == SecurityType.Option)
|
||||
if (removedSymbol.SecurityType == SecurityType.Option || removedSymbol.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
Remove(removedSymbol.Underlying);
|
||||
}
|
||||
|
||||
22
Api/Api.cs
22
Api/Api.cs
@@ -16,7 +16,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -997,5 +996,26 @@ namespace QuantConnect.Api
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will read the organization account status
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The target organization id, if null will return default organization</param>
|
||||
public Account ReadAccount(string organizationId = null)
|
||||
{
|
||||
var request = new RestRequest("account/read/", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
if (organizationId != null)
|
||||
{
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new { organizationId }), ParameterType.RequestBody);
|
||||
}
|
||||
|
||||
Account account;
|
||||
ApiConnection.TryRequest(request, out account);
|
||||
return account;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ namespace QuantConnect.Brokerages.Backtesting
|
||||
algorithm.UtcTime - _lastUpdate > _securitiesRescanPeriod)
|
||||
{
|
||||
var expirations = algorithm.Securities.Select(x => x.Key)
|
||||
.Where(x => x.ID.SecurityType == SecurityType.Option &&
|
||||
.Where(x => (x.ID.SecurityType == SecurityType.Option || x.ID.SecurityType == SecurityType.FutureOption) &&
|
||||
x.ID.Date > algorithm.Time &&
|
||||
x.ID.Date - algorithm.Time <= _securitiesRescanPeriod)
|
||||
.Select(x => x.ID.Date)
|
||||
@@ -136,7 +136,7 @@ namespace QuantConnect.Brokerages.Backtesting
|
||||
|
||||
algorithm.Securities
|
||||
// we take only options that expire soon
|
||||
.Where(x => x.Key.ID.SecurityType == SecurityType.Option &&
|
||||
.Where(x => (x.Key.ID.SecurityType == SecurityType.Option || x.Key.ID.SecurityType == SecurityType.FutureOption) &&
|
||||
x.Key.ID.Date - algorithm.UtcTime <= _priorExpiration)
|
||||
// we look into short positions only (short for user means long for us)
|
||||
.Where(x => x.Value.Holdings.IsShort)
|
||||
|
||||
@@ -37,6 +37,8 @@ using NodaTime;
|
||||
using QuantConnect.IBAutomater;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Orders.TimeInForces;
|
||||
using QuantConnect.Securities.Future;
|
||||
using QuantConnect.Securities.FutureOption;
|
||||
using QuantConnect.Securities.Option;
|
||||
using Bar = QuantConnect.Data.Market.Bar;
|
||||
using HistoryRequest = QuantConnect.Data.HistoryRequest;
|
||||
@@ -939,7 +941,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
/// <param name="exchange">The exchange to send the order to, defaults to "Smart" to use IB's smart routing</param>
|
||||
private void IBPlaceOrder(Order order, bool needsNewId, string exchange = null)
|
||||
{
|
||||
// MOO/MOC require directed option orders
|
||||
// MOO/MOC require directed option orders.
|
||||
// We resolve non-equity markets in the `CreateContract` method.
|
||||
if (exchange == null &&
|
||||
order.Symbol.SecurityType == SecurityType.Option &&
|
||||
(order.Type == OrderType.MarketOnOpen || order.Type == OrderType.MarketOnClose))
|
||||
@@ -1018,6 +1021,15 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
return details.Contract.TradingClass;
|
||||
}
|
||||
|
||||
if (symbol.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
// Futures options trading class is the same as the FOP ticker.
|
||||
// This is required in order to resolve the contract details successfully.
|
||||
// We let this method complete even though we assign twice so that the
|
||||
// contract details are added to the cache and won't require another lookup.
|
||||
contract.TradingClass = symbol.ID.Symbol;
|
||||
}
|
||||
|
||||
details = GetContractDetails(contract, symbol);
|
||||
if (details == null)
|
||||
{
|
||||
@@ -1824,12 +1836,13 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
/// <returns>A new IB contract for the order</returns>
|
||||
private Contract CreateContract(Symbol symbol, bool includeExpired, string exchange = null)
|
||||
{
|
||||
var securityType = ConvertSecurityType(symbol.ID.SecurityType);
|
||||
var securityType = ConvertSecurityType(symbol.SecurityType);
|
||||
var ibSymbol = _symbolMapper.GetBrokerageSymbol(symbol);
|
||||
|
||||
var contract = new Contract
|
||||
{
|
||||
Symbol = ibSymbol,
|
||||
Exchange = exchange ?? "Smart",
|
||||
Exchange = exchange ?? GetSymbolExchange(symbol),
|
||||
SecType = securityType,
|
||||
Currency = Currencies.USD
|
||||
};
|
||||
@@ -1846,18 +1859,23 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
contract.PrimaryExch = GetPrimaryExchange(contract, symbol);
|
||||
}
|
||||
|
||||
if (symbol.ID.SecurityType == SecurityType.Option)
|
||||
if (symbol.ID.SecurityType == SecurityType.Option || symbol.ID.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
contract.LastTradeDateOrContractMonth = symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter);
|
||||
contract.Right = symbol.ID.OptionRight == OptionRight.Call ? IB.RightType.Call : IB.RightType.Put;
|
||||
contract.Strike = Convert.ToDouble(symbol.ID.StrikePrice);
|
||||
contract.Symbol = ibSymbol;
|
||||
contract.Multiplier = _securityProvider.GetSecurity(symbol)?.SymbolProperties.ContractMultiplier.ToString(CultureInfo.InvariantCulture) ?? "100";
|
||||
contract.TradingClass = GetTradingClass(contract, symbol);
|
||||
contract.Multiplier = _symbolPropertiesDatabase.GetSymbolProperties(
|
||||
symbol.ID.Market,
|
||||
symbol,
|
||||
symbol.SecurityType,
|
||||
_algorithm.Portfolio.CashBook.AccountCurrency)
|
||||
.ContractMultiplier
|
||||
.ToStringInvariant();
|
||||
|
||||
contract.TradingClass = GetTradingClass(contract, symbol);
|
||||
contract.IncludeExpired = includeExpired;
|
||||
}
|
||||
|
||||
if (symbol.ID.SecurityType == SecurityType.Future)
|
||||
{
|
||||
// we convert Market.* markets into IB exchanges if we have them in our map
|
||||
@@ -1871,8 +1889,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
var symbolProperties = _symbolPropertiesDatabase.GetSymbolProperties(
|
||||
symbol.ID.Market,
|
||||
symbol.ID.Symbol,
|
||||
SecurityType.Future,
|
||||
symbol,
|
||||
symbol.SecurityType,
|
||||
Currencies.USD);
|
||||
|
||||
contract.Multiplier = Convert.ToInt32(symbolProperties.ContractMultiplier).ToStringInvariant();
|
||||
@@ -2087,7 +2105,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps SecurityType enum
|
||||
/// Maps SecurityType enum to an IBApi SecurityType value
|
||||
/// </summary>
|
||||
private static string ConvertSecurityType(SecurityType type)
|
||||
{
|
||||
@@ -2097,7 +2115,10 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
return IB.SecurityType.Stock;
|
||||
|
||||
case SecurityType.Option:
|
||||
return IB.SecurityType.Option;
|
||||
return IB.SecurityType.Option;
|
||||
|
||||
case SecurityType.FutureOption:
|
||||
return IB.SecurityType.FutureOption;
|
||||
|
||||
case SecurityType.Forex:
|
||||
return IB.SecurityType.Cash;
|
||||
@@ -2123,6 +2144,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
case IB.SecurityType.Option:
|
||||
return SecurityType.Option;
|
||||
|
||||
case IB.SecurityType.FutureOption:
|
||||
return SecurityType.FutureOption;
|
||||
|
||||
case IB.SecurityType.Cash:
|
||||
return SecurityType.Forex;
|
||||
|
||||
@@ -2224,21 +2248,38 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
var ibSymbol = securityType == SecurityType.Forex ? contract.Symbol + contract.Currency : contract.Symbol;
|
||||
|
||||
var market = InteractiveBrokersBrokerageModel.DefaultMarketMap[securityType];
|
||||
var isFutureOption = contract.SecType == IB.SecurityType.FutureOption;
|
||||
|
||||
if (securityType == SecurityType.Future)
|
||||
// Handle future options as a Future, up until we actually return the future.
|
||||
if (isFutureOption || securityType == SecurityType.Future)
|
||||
{
|
||||
var leanSymbol = _symbolMapper.GetLeanRootSymbol(ibSymbol);
|
||||
var defaultMarket = market;
|
||||
if (!_symbolPropertiesDatabase.TryGetMarket(leanSymbol, securityType, out market))
|
||||
if (!_symbolPropertiesDatabase.TryGetMarket(leanSymbol, SecurityType.Future, out market))
|
||||
{
|
||||
market = defaultMarket;
|
||||
}
|
||||
|
||||
var contractDate = DateTime.ParseExact(contract.LastTradeDateOrContractMonth, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
|
||||
var contractExpiryDate = DateTime.ParseExact(contract.LastTradeDateOrContractMonth, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
|
||||
|
||||
return _symbolMapper.GetLeanSymbol(ibSymbol, securityType, market, contractDate);
|
||||
if (!isFutureOption)
|
||||
{
|
||||
return _symbolMapper.GetLeanSymbol(ibSymbol, SecurityType.Future, market, contractExpiryDate);
|
||||
}
|
||||
|
||||
// Create a canonical future Symbol for lookup in the FuturesExpiryFunctions helper class.
|
||||
// We then get the delta between the futures option's expiry month vs. the future's expiry month.
|
||||
var canonicalFutureSymbol = _symbolMapper.GetLeanSymbol(ibSymbol, SecurityType.Future, market, SecurityIdentifier.DefaultDate);
|
||||
var futureExpiryFunction = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureSymbol);
|
||||
var futureContractExpiryDate = futureExpiryFunction(FuturesOptionsExpiryFunctions.GetFutureContractMonth(canonicalFutureSymbol, contractExpiryDate));
|
||||
var futureSymbol = Symbol.CreateFuture(canonicalFutureSymbol.ID.Symbol, canonicalFutureSymbol.ID.Market, futureContractExpiryDate);
|
||||
|
||||
var right = contract.Right == IB.RightType.Call ? OptionRight.Call : OptionRight.Put;
|
||||
var strike = Convert.ToDecimal(contract.Strike);
|
||||
|
||||
return Symbol.CreateOption(futureSymbol, market, OptionStyle.American, right, strike, contractExpiryDate);
|
||||
}
|
||||
else if (securityType == SecurityType.Option)
|
||||
if (securityType == SecurityType.Option)
|
||||
{
|
||||
var expiryDate = DateTime.ParseExact(contract.LastTradeDateOrContractMonth, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
|
||||
var right = contract.Right == IB.RightType.Call ? OptionRight.Call : OptionRight.Put;
|
||||
@@ -2324,7 +2365,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
var subscribeSymbol = symbol;
|
||||
|
||||
// we subscribe to the underlying
|
||||
if (symbol.ID.SecurityType == SecurityType.Option && symbol.IsCanonical())
|
||||
if ((symbol.ID.SecurityType == SecurityType.Option || symbol.ID.SecurityType == SecurityType.FutureOption) && symbol.IsCanonical())
|
||||
{
|
||||
subscribeSymbol = symbol.Underlying;
|
||||
_underlyings.Add(subscribeSymbol, symbol);
|
||||
@@ -2398,7 +2439,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
{
|
||||
Log.Trace("InteractiveBrokersBrokerage.Unsubscribe(): Unsubscribe Request: " + symbol.Value);
|
||||
|
||||
if (symbol.ID.SecurityType == SecurityType.Option && symbol.ID.StrikePrice == 0.0m)
|
||||
if ((symbol.ID.SecurityType == SecurityType.Option || symbol.ID.SecurityType == SecurityType.FutureOption) && symbol.ID.StrikePrice == 0.0m)
|
||||
{
|
||||
_underlyings.Remove(symbol.Underlying);
|
||||
}
|
||||
@@ -2450,10 +2491,13 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
if (symbol.Value.IndexOfInvariant("universe", true) != -1) return false;
|
||||
|
||||
// Include future options as a special case with no matching market, otherwise
|
||||
// our subscriptions are removed without any sort of notice.
|
||||
return
|
||||
(securityType == SecurityType.Equity && market == Market.USA) ||
|
||||
(securityType == SecurityType.Forex && market == Market.Oanda) ||
|
||||
(securityType == SecurityType.Option && market == Market.USA) ||
|
||||
(securityType == SecurityType.FutureOption) ||
|
||||
(securityType == SecurityType.Future);
|
||||
}
|
||||
|
||||
@@ -2677,7 +2721,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
case IBApi.TickType.OPTION_CALL_OPEN_INTEREST:
|
||||
case IBApi.TickType.OPTION_PUT_OPEN_INTEREST:
|
||||
|
||||
if (symbol.ID.SecurityType != SecurityType.Option && symbol.ID.SecurityType != SecurityType.Future)
|
||||
if (symbol.ID.SecurityType != SecurityType.Option && symbol.ID.SecurityType != SecurityType.FutureOption && symbol.ID.SecurityType != SecurityType.Future)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -2717,18 +2761,32 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
/// <summary>
|
||||
/// Method returns a collection of Symbols that are available at the broker.
|
||||
/// </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="symbol">Symbol to search future/option chain for</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></returns>
|
||||
public IEnumerable<Symbol> LookupSymbols(string lookupName, SecurityType securityType, bool includeExpired, string securityCurrency = null, string securityExchange = null)
|
||||
/// <returns>Future/Option chain associated with the Symbol provided</returns>
|
||||
public IEnumerable<Symbol> LookupSymbols(Symbol symbol, bool includeExpired, string securityCurrency = null)
|
||||
{
|
||||
// setting up exchange defaults and filters
|
||||
var exchangeSpecifier = securityType == SecurityType.Future ? securityExchange ?? "" : securityExchange ?? "Smart";
|
||||
var exchangeSpecifier = GetSymbolExchange(symbol);
|
||||
var futuresExchanges = _futuresExchanges.Values.Reverse().ToArray();
|
||||
Func<string, int> exchangeFilter = exchange => securityType == SecurityType.Future ? Array.IndexOf(futuresExchanges, exchange) : 0;
|
||||
Func<string, int> exchangeFilter = exchange => symbol.SecurityType == SecurityType.Future ? Array.IndexOf(futuresExchanges, exchange) : 0;
|
||||
|
||||
var lookupName = symbol.Value;
|
||||
|
||||
if (symbol.SecurityType == SecurityType.Future)
|
||||
{
|
||||
lookupName = symbol.ID.Symbol;
|
||||
}
|
||||
else if (symbol.SecurityType == SecurityType.Option)
|
||||
{
|
||||
lookupName = symbol.Underlying.Value;
|
||||
}
|
||||
else if (symbol.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
// Futures Options use the underlying Symbol ticker for their ticker on IB.
|
||||
lookupName = symbol.Underlying.ID.Symbol;
|
||||
}
|
||||
|
||||
// setting up lookup request
|
||||
var contract = new Contract
|
||||
@@ -2736,7 +2794,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
Symbol = _symbolMapper.GetBrokerageRootSymbol(lookupName),
|
||||
Currency = securityCurrency ?? Currencies.USD,
|
||||
Exchange = exchangeSpecifier,
|
||||
SecType = ConvertSecurityType(securityType),
|
||||
SecType = ConvertSecurityType(symbol.SecurityType),
|
||||
IncludeExpired = includeExpired
|
||||
};
|
||||
|
||||
@@ -2744,22 +2802,22 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
var symbols = new List<Symbol>();
|
||||
|
||||
if (securityType == SecurityType.Option)
|
||||
if (symbol.SecurityType == SecurityType.Option || symbol.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
// IB requests for full option chains are rate limited and responses can be delayed up to a minute for each underlying,
|
||||
// so we fetch them from the OCC website instead of using the IB API.
|
||||
var underlyingSymbol = Symbol.Create(contract.Symbol, SecurityType.Equity, Market.USA);
|
||||
symbols.AddRange(_algorithm.OptionChainProvider.GetOptionContractList(underlyingSymbol, DateTime.Today));
|
||||
// For futures options, we fetch the option chain from CME.
|
||||
symbols.AddRange(_algorithm.OptionChainProvider.GetOptionContractList(symbol.Underlying, DateTime.Today));
|
||||
}
|
||||
else if (securityType == SecurityType.Future)
|
||||
else if (symbol.SecurityType == SecurityType.Future)
|
||||
{
|
||||
string market;
|
||||
if (_symbolPropertiesDatabase.TryGetMarket(lookupName, securityType, out market))
|
||||
if (_symbolPropertiesDatabase.TryGetMarket(lookupName, symbol.SecurityType, out market))
|
||||
{
|
||||
var symbolProperties = _symbolPropertiesDatabase.GetSymbolProperties(
|
||||
market,
|
||||
lookupName,
|
||||
securityType,
|
||||
symbol,
|
||||
symbol.SecurityType,
|
||||
Currencies.USD);
|
||||
|
||||
contract.Multiplier = Convert.ToInt32(symbolProperties.ContractMultiplier).ToStringInvariant();
|
||||
@@ -2785,7 +2843,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
// Try to remove options or futures contracts that have expired
|
||||
if (!includeExpired)
|
||||
{
|
||||
if (securityType == SecurityType.Option || securityType == SecurityType.Future)
|
||||
if (symbol.SecurityType == SecurityType.Option ||
|
||||
symbol.SecurityType == SecurityType.Future ||
|
||||
symbol.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
var removedSymbols = symbols.Where(x => x.ID.Date < GetRealTimeTickTime(x).Date).ToHashSet();
|
||||
|
||||
@@ -2828,6 +2888,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
// skipping universe and canonical symbols
|
||||
if (!CanSubscribe(request.Symbol) ||
|
||||
(request.Symbol.ID.SecurityType == SecurityType.Option && request.Symbol.IsCanonical()) ||
|
||||
(request.Symbol.ID.SecurityType == SecurityType.FutureOption && request.Symbol.IsCanonical()) ||
|
||||
(request.Symbol.ID.SecurityType == SecurityType.Future && request.Symbol.IsCanonical()))
|
||||
{
|
||||
yield break;
|
||||
@@ -2838,6 +2899,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
request.Symbol.SecurityType != SecurityType.Forex &&
|
||||
request.Symbol.SecurityType != SecurityType.Cfd &&
|
||||
request.Symbol.SecurityType != SecurityType.Future &&
|
||||
request.Symbol.SecurityType != SecurityType.FutureOption &&
|
||||
request.Symbol.SecurityType != SecurityType.Option)
|
||||
{
|
||||
yield break;
|
||||
@@ -3022,6 +3084,30 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
return history;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exchange the Symbol should be routed to
|
||||
/// </summary>
|
||||
/// <param name="symbol">Symbol to route</param>
|
||||
private string GetSymbolExchange(Symbol symbol)
|
||||
{
|
||||
switch (symbol.SecurityType)
|
||||
{
|
||||
case SecurityType.Option:
|
||||
// Regular equity options uses default, in this case "Smart"
|
||||
goto default;
|
||||
|
||||
// Futures options share the same market as the underlying Symbol
|
||||
case SecurityType.FutureOption:
|
||||
case SecurityType.Future:
|
||||
return _futuresExchanges.ContainsKey(symbol.ID.Market)
|
||||
? _futuresExchanges[symbol.ID.Market]
|
||||
: symbol.ID.Market;
|
||||
|
||||
default:
|
||||
return "Smart";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the brokerage should perform the cash synchronization
|
||||
/// </summary>
|
||||
@@ -3143,4 +3229,5 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
105, 106, 107, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 129, 131, 132, 133, 134, 135, 136, 137, 140, 141, 146, 147, 148, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 163, 167, 168, 201, 313,314,315,325,328,329,334,335,336,337,338,339,340,341,342,343,345,347,348,349,350,352,353,355,356,358,359,360,361,362,363,364,367,368,369,370,371,372,373,374,375,376,377,378,379,380,382,383,387,388,389,390,391,392,393,394,395,396,397,398,400,401,402,403,405,406,407,408,409,410,411,412,413,417,418,419,421,423,424,427,428,429,433,434,435,436,437,439,440,441,442,443,444,445,446,447,448,449,10002,10006,10007,10008,10009,10010,10011,10012,10014,10020,2102
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using QuantConnect.Securities.Future;
|
||||
using QuantConnect.Securities.FutureOption;
|
||||
|
||||
namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
{
|
||||
@@ -26,7 +28,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
/// </summary>
|
||||
public class InteractiveBrokersSymbolMapper : ISymbolMapper
|
||||
{
|
||||
// we have a special treatment of futures, because IB renamed several exchange tickers (like GBP instead of 6B). We fix this:
|
||||
// we have a special treatment of futures, because IB renamed several exchange tickers (like GBP instead of 6B). We fix this:
|
||||
// We map those tickers back to their original names using the map below
|
||||
private readonly Dictionary<string, string> _ibNameMap = new Dictionary<string, string>();
|
||||
|
||||
@@ -71,6 +73,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
if (symbol.ID.SecurityType != SecurityType.Forex &&
|
||||
symbol.ID.SecurityType != SecurityType.Equity &&
|
||||
symbol.ID.SecurityType != SecurityType.Option &&
|
||||
symbol.ID.SecurityType != SecurityType.FutureOption &&
|
||||
symbol.ID.SecurityType != SecurityType.Future)
|
||||
throw new ArgumentException("Invalid security type: " + symbol.ID.SecurityType);
|
||||
|
||||
@@ -80,8 +83,16 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
switch (symbol.ID.SecurityType)
|
||||
{
|
||||
case SecurityType.Option:
|
||||
// Final case is for equities. We use the mapped value to select
|
||||
// the equity we want to trade.
|
||||
return symbol.Underlying.Value;
|
||||
|
||||
case SecurityType.FutureOption:
|
||||
// We use the underlying Future Symbol since IB doesn't use
|
||||
// the Futures Options' ticker, but rather uses the underlying's
|
||||
// Symbol, mapped to the brokerage.
|
||||
return GetBrokerageSymbol(symbol.Underlying);
|
||||
|
||||
case SecurityType.Future:
|
||||
return GetBrokerageRootSymbol(symbol.ID.Symbol);
|
||||
|
||||
@@ -110,7 +121,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
if (securityType != SecurityType.Forex &&
|
||||
securityType != SecurityType.Equity &&
|
||||
securityType != SecurityType.Option &&
|
||||
securityType != SecurityType.Future)
|
||||
securityType != SecurityType.Future &&
|
||||
securityType != SecurityType.FutureOption)
|
||||
throw new ArgumentException("Invalid security type: " + securityType);
|
||||
|
||||
try
|
||||
@@ -123,6 +135,22 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
case SecurityType.Option:
|
||||
return Symbol.CreateOption(brokerageSymbol, market, OptionStyle.American, optionRight, strike, expirationDate);
|
||||
|
||||
case SecurityType.FutureOption:
|
||||
var canonicalFutureSymbol = Symbol.Create(GetLeanRootSymbol(brokerageSymbol), SecurityType.Future, market);
|
||||
var futureContractMonth = FuturesOptionsExpiryFunctions.GetFutureContractMonth(canonicalFutureSymbol, expirationDate);
|
||||
var futureExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureSymbol)(futureContractMonth);
|
||||
|
||||
return Symbol.CreateOption(
|
||||
Symbol.CreateFuture(
|
||||
brokerageSymbol,
|
||||
market,
|
||||
futureExpiry),
|
||||
market,
|
||||
OptionStyle.American,
|
||||
optionRight,
|
||||
strike,
|
||||
expirationDate);
|
||||
|
||||
case SecurityType.Equity:
|
||||
brokerageSymbol = brokerageSymbol.Replace(" ", ".");
|
||||
break;
|
||||
@@ -136,7 +164,6 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// IB specific versions of the symbol mapping (GetBrokerageRootSymbol) for future root symbols
|
||||
/// </summary>
|
||||
@@ -160,4 +187,4 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
<HintPath>Fxcm\QuantConnect.Fxcm.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="QuantConnect.IBAutomater, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\QuantConnect.IBAutomater.1.0.34\lib\net45\QuantConnect.IBAutomater.exe</HintPath>
|
||||
<HintPath>..\packages\QuantConnect.IBAutomater.1.0.35\lib\net45\QuantConnect.IBAutomater.exe</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="RestSharp, Version=106.6.10.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\RestSharp.106.6.10\lib\net452\RestSharp.dll</HintPath>
|
||||
@@ -717,9 +717,9 @@
|
||||
<Error Condition="!Exists('..\packages\Microsoft.NetCore.Analyzers.2.9.3\build\Microsoft.NetCore.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetCore.Analyzers.2.9.3\build\Microsoft.NetCore.Analyzers.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.NetFramework.Analyzers.2.9.3\build\Microsoft.NetFramework.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetFramework.Analyzers.2.9.3\build\Microsoft.NetFramework.Analyzers.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.3\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.3\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props'))" />
|
||||
<Error Condition="!Exists('..\packages\QuantConnect.IBAutomater.1.0.34\build\QuantConnect.IBAutomater.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\QuantConnect.IBAutomater.1.0.34\build\QuantConnect.IBAutomater.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\QuantConnect.IBAutomater.1.0.35\build\QuantConnect.IBAutomater.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\QuantConnect.IBAutomater.1.0.35\build\QuantConnect.IBAutomater.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\QuantConnect.IBAutomater.1.0.34\build\QuantConnect.IBAutomater.targets" Condition="Exists('..\packages\QuantConnect.IBAutomater.1.0.34\build\QuantConnect.IBAutomater.targets')" />
|
||||
<Import Project="..\packages\QuantConnect.IBAutomater.1.0.35\build\QuantConnect.IBAutomater.targets" Condition="Exists('..\packages\QuantConnect.IBAutomater.1.0.35\build\QuantConnect.IBAutomater.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<package id="NATS.Client" version="0.8.1" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net452" />
|
||||
<package id="NodaTime" version="1.3.4" targetFramework="net452" />
|
||||
<package id="QuantConnect.IBAutomater" version="1.0.34" targetFramework="net462" />
|
||||
<package id="QuantConnect.IBAutomater" version="1.0.35" targetFramework="net462" />
|
||||
<package id="RestSharp" version="106.6.10" targetFramework="net452" />
|
||||
<package id="System.Net.Http" version="4.3.4" targetFramework="net462" />
|
||||
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />
|
||||
|
||||
68
Common/Api/Account.cs
Normal file
68
Common/Api/Account.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace QuantConnect.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Account information for an organization
|
||||
/// </summary>
|
||||
public class Account : RestResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The organization Id
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "organizationId")]
|
||||
public string OrganizationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current account balance
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "creditBalance")]
|
||||
public decimal CreditBalance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current organizations credit card
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "card")]
|
||||
public Card Card { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Credit card
|
||||
/// </summary>
|
||||
public class Card
|
||||
{
|
||||
/// <summary>
|
||||
/// Credit card brand
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "brand")]
|
||||
public string Brand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The credit card expiration
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "expiration")]
|
||||
public DateTime Expiration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The last 4 digits of the card
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "last4")]
|
||||
public decimal LastFourDigits { get; set; }
|
||||
}
|
||||
}
|
||||
199
Common/BinaryComparison.cs
Normal file
199
Common/BinaryComparison.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Linq.Expressions;
|
||||
using static QuantConnect.Util.ExpressionBuilder;
|
||||
|
||||
namespace QuantConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration class defining binary comparisons and providing access to expressions and functions
|
||||
/// capable of evaluating a particular comparison for any type. If a particular type does not implement
|
||||
/// a binary comparison than an exception will be thrown.
|
||||
/// </summary>
|
||||
public class BinaryComparison
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.Equal"/>
|
||||
/// </summary>
|
||||
public static readonly BinaryComparison Equal = new BinaryComparison(ExpressionType.Equal);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.NotEqual"/>
|
||||
/// </summary>
|
||||
public static readonly BinaryComparison NotEqual = new BinaryComparison(ExpressionType.NotEqual);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.LessThan"/>
|
||||
/// </summary>
|
||||
public static readonly BinaryComparison LessThan = new BinaryComparison(ExpressionType.LessThan);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.GreaterThan"/>
|
||||
/// </summary>
|
||||
public static readonly BinaryComparison GreaterThan = new BinaryComparison(ExpressionType.GreaterThan);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.LessThanOrEqual"/>
|
||||
/// </summary>
|
||||
public static readonly BinaryComparison LessThanOrEqual = new BinaryComparison(ExpressionType.LessThanOrEqual);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.GreaterThanOrEqual"/>
|
||||
/// </summary>
|
||||
public static readonly BinaryComparison GreaterThanOrEqual = new BinaryComparison(ExpressionType.GreaterThanOrEqual);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BinaryComparison"/> matching the provided <paramref name="type"/>
|
||||
/// </summary>
|
||||
public static BinaryComparison FromExpressionType(ExpressionType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ExpressionType.Equal: return Equal;
|
||||
case ExpressionType.NotEqual: return NotEqual;
|
||||
case ExpressionType.LessThan: return LessThan;
|
||||
case ExpressionType.LessThanOrEqual: return LessThanOrEqual;
|
||||
case ExpressionType.GreaterThan: return GreaterThan;
|
||||
case ExpressionType.GreaterThanOrEqual: return GreaterThanOrEqual;
|
||||
default:
|
||||
throw new InvalidOperationException($"The specified ExpressionType '{type}' is not a binary comparison.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expression type defining the binary comparison.
|
||||
/// </summary>
|
||||
public ExpressionType Type { get; }
|
||||
|
||||
private BinaryComparison(ExpressionType type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the specified <paramref name="left"/> and <paramref name="right"/> according to this <see cref="BinaryComparison"/>
|
||||
/// </summary>
|
||||
public bool Evaluate<T>(T left, T right)
|
||||
=> OfType<T>.GetFunc(Type)(left, right);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a function capable of performing this <see cref="BinaryComparison"/>
|
||||
/// </summary>
|
||||
public Func<T, T, bool> GetEvaluator<T>()
|
||||
=> OfType<T>.GetFunc(Type);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an expression representing this <see cref="BinaryComparison"/>
|
||||
/// </summary>
|
||||
public Expression<Func<T, T, bool>> GetExpression<T>()
|
||||
=> OfType<T>.GetExpression(Type);
|
||||
|
||||
/// <summary>
|
||||
/// Flips the logic ordering of the comparison's operands. For example, <see cref="LessThan"/>
|
||||
/// is converted into <see cref="GreaterThan"/>
|
||||
/// </summary>
|
||||
public BinaryComparison FlipOperands()
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case ExpressionType.Equal: return this;
|
||||
case ExpressionType.NotEqual: return this;
|
||||
case ExpressionType.LessThan: return GreaterThan;
|
||||
case ExpressionType.LessThanOrEqual: return GreaterThanOrEqual;
|
||||
case ExpressionType.GreaterThan: return LessThan;
|
||||
case ExpressionType.GreaterThanOrEqual: return LessThanOrEqual;
|
||||
default:
|
||||
throw new Exception(
|
||||
"The skies are falling and the oceans are rising! " +
|
||||
"If you've made it here then this exception is the least of your worries! " +
|
||||
$"ExpressionType: {Type}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns a string that represents the current object.</summary>
|
||||
/// <returns>A string that represents the current object.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Type.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides thread-safe lookups of expressions and functions for binary comparisons by type.
|
||||
/// MUCH faster than using a concurrency dictionary, for example, as it's expanded at runtime
|
||||
/// and hard-linked, no look-up is actually performed!
|
||||
/// </summary>
|
||||
private static class OfType<T>
|
||||
{
|
||||
private static readonly Expression<Func<T, T, bool>> EqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.Equal);
|
||||
private static readonly Expression<Func<T, T, bool>> NotEqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.NotEqual);
|
||||
private static readonly Expression<Func<T, T, bool>> LessThanExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.LessThan);
|
||||
private static readonly Expression<Func<T, T, bool>> LessThanOrEqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.LessThanOrEqual);
|
||||
private static readonly Expression<Func<T, T, bool>> GreaterThanExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.GreaterThan);
|
||||
private static readonly Expression<Func<T, T, bool>> GreaterThanOrEqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.GreaterThanOrEqual);
|
||||
|
||||
public static Expression<Func<T, T, bool>> GetExpression(ExpressionType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ExpressionType.Equal: return EqualExpr;
|
||||
case ExpressionType.NotEqual: return NotEqualExpr;
|
||||
case ExpressionType.LessThan: return LessThanExpr;
|
||||
case ExpressionType.LessThanOrEqual: return LessThanOrEqualExpr;
|
||||
case ExpressionType.GreaterThan: return GreaterThanExpr;
|
||||
case ExpressionType.GreaterThanOrEqual: return GreaterThanOrEqualExpr;
|
||||
default:
|
||||
throw new InvalidOperationException($"The specified ExpressionType '{type}' is not a binary comparison.");
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Func<T, T, bool> EqualFunc = EqualExpr?.Compile();
|
||||
private static readonly Func<T, T, bool> NotEqualFunc = NotEqualExpr?.Compile();
|
||||
private static readonly Func<T, T, bool> LessThanFunc = LessThanExpr?.Compile();
|
||||
private static readonly Func<T, T, bool> LessThanOrEqualFunc = LessThanOrEqualExpr?.Compile();
|
||||
private static readonly Func<T, T, bool> GreaterThanFunc = GreaterThanExpr?.Compile();
|
||||
private static readonly Func<T, T, bool> GreaterThanOrEqualFunc = GreaterThanOrEqualExpr?.Compile();
|
||||
|
||||
public static Func<T, T, bool> GetFunc(ExpressionType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ExpressionType.Equal: return EqualFunc;
|
||||
case ExpressionType.NotEqual: return NotEqualFunc;
|
||||
case ExpressionType.LessThan: return LessThanFunc;
|
||||
case ExpressionType.LessThanOrEqual: return LessThanOrEqualFunc;
|
||||
case ExpressionType.GreaterThan: return GreaterThanFunc;
|
||||
case ExpressionType.GreaterThanOrEqual: return GreaterThanOrEqualFunc;
|
||||
default:
|
||||
throw new InvalidOperationException($"The specified ExpressionType '{type}' is not a binary comparison.");
|
||||
}
|
||||
}
|
||||
|
||||
private static Expression<Func<T, T, bool>> MakeBinaryComparisonLambdaOrNull(ExpressionType type)
|
||||
{
|
||||
try
|
||||
{
|
||||
return MakeBinaryComparisonLambda<T>(type);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
Common/BinaryComparisonExtensions.cs
Normal file
211
Common/BinaryComparisonExtensions.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace QuantConnect
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides convenience extension methods for applying a <see cref="BinaryComparison"/> to collections.
|
||||
/// </summary>
|
||||
public static class BinaryComparisonExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
|
||||
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
|
||||
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
|
||||
/// we call Filter(values, 42). We're looking for keys that are less than 42.
|
||||
/// </summary>
|
||||
public static TCollection Filter<T, TCollection>(
|
||||
this BinaryComparison comparison,
|
||||
TCollection values,
|
||||
T reference
|
||||
)
|
||||
where TCollection : ICollection<T>, new()
|
||||
{
|
||||
var result = new TCollection();
|
||||
var evaluator = comparison.GetEvaluator<T>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (evaluator(value, reference))
|
||||
{
|
||||
result.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
|
||||
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
|
||||
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
|
||||
/// we call Filter(values, 42). We're looking for keys that are less than 42.
|
||||
/// </summary>
|
||||
public static SortedDictionary<TKey, TValue> Filter<TKey, TValue>(
|
||||
this BinaryComparison comparison,
|
||||
SortedDictionary<TKey, TValue> values,
|
||||
TKey reference
|
||||
)
|
||||
{
|
||||
SortedDictionary<TKey, TValue> result;
|
||||
if (comparison.Type == ExpressionType.NotEqual)
|
||||
{
|
||||
result = new SortedDictionary<TKey, TValue>(values);
|
||||
result.Remove(reference);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = new SortedDictionary<TKey, TValue>();
|
||||
if (comparison.Type == ExpressionType.Equal)
|
||||
{
|
||||
TValue value;
|
||||
if (values.TryGetValue(reference, out value))
|
||||
{
|
||||
result.Add(reference, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// since we're enumerating a sorted collection, once we receive
|
||||
// a mismatch it means we'll never again receive a match
|
||||
var breakAfterFailure =
|
||||
comparison == BinaryComparison.LessThanOrEqual ||
|
||||
comparison == BinaryComparison.LessThanOrEqual;
|
||||
|
||||
var evaluator = comparison.GetEvaluator<TKey>();
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (evaluator(kvp.Key, reference))
|
||||
{
|
||||
result.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
else if (breakAfterFailure)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
|
||||
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
|
||||
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
|
||||
/// we call Filter(values, 42). We're looking for keys that are less than 42.
|
||||
/// </summary>
|
||||
public static ImmutableSortedDictionary<TKey, TValue> Filter<TKey, TValue>(
|
||||
this BinaryComparison comparison,
|
||||
ImmutableSortedDictionary<TKey, TValue> values,
|
||||
TKey reference
|
||||
)
|
||||
{
|
||||
if (comparison.Type == ExpressionType.NotEqual)
|
||||
{
|
||||
return values.Remove(reference);
|
||||
}
|
||||
|
||||
var result = ImmutableSortedDictionary<TKey, TValue>.Empty;
|
||||
if (comparison.Type == ExpressionType.Equal)
|
||||
{
|
||||
TValue value;
|
||||
if (values.TryGetValue(reference, out value))
|
||||
{
|
||||
result = result.Add(reference, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// since we're enumerating a sorted collection, once we receive
|
||||
// a mismatch it means we'll never again receive a match
|
||||
var breakAfterFailure =
|
||||
comparison == BinaryComparison.LessThanOrEqual ||
|
||||
comparison == BinaryComparison.LessThanOrEqual;
|
||||
|
||||
var evaluator = comparison.GetEvaluator<TKey>();
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (evaluator(kvp.Key, reference))
|
||||
{
|
||||
result = result.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
else if (breakAfterFailure)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
|
||||
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
|
||||
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
|
||||
/// we call Filter(values, 42). We're looking for keys that are less than 42.
|
||||
/// </summary>
|
||||
public static Tuple<ImmutableSortedDictionary<TKey, TValue>, ImmutableSortedDictionary<TKey, TValue>> SplitBy<TKey, TValue>(
|
||||
this BinaryComparison comparison,
|
||||
ImmutableSortedDictionary<TKey, TValue> values,
|
||||
TKey reference
|
||||
)
|
||||
{
|
||||
var matches = ImmutableSortedDictionary<TKey, TValue>.Empty;
|
||||
var removed = ImmutableSortedDictionary<TKey, TValue>.Empty;
|
||||
|
||||
if (comparison.Type == ExpressionType.NotEqual)
|
||||
{
|
||||
var match = values.Remove(reference);
|
||||
removed = BinaryComparison.Equal.Filter(values, reference);
|
||||
return Tuple.Create(match, removed);
|
||||
}
|
||||
|
||||
if (comparison.Type == ExpressionType.Equal)
|
||||
{
|
||||
TValue value;
|
||||
if (values.TryGetValue(reference, out value))
|
||||
{
|
||||
matches = matches.Add(reference, value);
|
||||
removed = BinaryComparison.NotEqual.Filter(values, reference);
|
||||
return Tuple.Create(matches, removed);
|
||||
}
|
||||
|
||||
// no matches
|
||||
return Tuple.Create(ImmutableSortedDictionary<TKey, TValue>.Empty, values);
|
||||
}
|
||||
|
||||
var evaluator = comparison.GetEvaluator<TKey>();
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (evaluator(kvp.Key, reference))
|
||||
{
|
||||
matches = matches.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
removed = removed.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return Tuple.Create(matches, removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,7 @@ namespace QuantConnect.Brokerages
|
||||
case SecurityType.Base:
|
||||
case SecurityType.Commodity:
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
case SecurityType.Future:
|
||||
default:
|
||||
return 1m;
|
||||
@@ -92,4 +93,4 @@ namespace QuantConnect.Brokerages
|
||||
/// <returns>The settlement model for this brokerage</returns>
|
||||
public override ISettlementModel GetSettlementModel(Security security) => new ImmediateSettlementModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace QuantConnect.Brokerages
|
||||
{SecurityType.Equity, Market.USA},
|
||||
{SecurityType.Option, Market.USA},
|
||||
{SecurityType.Future, Market.CME},
|
||||
{SecurityType.FutureOption, Market.CME},
|
||||
{SecurityType.Forex, Market.Oanda},
|
||||
{SecurityType.Cfd, Market.FXCM},
|
||||
{SecurityType.Crypto, Market.GDAX}
|
||||
@@ -174,6 +175,7 @@ namespace QuantConnect.Brokerages
|
||||
case SecurityType.Base:
|
||||
case SecurityType.Commodity:
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
case SecurityType.Future:
|
||||
default:
|
||||
return 1m;
|
||||
@@ -195,6 +197,8 @@ namespace QuantConnect.Brokerages
|
||||
return new EquityFillModel();
|
||||
case SecurityType.Option:
|
||||
break;
|
||||
case SecurityType.FutureOption:
|
||||
break;
|
||||
case SecurityType.Commodity:
|
||||
break;
|
||||
case SecurityType.Forex:
|
||||
@@ -230,6 +234,7 @@ namespace QuantConnect.Brokerages
|
||||
case SecurityType.Equity:
|
||||
case SecurityType.Option:
|
||||
case SecurityType.Future:
|
||||
case SecurityType.FutureOption:
|
||||
return new InteractiveBrokersFeeModel();
|
||||
|
||||
case SecurityType.Commodity:
|
||||
@@ -258,6 +263,7 @@ namespace QuantConnect.Brokerages
|
||||
|
||||
case SecurityType.Commodity:
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
case SecurityType.Future:
|
||||
default:
|
||||
return new ConstantSlippageModel(0);
|
||||
@@ -321,6 +327,9 @@ namespace QuantConnect.Brokerages
|
||||
case SecurityType.Option:
|
||||
model = new OptionMarginModel(RequiredFreeBuyingPowerPercent);
|
||||
break;
|
||||
case SecurityType.FutureOption:
|
||||
model = new FuturesOptionsMarginModel(RequiredFreeBuyingPowerPercent, (Option)security);
|
||||
break;
|
||||
case SecurityType.Future:
|
||||
model = new FutureMarginModel(RequiredFreeBuyingPowerPercent, security);
|
||||
break;
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace QuantConnect.Brokerages
|
||||
{SecurityType.Equity, Market.USA},
|
||||
{SecurityType.Option, Market.USA},
|
||||
{SecurityType.Future, Market.CME},
|
||||
{SecurityType.FutureOption, Market.CME},
|
||||
{SecurityType.Forex, Market.Oanda},
|
||||
{SecurityType.Cfd, Market.Oanda}
|
||||
}.ToReadOnlyDictionary();
|
||||
@@ -95,7 +96,8 @@ namespace QuantConnect.Brokerages
|
||||
if (security.Type != SecurityType.Equity &&
|
||||
security.Type != SecurityType.Forex &&
|
||||
security.Type != SecurityType.Option &&
|
||||
security.Type != SecurityType.Future)
|
||||
security.Type != SecurityType.Future &&
|
||||
security.Type != SecurityType.FutureOption)
|
||||
{
|
||||
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
|
||||
Invariant($"The {nameof(InteractiveBrokersBrokerageModel)} does not support {security.Type} security type.")
|
||||
|
||||
@@ -63,6 +63,11 @@ namespace QuantConnect.Data
|
||||
/// </summary>
|
||||
protected static readonly List<Resolution> MinuteResolution = new List<Resolution> { Resolution.Minute };
|
||||
|
||||
/// <summary>
|
||||
/// A list of high <see cref="Resolution"/>, including minute, second, and tick.
|
||||
/// </summary>
|
||||
protected static readonly List<Resolution> HighResolution = new List<Resolution> { Resolution.Minute, Resolution.Second, Resolution.Tick };
|
||||
|
||||
/// <summary>
|
||||
/// Market Data Type of this data - does it come in individual price packets or is it grouped into OHLC.
|
||||
/// </summary>
|
||||
@@ -198,7 +203,8 @@ namespace QuantConnect.Data
|
||||
/// <returns>True indicates mapping should be used</returns>
|
||||
public virtual bool RequiresMapping()
|
||||
{
|
||||
return Symbol.SecurityType == SecurityType.Equity || Symbol.SecurityType == SecurityType.Option;
|
||||
return Symbol.SecurityType == SecurityType.Equity ||
|
||||
Symbol.SecurityType == SecurityType.Option;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -232,11 +238,16 @@ namespace QuantConnect.Data
|
||||
/// custom data types can override it</remarks>
|
||||
public virtual List<Resolution> SupportedResolutions()
|
||||
{
|
||||
if (Symbol.SecurityType == SecurityType.Option)
|
||||
if (Symbol.SecurityType == SecurityType.Option || Symbol.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
return MinuteResolution;
|
||||
}
|
||||
|
||||
if (Symbol.SecurityType == SecurityType.Future)
|
||||
{
|
||||
return HighResolution;
|
||||
}
|
||||
|
||||
return AllResolutions;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,5 +61,10 @@ namespace QuantConnect.Data.Custom.SmartInsider
|
||||
/// </summary>
|
||||
[EnumMember(Value = "Independent 3rd Party")]
|
||||
ThirdParty,
|
||||
|
||||
/// <summary>
|
||||
/// The field was not found in this enum
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,9 @@ namespace QuantConnect.Data.Custom.SmartInsider
|
||||
SatisfyStockVesting,
|
||||
|
||||
/// <summary>
|
||||
/// Error, but should actually be SatisfyStockVesting
|
||||
/// The field was not found in the enum, or is representative of a SatisfyStockVesting entry.
|
||||
/// </summary>
|
||||
/// <remarks>The EnumMember attribute is kept for backwards compatibility</remarks>
|
||||
[EnumMember(Value = "Missing Lookup Formula for BuybackHoldingTypeId 10.00")]
|
||||
Error
|
||||
}
|
||||
|
||||
@@ -41,6 +41,11 @@ namespace QuantConnect.Data.Custom.SmartInsider
|
||||
/// Under a specific agreement between the issuer and shareholder
|
||||
/// </summary>
|
||||
[EnumMember(Value = "Off Market Agreement")]
|
||||
OffMarket
|
||||
OffMarket,
|
||||
|
||||
/// <summary>
|
||||
/// Field is not in this enum
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace QuantConnect.Data.Custom.SmartInsider
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
Log.Error($"SmartInsiderIntention.FromRawData: New unexpected entry found {tsv[1]}. Parsed as NotSpecified.");
|
||||
Log.Error($"SmartInsiderIntention.FromRawData(): New unexpected entry found {tsv[1]}. Parsed as NotSpecified.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,10 +171,54 @@ namespace QuantConnect.Data.Custom.SmartInsider
|
||||
TimeProcessedUtc = string.IsNullOrWhiteSpace(tsv[41]) ? (DateTime?)null : ParseDate(tsv[41]);
|
||||
AnnouncedIn = string.IsNullOrWhiteSpace(tsv[42]) ? null : tsv[42];
|
||||
|
||||
Execution = string.IsNullOrWhiteSpace(tsv[43]) ? (SmartInsiderExecution?)null : JsonConvert.DeserializeObject<SmartInsiderExecution>($"\"{tsv[43]}\"");
|
||||
ExecutionEntity = string.IsNullOrWhiteSpace(tsv[44]) ? (SmartInsiderExecutionEntity?)null : JsonConvert.DeserializeObject<SmartInsiderExecutionEntity>($"\"{tsv[44]}\"");
|
||||
ExecutionHolding = string.IsNullOrWhiteSpace(tsv[45]) ? (SmartInsiderExecutionHolding?)null : JsonConvert.DeserializeObject<SmartInsiderExecutionHolding>($"\"{tsv[45]}\"");
|
||||
ExecutionHolding = ExecutionHolding == SmartInsiderExecutionHolding.Error ? SmartInsiderExecutionHolding.SatisfyStockVesting : ExecutionHolding;
|
||||
Execution = null;
|
||||
if (!string.IsNullOrWhiteSpace(tsv[43]))
|
||||
{
|
||||
try
|
||||
{
|
||||
Execution = JsonConvert.DeserializeObject<SmartInsiderExecution>($"\"{tsv[43]}\"");
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
Log.Error($"SmartInsiderIntention.FromRawData(): New unexpected entry found for Execution: {tsv[43]}. Parsed as Error.");
|
||||
Execution = SmartInsiderExecution.Error;
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionEntity = null;
|
||||
if (!string.IsNullOrWhiteSpace(tsv[44]))
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecutionEntity = JsonConvert.DeserializeObject<SmartInsiderExecutionEntity>($"\"{tsv[44]}\"");
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
Log.Error($"SmartInsiderIntention.FromRawData(): New unexpected entry found for ExecutionEntity: {tsv[44]}. Parsed as Error.");
|
||||
ExecutionEntity = SmartInsiderExecutionEntity.Error;
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionHolding = null;
|
||||
if (!string.IsNullOrWhiteSpace(tsv[45]))
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecutionHolding = JsonConvert.DeserializeObject<SmartInsiderExecutionHolding>($"\"{tsv[45]}\"");
|
||||
if (ExecutionHolding == SmartInsiderExecutionHolding.Error)
|
||||
{
|
||||
// This error in particular represents a SatisfyStockVesting field.
|
||||
ExecutionHolding = SmartInsiderExecutionHolding.SatisfyStockVesting;
|
||||
}
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
Log.Error($"SmartInsiderIntention.FromRawData(): New unexpected entry found for ExecutionHolding: {tsv[45]}. Parsed as Error.");
|
||||
ExecutionHolding = SmartInsiderExecutionHolding.Error;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Amount = string.IsNullOrWhiteSpace(tsv[46]) ? (int?)null : Convert.ToInt32(tsv[46], CultureInfo.InvariantCulture);
|
||||
ValueCurrency = string.IsNullOrWhiteSpace(tsv[47]) ? null : tsv[47];
|
||||
AmountValue = string.IsNullOrWhiteSpace(tsv[48]) ? (long?)null : Convert.ToInt64(tsv[48], CultureInfo.InvariantCulture);
|
||||
|
||||
@@ -17,6 +17,7 @@ using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using QuantConnect.Logging;
|
||||
|
||||
namespace QuantConnect.Data.Custom.SmartInsider
|
||||
{
|
||||
@@ -154,7 +155,18 @@ namespace QuantConnect.Data.Custom.SmartInsider
|
||||
var tsv = line.Split('\t');
|
||||
|
||||
TransactionID = string.IsNullOrWhiteSpace(tsv[0]) ? null : tsv[0];
|
||||
EventType = string.IsNullOrWhiteSpace(tsv[1]) ? (SmartInsiderEventType?)null : JsonConvert.DeserializeObject<SmartInsiderEventType>($"\"{tsv[1]}\"");
|
||||
EventType = SmartInsiderEventType.NotSpecified;
|
||||
if (!string.IsNullOrWhiteSpace(tsv[1]))
|
||||
{
|
||||
try
|
||||
{
|
||||
EventType = JsonConvert.DeserializeObject<SmartInsiderEventType>($"\"{tsv[1]}\"");
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
Log.Error($"SmartInsiderTransaction.FromRawData(): New unexpected entry found for EventType: {tsv[1]}. Parsed as NotSpecified.");
|
||||
}
|
||||
}
|
||||
LastUpdate = DateTime.ParseExact(tsv[2], "yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
LastIDsUpdate = string.IsNullOrWhiteSpace(tsv[3]) ? (DateTime?)null : DateTime.ParseExact(tsv[3], "yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
ISIN = string.IsNullOrWhiteSpace(tsv[4]) ? null : tsv[4];
|
||||
@@ -175,10 +187,54 @@ namespace QuantConnect.Data.Custom.SmartInsider
|
||||
TickerSymbol = string.IsNullOrWhiteSpace(tsv[19]) ? null : tsv[19];
|
||||
|
||||
BuybackDate = string.IsNullOrWhiteSpace(tsv[20]) ? (DateTime?)null : DateTime.ParseExact(tsv[20], "yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
Execution = string.IsNullOrWhiteSpace(tsv[21]) ? (SmartInsiderExecution?)null : JsonConvert.DeserializeObject<SmartInsiderExecution>($"\"{tsv[21]}\"");
|
||||
ExecutionEntity = string.IsNullOrWhiteSpace(tsv[22]) ? (SmartInsiderExecutionEntity?)null : JsonConvert.DeserializeObject<SmartInsiderExecutionEntity>($"\"{tsv[22]}\"");
|
||||
ExecutionHolding = string.IsNullOrWhiteSpace(tsv[23]) ? (SmartInsiderExecutionHolding?)null : JsonConvert.DeserializeObject<SmartInsiderExecutionHolding>($"\"{tsv[23]}\"");
|
||||
ExecutionHolding = ExecutionHolding == SmartInsiderExecutionHolding.Error ? SmartInsiderExecutionHolding.SatisfyStockVesting : ExecutionHolding;
|
||||
|
||||
Execution = null;
|
||||
if (!string.IsNullOrWhiteSpace(tsv[21]))
|
||||
{
|
||||
try
|
||||
{
|
||||
Execution = JsonConvert.DeserializeObject<SmartInsiderExecution>($"\"{tsv[21]}\"");
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
Log.Error($"SmartInsiderTransaction.FromRawData(): New unexpected entry found for Execution: {tsv[21]}. Parsed as Error.");
|
||||
Execution = SmartInsiderExecution.Error;
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionEntity = null;
|
||||
if (!string.IsNullOrWhiteSpace(tsv[22]))
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecutionEntity = JsonConvert.DeserializeObject<SmartInsiderExecutionEntity>($"\"{tsv[22]}\"");
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
Log.Error($"SmartInsiderTransaction.FromRawData(): New unexpected entry found for ExecutionEntity: {tsv[22]}. Parsed as Error.");
|
||||
ExecutionEntity = SmartInsiderExecutionEntity.Error;
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionHolding = null;
|
||||
if (!string.IsNullOrWhiteSpace(tsv[23]))
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecutionHolding = JsonConvert.DeserializeObject<SmartInsiderExecutionHolding>($"\"{tsv[23]}\"");
|
||||
if (ExecutionHolding == SmartInsiderExecutionHolding.Error)
|
||||
{
|
||||
// This error in particular represents a SatisfyStockVesting field.
|
||||
ExecutionHolding = SmartInsiderExecutionHolding.SatisfyStockVesting;
|
||||
}
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
Log.Error($"SmartInsiderTransaction.FromRawData(): New unexpected entry found for ExecutionHolding: {tsv[23]}. Parsed as Error.");
|
||||
ExecutionHolding = SmartInsiderExecutionHolding.Error;
|
||||
}
|
||||
}
|
||||
|
||||
Currency = string.IsNullOrWhiteSpace(tsv[24]) ? null : tsv[24];
|
||||
ExecutionPrice = string.IsNullOrWhiteSpace(tsv[25]) ? (decimal?)null : Convert.ToDecimal(tsv[25], CultureInfo.InvariantCulture);
|
||||
Amount = string.IsNullOrWhiteSpace(tsv[26]) ? (decimal?)null : Convert.ToDecimal(tsv[26], CultureInfo.InvariantCulture);
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace QuantConnect.Data.Fundamental
|
||||
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
|
||||
{
|
||||
var source =
|
||||
Path.Combine(Globals.DataFolder, Invariant(
|
||||
Path.Combine(Globals.CacheDataFolder, Invariant(
|
||||
$"equity/{config.Market}/fundamental/fine/{config.Symbol.Value.ToLowerInvariant()}/{date:yyyyMMdd}.zip"
|
||||
));
|
||||
|
||||
|
||||
@@ -54,17 +54,8 @@ namespace QuantConnect.Data.Market
|
||||
/// <param name="time">The time this data was emitted.</param>
|
||||
public DataDictionary(DateTime time)
|
||||
{
|
||||
#pragma warning disable 618 // This assignment is left here until the Time property is removed.
|
||||
Time = time;
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time associated with this collection of data
|
||||
/// </summary>
|
||||
[Obsolete("The DataDictionary<T> Time property is now obsolete. All algorithms should use algorithm.Time instead.")]
|
||||
public DateTime Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
@@ -300,4 +291,4 @@ namespace QuantConnect.Data.Market
|
||||
dictionary.Add(data.Symbol, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="ticker">The ticker of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new Delisting this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
|
||||
public new Delisting this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Delisting with the specified Symbol.
|
||||
@@ -57,6 +57,6 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="symbol">The Symbol of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new Delisting this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
|
||||
public new Delisting this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="ticker">The ticker of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new Dividend this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
|
||||
public new Dividend this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Dividend with the specified Symbol.
|
||||
@@ -57,6 +57,6 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="symbol">The Symbol of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new Dividend this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
|
||||
public new Dividend this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="ticker">The ticker of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new FuturesChain this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
|
||||
public new FuturesChain this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FuturesChain with the specified Symbol.
|
||||
@@ -55,6 +55,6 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="symbol">The Symbol of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new FuturesChain this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
|
||||
public new FuturesChain this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ namespace QuantConnect.Data.Market
|
||||
|
||||
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
|
||||
if (config.SecurityType == SecurityType.Option ||
|
||||
config.SecurityType == SecurityType.FutureOption ||
|
||||
config.SecurityType == SecurityType.Future)
|
||||
{
|
||||
source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType);
|
||||
@@ -153,4 +154,4 @@ namespace QuantConnect.Data.Market
|
||||
return new OpenInterest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="ticker">The ticker of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new OptionChain this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
|
||||
public new OptionChain this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the OptionChain with the specified Symbol.
|
||||
@@ -55,6 +55,6 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="symbol">The Symbol of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new OptionChain this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
|
||||
public new OptionChain this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +298,7 @@ namespace QuantConnect.Data.Market
|
||||
return ParseCfd(config, stream, date);
|
||||
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
return ParseOption(config, stream, date);
|
||||
|
||||
case SecurityType.Future:
|
||||
@@ -344,6 +345,7 @@ namespace QuantConnect.Data.Market
|
||||
return ParseCfd(config, line, date);
|
||||
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
return ParseOption(config, line, date);
|
||||
|
||||
case SecurityType.Future:
|
||||
@@ -455,7 +457,7 @@ namespace QuantConnect.Data.Market
|
||||
/// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
|
||||
public QuoteBar ParseOption(SubscriptionDataConfig config, string line, DateTime date)
|
||||
{
|
||||
return ParseQuote(config, date, line, true);
|
||||
return ParseQuote(config, date, line, config.Symbol.SecurityType == SecurityType.Option);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -467,7 +469,7 @@ namespace QuantConnect.Data.Market
|
||||
/// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
|
||||
public QuoteBar ParseOption(SubscriptionDataConfig config, StreamReader streamReader, DateTime date)
|
||||
{
|
||||
return ParseQuote(config, date, streamReader, true);
|
||||
return ParseQuote(config, date, streamReader, config.Symbol.SecurityType == SecurityType.Option);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -552,6 +554,7 @@ namespace QuantConnect.Data.Market
|
||||
/// <returns><see cref="QuoteBar"/> with the bid/ask prices set appropriately</returns>
|
||||
private QuoteBar ParseQuote(SubscriptionDataConfig config, DateTime date, StreamReader streamReader, bool useScaleFactor)
|
||||
{
|
||||
// Non-equity asset classes will not use scaling, including options that have a non-equity underlying asset class.
|
||||
var scaleFactor = useScaleFactor
|
||||
? _scaleFactor
|
||||
: 1;
|
||||
@@ -712,7 +715,8 @@ namespace QuantConnect.Data.Market
|
||||
|
||||
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
|
||||
if (config.SecurityType == SecurityType.Option ||
|
||||
config.SecurityType == SecurityType.Future)
|
||||
config.SecurityType == SecurityType.Future ||
|
||||
config.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="ticker">The ticker of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new QuoteBar this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
|
||||
public new QuoteBar this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the QuoteBar with the specified Symbol.
|
||||
@@ -55,6 +55,6 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="symbol">The Symbol of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new QuoteBar this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
|
||||
public new QuoteBar this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="ticker">The ticker of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new Split this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
|
||||
public new Split this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Split with the specified Symbol.
|
||||
@@ -57,6 +57,6 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="symbol">The Symbol of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new Split this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
|
||||
public new Split this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="ticker">The ticker of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new SymbolChangedEvent this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
|
||||
public new SymbolChangedEvent this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SymbolChangedEvent with the specified Symbol.
|
||||
@@ -57,6 +57,6 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="symbol">The Symbol of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new SymbolChangedEvent this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
|
||||
public new SymbolChangedEvent this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ namespace QuantConnect.Data.Market
|
||||
DataType = MarketDataType.Tick;
|
||||
Symbol = symbol;
|
||||
Time = baseDate.Date.AddMilliseconds(csv[0].ToInt32());
|
||||
Value = csv[1].ToDecimal() / GetScaleFactor(symbol.SecurityType);
|
||||
Value = csv[1].ToDecimal() / GetScaleFactor(symbol);
|
||||
TickType = TickType.Trade;
|
||||
Quantity = csv[2].ToDecimal();
|
||||
Exchange = csv[3].Trim();
|
||||
@@ -302,7 +302,7 @@ namespace QuantConnect.Data.Market
|
||||
Symbol = config.Symbol;
|
||||
|
||||
// Which security type is this data feed:
|
||||
var scaleFactor = GetScaleFactor(config.SecurityType);
|
||||
var scaleFactor = GetScaleFactor(config.Symbol);
|
||||
|
||||
switch (config.SecurityType)
|
||||
{
|
||||
@@ -387,6 +387,7 @@ namespace QuantConnect.Data.Market
|
||||
}
|
||||
case SecurityType.Future:
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
{
|
||||
TickType = config.TickType;
|
||||
Time = date.Date.AddMilliseconds((double)reader.GetDecimal())
|
||||
@@ -440,7 +441,7 @@ namespace QuantConnect.Data.Market
|
||||
Symbol = config.Symbol;
|
||||
|
||||
// Which security type is this data feed:
|
||||
var scaleFactor = GetScaleFactor(config.SecurityType);
|
||||
var scaleFactor = GetScaleFactor(config.Symbol);
|
||||
|
||||
switch (config.SecurityType)
|
||||
{
|
||||
@@ -530,6 +531,7 @@ namespace QuantConnect.Data.Market
|
||||
}
|
||||
case SecurityType.Future:
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
{
|
||||
var csv = line.ToCsv(7);
|
||||
TickType = config.TickType;
|
||||
@@ -632,7 +634,8 @@ namespace QuantConnect.Data.Market
|
||||
|
||||
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
|
||||
if (config.SecurityType == SecurityType.Option ||
|
||||
config.SecurityType == SecurityType.Future)
|
||||
config.SecurityType == SecurityType.Future ||
|
||||
config.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType);
|
||||
}
|
||||
@@ -714,10 +717,16 @@ namespace QuantConnect.Data.Market
|
||||
}
|
||||
}
|
||||
|
||||
private static decimal GetScaleFactor(SecurityType securityType)
|
||||
/// <summary>
|
||||
/// Gets the scaling factor according to the <see cref="SecurityType"/> of the <see cref="Symbol"/> provided.
|
||||
/// Non-equity data will not be scaled, including options with an underlying non-equity asset class.
|
||||
/// </summary>
|
||||
/// <param name="symbol">Symbol to get scaling factor for</param>
|
||||
/// <returns>Scaling factor</returns>
|
||||
private static decimal GetScaleFactor(Symbol symbol)
|
||||
{
|
||||
return securityType == SecurityType.Equity || securityType == SecurityType.Option ? 10000m : 1;
|
||||
return symbol.SecurityType == SecurityType.Equity || symbol.SecurityType == SecurityType.Option ? 10000m : 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="ticker">The ticker of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new List<Tick> this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
|
||||
public new List<Tick> this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of Tick with the specified Symbol.
|
||||
@@ -58,6 +58,6 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="symbol">The Symbol of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new List<Tick> this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
|
||||
public new List<Tick> this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +221,7 @@ namespace QuantConnect.Data.Market
|
||||
return ParseCfd(config, line, date);
|
||||
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
return ParseOption(config, line, date);
|
||||
|
||||
case SecurityType.Future:
|
||||
@@ -278,6 +279,7 @@ namespace QuantConnect.Data.Market
|
||||
return ParseCfd(config, stream, date);
|
||||
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
return ParseOption(config, stream, date);
|
||||
|
||||
case SecurityType.Future:
|
||||
@@ -682,10 +684,11 @@ namespace QuantConnect.Data.Market
|
||||
tradeBar.Time = date.Date.AddMilliseconds(csv[0].ToInt32()).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
|
||||
}
|
||||
|
||||
tradeBar.Open = csv[1].ToDecimal() * _scaleFactor;
|
||||
tradeBar.High = csv[2].ToDecimal() * _scaleFactor;
|
||||
tradeBar.Low = csv[3].ToDecimal() * _scaleFactor;
|
||||
tradeBar.Close = csv[4].ToDecimal() * _scaleFactor;
|
||||
var scalingFactor = GetScaleFactor(config.Symbol);
|
||||
tradeBar.Open = csv[1].ToDecimal() * scalingFactor;
|
||||
tradeBar.High = csv[2].ToDecimal() * scalingFactor;
|
||||
tradeBar.Low = csv[3].ToDecimal() * scalingFactor;
|
||||
tradeBar.Close = csv[4].ToDecimal() * scalingFactor;
|
||||
tradeBar.Volume = csv[5].ToDecimal();
|
||||
|
||||
return tradeBar;
|
||||
@@ -719,10 +722,11 @@ namespace QuantConnect.Data.Market
|
||||
tradeBar.Time = date.Date.AddMilliseconds(streamReader.GetInt32()).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
|
||||
}
|
||||
|
||||
tradeBar.Open = streamReader.GetDecimal() * _scaleFactor;
|
||||
tradeBar.High = streamReader.GetDecimal() * _scaleFactor;
|
||||
tradeBar.Low = streamReader.GetDecimal() * _scaleFactor;
|
||||
tradeBar.Close = streamReader.GetDecimal() * _scaleFactor;
|
||||
var scalingFactor = GetScaleFactor(config.Symbol);
|
||||
tradeBar.Open = streamReader.GetDecimal() * scalingFactor;
|
||||
tradeBar.High = streamReader.GetDecimal() * scalingFactor;
|
||||
tradeBar.Low = streamReader.GetDecimal() * scalingFactor;
|
||||
tradeBar.Close = streamReader.GetDecimal() * scalingFactor;
|
||||
tradeBar.Volume = streamReader.GetDecimal();
|
||||
|
||||
return tradeBar;
|
||||
@@ -889,7 +893,8 @@ namespace QuantConnect.Data.Market
|
||||
|
||||
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
|
||||
if (config.SecurityType == SecurityType.Option ||
|
||||
config.SecurityType == SecurityType.Future)
|
||||
config.SecurityType == SecurityType.Future ||
|
||||
config.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType);
|
||||
}
|
||||
@@ -936,6 +941,17 @@ namespace QuantConnect.Data.Market
|
||||
$"V: {Volume.SmartRounding()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scaling factor according to the <see cref="SecurityType"/> of the <see cref="Symbol"/> provided.
|
||||
/// Non-equity data will not be scaled, including options with an underlying non-equity asset class.
|
||||
/// </summary>
|
||||
/// <param name="symbol">Symbol to get scaling factor for</param>
|
||||
/// <returns>Scaling factor</returns>
|
||||
private static decimal GetScaleFactor(Symbol symbol)
|
||||
{
|
||||
return symbol.SecurityType == SecurityType.Equity || symbol.SecurityType == SecurityType.Option ? _scaleFactor : 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this bar with a first data point
|
||||
/// </summary>
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="ticker">The ticker of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new TradeBar this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
|
||||
public new TradeBar this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TradeBar with the specified Symbol.
|
||||
@@ -56,6 +56,6 @@ namespace QuantConnect.Data.Market
|
||||
/// </returns>
|
||||
/// <param name="symbol">The Symbol of the element to get or set.</param>
|
||||
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
|
||||
public new TradeBar this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
|
||||
public new TradeBar this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,15 +203,15 @@ namespace QuantConnect.Data
|
||||
/// <param name="time">The timestamp for this slice of data</param>
|
||||
/// <param name="data">The raw data in this slice</param>
|
||||
public Slice(DateTime time, List<BaseData> data)
|
||||
: this(time, data, CreateCollection<TradeBars, TradeBar>(time, data),
|
||||
CreateCollection<QuoteBars, QuoteBar>(time, data),
|
||||
CreateTicksCollection(time, data),
|
||||
CreateCollection<OptionChains, OptionChain>(time, data),
|
||||
CreateCollection<FuturesChains, FuturesChain>(time, data),
|
||||
CreateCollection<Splits, Split>(time, data),
|
||||
CreateCollection<Dividends, Dividend>(time, data),
|
||||
CreateCollection<Delistings, Delisting>(time, data),
|
||||
CreateCollection<SymbolChangedEvents, SymbolChangedEvent>(time, data))
|
||||
: this(time, data, CreateCollection<TradeBars, TradeBar>(data),
|
||||
CreateCollection<QuoteBars, QuoteBar>(data),
|
||||
CreateTicksCollection(data),
|
||||
CreateCollection<OptionChains, OptionChain>(data),
|
||||
CreateCollection<FuturesChains, FuturesChain>(data),
|
||||
CreateCollection<Splits, Split>(data),
|
||||
CreateCollection<Dividends, Dividend>(data),
|
||||
CreateCollection<Delistings, Delisting>(data),
|
||||
CreateCollection<SymbolChangedEvents, SymbolChangedEvent>(data))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -500,9 +500,9 @@ namespace QuantConnect.Data
|
||||
/// <summary>
|
||||
/// Dynamically produces a <see cref="Ticks"/> data dictionary using the provided data
|
||||
/// </summary>
|
||||
private static Ticks CreateTicksCollection(DateTime time, IEnumerable<BaseData> data)
|
||||
private static Ticks CreateTicksCollection(IEnumerable<BaseData> data)
|
||||
{
|
||||
var ticks = new Ticks(time);
|
||||
var ticks = new Ticks();
|
||||
foreach (var tick in data.OfType<Tick>())
|
||||
{
|
||||
List<Tick> listTicks;
|
||||
@@ -523,16 +523,11 @@ namespace QuantConnect.Data
|
||||
/// <param name="time">The current slice time</param>
|
||||
/// <param name="data">The data to create the collection</param>
|
||||
/// <returns>The data dictionary of <typeparamref name="TItem"/> containing all the data of that type in this slice</returns>
|
||||
private static T CreateCollection<T, TItem>(DateTime time, IEnumerable<BaseData> data)
|
||||
private static T CreateCollection<T, TItem>(IEnumerable<BaseData> data)
|
||||
where T : DataDictionary<TItem>, new()
|
||||
where TItem : BaseData
|
||||
{
|
||||
var collection = new T
|
||||
{
|
||||
#pragma warning disable 618 // This assignment is left here until the Time property is removed.
|
||||
Time = time
|
||||
#pragma warning restore 618
|
||||
};
|
||||
var collection = new T();
|
||||
foreach (var item in data.OfType<TItem>())
|
||||
{
|
||||
collection[item.Symbol] = item;
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace QuantConnect.Data
|
||||
{
|
||||
get
|
||||
{
|
||||
return Symbol.ID.SecurityType == SecurityType.Option ?
|
||||
return (Symbol.ID.SecurityType == SecurityType.Option || Symbol.ID.SecurityType == SecurityType.FutureOption) ?
|
||||
(Symbol.HasUnderlying ? Symbol.Underlying.Value : Symbol.Value) :
|
||||
Symbol.Value;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* 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");
|
||||
*
|
||||
* 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.
|
||||
@@ -20,7 +20,7 @@ using System.Linq;
|
||||
namespace QuantConnect.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides convenient methods for holding several <see cref="SubscriptionDataConfig"/>
|
||||
/// Provides convenient methods for holding several <see cref="SubscriptionDataConfig"/>
|
||||
/// </summary>
|
||||
public class SubscriptionDataConfigList : List<SubscriptionDataConfig>
|
||||
{
|
||||
@@ -56,9 +56,9 @@ namespace QuantConnect.Data
|
||||
/// <param name="normalizationMode"></param>
|
||||
public void SetDataNormalizationMode(DataNormalizationMode normalizationMode)
|
||||
{
|
||||
if (Symbol.SecurityType == SecurityType.Option && normalizationMode != DataNormalizationMode.Raw)
|
||||
if ((Symbol.SecurityType == SecurityType.Option || Symbol.SecurityType == SecurityType.FutureOption) && normalizationMode != DataNormalizationMode.Raw)
|
||||
{
|
||||
throw new ArgumentException("DataNormalizationMode.Raw must be used with options");
|
||||
throw new ArgumentException($"DataNormalizationMode.Raw must be used with SecurityType {Symbol.SecurityType}");
|
||||
}
|
||||
|
||||
foreach (var config in this)
|
||||
|
||||
@@ -212,6 +212,7 @@ namespace QuantConnect.Data
|
||||
{SecurityType.Forex, new List<TickType> {TickType.Quote}},
|
||||
{SecurityType.Equity, new List<TickType> {TickType.Trade, TickType.Quote}},
|
||||
{SecurityType.Option, new List<TickType> {TickType.Quote, TickType.Trade, TickType.OpenInterest}},
|
||||
{SecurityType.FutureOption, new List<TickType> {TickType.Quote, TickType.Trade, TickType.OpenInterest}},
|
||||
{SecurityType.Cfd, new List<TickType> {TickType.Quote}},
|
||||
{SecurityType.Future, new List<TickType> {TickType.Quote, TickType.Trade, TickType.OpenInterest}},
|
||||
{SecurityType.Commodity, new List<TickType> {TickType.Trade}},
|
||||
|
||||
@@ -167,6 +167,11 @@ namespace QuantConnect.Data.UniverseSelection
|
||||
sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlying, market, 0, 0, 0);
|
||||
break;
|
||||
|
||||
case SecurityType.FutureOption:
|
||||
var underlyingFuture = SecurityIdentifier.GenerateFuture(SecurityIdentifier.DefaultDate, ticker, market);
|
||||
sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlyingFuture, market, 0, 0, 0);
|
||||
break;
|
||||
|
||||
case SecurityType.Forex:
|
||||
sid = SecurityIdentifier.GenerateForex(ticker, market);
|
||||
break;
|
||||
|
||||
@@ -17,6 +17,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -49,6 +50,8 @@ using Timer = System.Timers.Timer;
|
||||
using static QuantConnect.StringExtensions;
|
||||
using Microsoft.IO;
|
||||
using QuantConnect.Data.Auxiliary;
|
||||
using QuantConnect.Securities.Future;
|
||||
using QuantConnect.Securities.FutureOption;
|
||||
using QuantConnect.Securities.Option;
|
||||
|
||||
namespace QuantConnect
|
||||
@@ -64,6 +67,18 @@ namespace QuantConnect
|
||||
private static readonly Dictionary<IntPtr, PythonActivator> PythonActivators
|
||||
= new Dictionary<IntPtr, PythonActivator>();
|
||||
|
||||
/// <summary>
|
||||
/// Safe multiplies a decimal by 100
|
||||
/// </summary>
|
||||
/// <param name="value">The decimal to multiply</param>
|
||||
/// <returns>The result, maxed out at decimal.MaxValue</returns>
|
||||
public static decimal SafeMultiply100(this decimal value)
|
||||
{
|
||||
const decimal max = decimal.MaxValue / 100m;
|
||||
if (value >= max) return decimal.MaxValue;
|
||||
return value * 100m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will return a memory stream using the <see cref="RecyclableMemoryStreamManager"/> instance.
|
||||
/// </summary>
|
||||
@@ -103,6 +118,10 @@ namespace QuantConnect
|
||||
{
|
||||
Guids.Add(guid);
|
||||
}
|
||||
|
||||
/// Unix epoch (1970-01-01 00:00:00.000000000Z)
|
||||
/// </summary>
|
||||
public static DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a list of ticks using protobuf
|
||||
@@ -640,6 +659,116 @@ namespace QuantConnect
|
||||
list.Add(element);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified element to the collection with the specified key. If an entry does not exist for the
|
||||
/// specified key then one will be created.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The key type</typeparam>
|
||||
/// <typeparam name="TElement">The collection element type</typeparam>
|
||||
/// <param name="dictionary">The source dictionary to be added to</param>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="element">The element to be added</param>
|
||||
public static ImmutableDictionary<TKey, ImmutableHashSet<TElement>> Add<TKey, TElement>(
|
||||
this ImmutableDictionary<TKey, ImmutableHashSet<TElement>> dictionary,
|
||||
TKey key,
|
||||
TElement element
|
||||
)
|
||||
{
|
||||
ImmutableHashSet<TElement> set;
|
||||
if (!dictionary.TryGetValue(key, out set))
|
||||
{
|
||||
set = ImmutableHashSet<TElement>.Empty.Add(element);
|
||||
return dictionary.Add(key, set);
|
||||
}
|
||||
|
||||
return dictionary.SetItem(key, set.Add(element));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified element to the collection with the specified key. If an entry does not exist for the
|
||||
/// specified key then one will be created.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The key type</typeparam>
|
||||
/// <typeparam name="TElement">The collection element type</typeparam>
|
||||
/// <param name="dictionary">The source dictionary to be added to</param>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="element">The element to be added</param>
|
||||
public static ImmutableSortedDictionary<TKey, ImmutableHashSet<TElement>> Add<TKey, TElement>(
|
||||
this ImmutableSortedDictionary<TKey, ImmutableHashSet<TElement>> dictionary,
|
||||
TKey key,
|
||||
TElement element
|
||||
)
|
||||
{
|
||||
ImmutableHashSet<TElement> set;
|
||||
if (!dictionary.TryGetValue(key, out set))
|
||||
{
|
||||
set = ImmutableHashSet<TElement>.Empty.Add(element);
|
||||
return dictionary.Add(key, set);
|
||||
}
|
||||
|
||||
return dictionary.SetItem(key, set.Add(element));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified element to the collection with the specified key. If the entry's count drops to
|
||||
/// zero, then the entry will be removed.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The key type</typeparam>
|
||||
/// <typeparam name="TElement">The collection element type</typeparam>
|
||||
/// <param name="dictionary">The source dictionary to be added to</param>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="element">The element to be added</param>
|
||||
public static ImmutableDictionary<TKey, ImmutableHashSet<TElement>> Remove<TKey, TElement>(
|
||||
this ImmutableDictionary<TKey, ImmutableHashSet<TElement>> dictionary,
|
||||
TKey key,
|
||||
TElement element
|
||||
)
|
||||
{
|
||||
ImmutableHashSet<TElement> set;
|
||||
if (!dictionary.TryGetValue(key, out set))
|
||||
{
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
set = set.Remove(element);
|
||||
if (set.Count == 0)
|
||||
{
|
||||
return dictionary.Remove(key);
|
||||
}
|
||||
|
||||
return dictionary.SetItem(key, set);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified element to the collection with the specified key. If the entry's count drops to
|
||||
/// zero, then the entry will be removed.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The key type</typeparam>
|
||||
/// <typeparam name="TElement">The collection element type</typeparam>
|
||||
/// <param name="dictionary">The source dictionary to be added to</param>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="element">The element to be added</param>
|
||||
public static ImmutableSortedDictionary<TKey, ImmutableHashSet<TElement>> Remove<TKey, TElement>(
|
||||
this ImmutableSortedDictionary<TKey, ImmutableHashSet<TElement>> dictionary,
|
||||
TKey key,
|
||||
TElement element
|
||||
)
|
||||
{
|
||||
ImmutableHashSet<TElement> set;
|
||||
if (!dictionary.TryGetValue(key, out set))
|
||||
{
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
set = set.Remove(element);
|
||||
if (set.Count == 0)
|
||||
{
|
||||
return dictionary.Remove(key);
|
||||
}
|
||||
|
||||
return dictionary.SetItem(key, set);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified Tick to the Ticks collection. If an entry does not exist for the specified key then one will be created.
|
||||
/// </summary>
|
||||
@@ -818,6 +947,26 @@ namespace QuantConnect
|
||||
return new decimal(lo, mid, 0, isNegative, (byte)(hasDecimals ? decimalPlaces : 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method for faster string to normalized decimal conversion, i.e. 20.0% should be parsed into 0.2
|
||||
/// </summary>
|
||||
/// <param name="str">String to be converted to positive decimal value</param>
|
||||
/// <remarks>
|
||||
/// Leading and trailing whitespace chars are ignored
|
||||
/// </remarks>
|
||||
/// <returns>Decimal value of the string</returns>
|
||||
public static decimal ToNormalizedDecimal(this string str)
|
||||
{
|
||||
var trimmed = str.Trim();
|
||||
var value = str.TrimEnd('%').ToDecimal();
|
||||
if (trimmed.EndsWith("%"))
|
||||
{
|
||||
value /= 100;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method for string to decimal conversion where string can represent a number with exponent xe-y
|
||||
/// </summary>
|
||||
@@ -1153,14 +1302,13 @@ namespace QuantConnect
|
||||
/// <param name="to">The time zone to be converted to</param>
|
||||
/// <param name="strict">True for strict conversion, this will throw during ambiguitities, false for lenient conversion</param>
|
||||
/// <returns>The time in terms of the to time zone</returns>
|
||||
public static DateTime ConvertTo(this DateTime time, DateTimeZone from, DateTimeZone to, bool strict = false)
|
||||
public static DateTime ConvertTo(this DateTime time, DateTimeZone from, DateTimeZone to)
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
return from.AtStrictly(LocalDateTime.FromDateTime(time)).WithZone(to).ToDateTimeUnspecified();
|
||||
}
|
||||
var instant = new Instant(time.Ticks - UnixEpoch.Ticks);
|
||||
var fromOffset = from.GetUtcOffset(instant).ToTimeSpan();
|
||||
var toOffset = to.GetUtcOffset(instant).ToTimeSpan();
|
||||
|
||||
return from.AtLeniently(LocalDateTime.FromDateTime(time)).WithZone(to).ToDateTimeUnspecified();
|
||||
return time - (fromOffset - toOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1170,11 +1318,12 @@ namespace QuantConnect
|
||||
/// <param name="to">The destinatio time zone</param>
|
||||
/// <param name="strict">True for strict conversion, this will throw during ambiguitities, false for lenient conversion</param>
|
||||
/// <returns>The time in terms of the <paramref name="to"/> time zone</returns>
|
||||
public static DateTime ConvertFromUtc(this DateTime time, DateTimeZone to, bool strict = false)
|
||||
public static DateTime ConvertFromUtc(this DateTime time, DateTimeZone to)
|
||||
{
|
||||
return time.ConvertTo(TimeZones.Utc, to, strict);
|
||||
return time + to.GetUtcOffset(new Instant(time.Ticks - UnixEpoch.Ticks)).ToTimeSpan();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified time from the <paramref name="from"/> time zone to <see cref="TimeZones.Utc"/>
|
||||
/// </summary>
|
||||
@@ -1182,14 +1331,9 @@ namespace QuantConnect
|
||||
/// <param name="from">The time zone the specified <paramref name="time"/> is in</param>
|
||||
/// <param name="strict">True for strict conversion, this will throw during ambiguitities, false for lenient conversion</param>
|
||||
/// <returns>The time in terms of the to time zone</returns>
|
||||
public static DateTime ConvertToUtc(this DateTime time, DateTimeZone from, bool strict = false)
|
||||
public static DateTime ConvertToUtc(this DateTime time, DateTimeZone from)
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
return from.AtStrictly(LocalDateTime.FromDateTime(time)).ToDateTimeUtc();
|
||||
}
|
||||
|
||||
return from.AtLeniently(LocalDateTime.FromDateTime(time)).ToDateTimeUtc();
|
||||
return time.Subtract(from.GetUtcOffset(Instant.FromTicksSinceUnixEpoch((time.Ticks - UnixEpoch.Ticks))).ToTimeSpan());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1512,6 +1656,7 @@ namespace QuantConnect
|
||||
case SecurityType.Base:
|
||||
case SecurityType.Equity:
|
||||
case SecurityType.Option:
|
||||
case SecurityType.FutureOption:
|
||||
case SecurityType.Commodity:
|
||||
case SecurityType.Forex:
|
||||
case SecurityType.Future:
|
||||
@@ -1559,6 +1704,8 @@ namespace QuantConnect
|
||||
return "equity";
|
||||
case SecurityType.Option:
|
||||
return "option";
|
||||
case SecurityType.FutureOption:
|
||||
return "futureoption";
|
||||
case SecurityType.Commodity:
|
||||
return "commodity";
|
||||
case SecurityType.Forex:
|
||||
@@ -2181,6 +2328,27 @@ namespace QuantConnect
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delisting date for the provided Symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol to lookup the last trading date</param>
|
||||
/// <param name="mapFile">Map file to use for delisting date. Defaults to SID.DefaultDate if no value is passed and is equity.</param>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetDelistingDate(this Symbol symbol, MapFile mapFile = null)
|
||||
{
|
||||
switch (symbol.ID.SecurityType)
|
||||
{
|
||||
case SecurityType.Future:
|
||||
return symbol.ID.Date;
|
||||
case SecurityType.Option:
|
||||
return OptionSymbol.GetLastDayOfTrading(symbol);
|
||||
case SecurityType.FutureOption:
|
||||
return FutureOptionSymbol.GetLastDayOfTrading(symbol);
|
||||
default:
|
||||
return mapFile?.DelistingDate ?? SecurityIdentifier.DefaultDate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scale data based on factor function
|
||||
/// </summary>
|
||||
@@ -2202,6 +2370,7 @@ namespace QuantConnect
|
||||
var securityType = data.Symbol.SecurityType;
|
||||
if (securityType != SecurityType.Equity &&
|
||||
securityType != SecurityType.Option &&
|
||||
securityType != SecurityType.FutureOption &&
|
||||
securityType != SecurityType.Future)
|
||||
{
|
||||
break;
|
||||
@@ -2357,7 +2526,7 @@ namespace QuantConnect
|
||||
/// <returns><see cref="OptionChainUniverse"/> for the given symbol</returns>
|
||||
public static OptionChainUniverse CreateOptionChain(this IAlgorithm algorithm, Symbol symbol, Func<OptionFilterUniverse, OptionFilterUniverse> filter, UniverseSettings universeSettings = null)
|
||||
{
|
||||
if (symbol.SecurityType != SecurityType.Option)
|
||||
if (symbol.SecurityType != SecurityType.Option && symbol.SecurityType != SecurityType.FutureOption)
|
||||
{
|
||||
throw new ArgumentException("CreateOptionChain requires an option symbol.");
|
||||
}
|
||||
@@ -2367,8 +2536,20 @@ namespace QuantConnect
|
||||
var underlying = symbol.Underlying;
|
||||
if (!symbol.IsCanonical())
|
||||
{
|
||||
// The underlying can be a non-equity Symbol, so we must explicitly
|
||||
// initialize the Symbol using the CreateOption(Symbol, ...) overload
|
||||
// to ensure that the underlying SecurityType is preserved and not
|
||||
// written as SecurityType.Equity.
|
||||
var alias = $"?{underlying.Value}";
|
||||
symbol = Symbol.Create(underlying.Value, SecurityType.Option, market, alias);
|
||||
|
||||
symbol = Symbol.CreateOption(
|
||||
underlying,
|
||||
market,
|
||||
default(OptionStyle),
|
||||
default(OptionRight),
|
||||
0m,
|
||||
SecurityIdentifier.DefaultDate,
|
||||
alias);
|
||||
}
|
||||
|
||||
// resolve defaults if not specified
|
||||
@@ -2401,5 +2582,32 @@ namespace QuantConnect
|
||||
|
||||
return new OptionChainUniverse(optionChain, settings, algorithm.LiveMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inverts the specified <paramref name="right"/>
|
||||
/// </summary>
|
||||
public static OptionRight Invert(this OptionRight right)
|
||||
{
|
||||
switch (right)
|
||||
{
|
||||
case OptionRight.Call: return OptionRight.Put;
|
||||
case OptionRight.Put: return OptionRight.Call;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(right), right, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two values using given operator
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="op">Comparison operator</param>
|
||||
/// <param name="arg1">The first value</param>
|
||||
/// <param name="arg2">The second value</param>
|
||||
/// <returns>Returns true if its left-hand operand meets the operator value to its right-hand operand, false otherwise</returns>
|
||||
public static bool Compare<T>(this ComparisonOperatorTypes op, T arg1, T arg2) where T : IComparable
|
||||
{
|
||||
return ComparisonOperator.Compare(op, arg1, arg2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +313,19 @@ namespace QuantConnect
|
||||
/// <summary>
|
||||
/// Cryptocurrency Security Type.
|
||||
/// </summary>
|
||||
Crypto
|
||||
Crypto,
|
||||
|
||||
/// <summary>
|
||||
/// Futures Options Security Type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Futures options function similar to equity options, but with a few key differences.
|
||||
/// Firstly, the contract unit of trade is 1x, rather than 100x. This means that each
|
||||
/// option represents the right to buy or sell 1 future contract at expiry/exercise.
|
||||
/// The contract multiplier for Futures Options plays a big part in determining the premium
|
||||
/// of the option, which can also differ from the underlying future's multiplier.
|
||||
/// </remarks>
|
||||
FutureOption
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -444,6 +456,27 @@ namespace QuantConnect
|
||||
Daily
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies what side a position is on, long/short
|
||||
/// </summary>
|
||||
public enum PositionSide
|
||||
{
|
||||
/// <summary>
|
||||
/// A short position, quantity less than zero
|
||||
/// </summary>
|
||||
Short = -1,
|
||||
|
||||
/// <summary>
|
||||
/// No position, quantity equals zero
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A long position, quantity greater than zero
|
||||
/// </summary>
|
||||
Long = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the different types of options
|
||||
/// </summary>
|
||||
|
||||
@@ -26,13 +26,11 @@ namespace QuantConnect.Interfaces
|
||||
/// <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="symbol">Symbol to lookup</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></returns>
|
||||
IEnumerable<Symbol> LookupSymbols(string lookupName, SecurityType securityType, bool includeExpired, string securityCurrency = null, string securityExchange = null);
|
||||
/// <returns>Enumerable of Symbols, that are associated with the provided Symbol</returns>
|
||||
IEnumerable<Symbol> LookupSymbols(Symbol symbol, bool includeExpired, string securityCurrency = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the time can be advanced or not.
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace QuantConnect.Orders.Fees
|
||||
{SecurityType.Forex, 0.000002m},
|
||||
// Commission plus clearing fee
|
||||
{SecurityType.Future, 0.4m + 0.1m},
|
||||
{SecurityType.FutureOption, 0.4m + 0.1m},
|
||||
{SecurityType.Option, 0.4m + 0.1m},
|
||||
{SecurityType.Cfd, 0m}
|
||||
};
|
||||
@@ -62,11 +63,12 @@ namespace QuantConnect.Orders.Fees
|
||||
|
||||
var market = security.Symbol.ID.Market;
|
||||
decimal feeRate;
|
||||
|
||||
|
||||
switch (security.Type)
|
||||
{
|
||||
case SecurityType.Option:
|
||||
case SecurityType.Future:
|
||||
case SecurityType.FutureOption:
|
||||
case SecurityType.Cfd:
|
||||
_feeRates.TryGetValue(security.Type, out feeRate);
|
||||
return new OrderFee(new CashAmount(feeRate * order.AbsoluteQuantity, Currencies.USD));
|
||||
@@ -125,9 +127,9 @@ namespace QuantConnect.Orders.Fees
|
||||
{
|
||||
tradeFee = maximumPerOrder;
|
||||
}
|
||||
|
||||
|
||||
return new OrderFee(new CashAmount(Math.Abs(tradeFee), equityFee.Currency));
|
||||
|
||||
|
||||
default:
|
||||
// unsupported security type
|
||||
throw new ArgumentException(Invariant($"Unsupported security type: {security.Type}"));
|
||||
@@ -156,4 +158,4 @@ namespace QuantConnect.Orders.Fees
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,8 +68,9 @@ namespace QuantConnect.Orders.Fees
|
||||
{
|
||||
var optionOrder = (OptionExerciseOrder)order;
|
||||
|
||||
if (optionOrder.Symbol.ID.SecurityType == SecurityType.Option &&
|
||||
optionOrder.Symbol.ID.Underlying.SecurityType == SecurityType.Equity)
|
||||
// For Futures Options, contracts are charged the standard commission at expiration of the contract.
|
||||
// Read more here: https://www1.interactivebrokers.com/en/index.php?f=14718#trading-related-fees
|
||||
if (optionOrder.Symbol.ID.SecurityType == SecurityType.Option)
|
||||
{
|
||||
return OrderFee.Zero;
|
||||
}
|
||||
@@ -102,6 +103,8 @@ namespace QuantConnect.Orders.Fees
|
||||
break;
|
||||
|
||||
case SecurityType.Future:
|
||||
case SecurityType.FutureOption:
|
||||
// The futures options fee model is exactly the same as futures' fees on IB.
|
||||
if (market == Market.Globex || market == Market.NYMEX
|
||||
|| market == Market.CBOT || market == Market.ICE
|
||||
|| market == Market.CBOE || market == Market.COMEX
|
||||
|
||||
@@ -251,7 +251,7 @@ namespace QuantConnect.Orders
|
||||
message += Invariant($" Message: {Message}");
|
||||
}
|
||||
|
||||
if (Symbol.SecurityType == SecurityType.Option)
|
||||
if (Symbol.SecurityType == SecurityType.Option || Symbol.SecurityType == SecurityType.FutureOption)
|
||||
{
|
||||
message += Invariant($" IsAssignment: {IsAssignment}");
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace QuantConnect.Orders.TimeInForces
|
||||
case SecurityType.Equity:
|
||||
case SecurityType.Option:
|
||||
case SecurityType.Future:
|
||||
case SecurityType.FutureOption:
|
||||
default:
|
||||
// expires at market close
|
||||
expired = time >= exchangeHours.GetNextMarketClose(orderTime, false);
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace QuantConnect.Orders.TimeInForces
|
||||
case SecurityType.Equity:
|
||||
case SecurityType.Option:
|
||||
case SecurityType.Future:
|
||||
case SecurityType.FutureOption:
|
||||
default:
|
||||
// expires at market close of expiry date
|
||||
expired = time >= exchangeHours.GetNextMarketClose(Expiry.Date, false);
|
||||
|
||||
@@ -143,6 +143,12 @@ namespace QuantConnect.Packets
|
||||
[JsonProperty(PropertyName = "dataResolutionPermissions")]
|
||||
public HashSet<Resolution> DataResolutionPermissions;
|
||||
|
||||
/// <summary>
|
||||
/// The cost associated with running this job
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "iCreditCost")]
|
||||
public uint CreditCost;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new default instance of the <see cref="Controls"/> class
|
||||
/// </summary>
|
||||
|
||||
@@ -153,6 +153,15 @@ namespace QuantConnect.Packets
|
||||
AlphaHeartbeat,
|
||||
|
||||
/// Used when debugging to send status updates
|
||||
DebuggingStatus
|
||||
DebuggingStatus,
|
||||
|
||||
/// Optimization Node Packet:
|
||||
OptimizationNode,
|
||||
|
||||
/// Optimization Estimate Packet:
|
||||
OptimizationEstimate,
|
||||
|
||||
/// Optimization work status update
|
||||
OptimizationStatus
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,12 @@ namespace QuantConnect.Parameters
|
||||
string parameterValue;
|
||||
if (!parameters.TryGetValue(parameterName, out parameterValue)) continue;
|
||||
|
||||
if (string.IsNullOrEmpty(parameterValue))
|
||||
{
|
||||
Log.Error($"ParameterAttribute.ApplyAttributes(): parameter '{parameterName}' provided value is null/empty, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
// if it's a read-only property with a parameter value we can't really do anything, bail
|
||||
if (propertyInfo != null && !propertyInfo.CanWrite)
|
||||
{
|
||||
|
||||
189
Common/Python/PandasArrowMemoryAllocator.cs
Normal file
189
Common/Python/PandasArrowMemoryAllocator.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Apache.Arrow.Memory;
|
||||
|
||||
namespace QuantConnect.Python
|
||||
{
|
||||
public class PandasArrowMemoryAllocator : NativeMemoryAllocator, IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly List<PandasMemoryOwner> _free = new List<PandasMemoryOwner>();
|
||||
private readonly List<PandasMemoryOwner> _used = new List<PandasMemoryOwner>();
|
||||
|
||||
public PandasArrowMemoryAllocator() : base()
|
||||
{
|
||||
}
|
||||
|
||||
protected override IMemoryOwner<byte> AllocateInternal(int length, out int bytesAllocated)
|
||||
{
|
||||
PandasMemoryOwner owner;
|
||||
var memoryResizeIndexes = new List<KeyValuePair<int, int>>();
|
||||
|
||||
for (var i = 0; i < _free.Count; i++)
|
||||
{
|
||||
var memory = _free[i];
|
||||
if (length > memory.Original.Memory.Length)
|
||||
{
|
||||
memoryResizeIndexes.Add(new KeyValuePair<int, int>(i, memory.Original.Memory.Length));
|
||||
continue;
|
||||
}
|
||||
|
||||
owner = memory;
|
||||
bytesAllocated = 0;
|
||||
|
||||
_free.Remove(owner);
|
||||
_used.Add(owner);
|
||||
owner.Reset();
|
||||
|
||||
if (length != memory.Original.Memory.Length)
|
||||
{
|
||||
owner.Slice(0, length);
|
||||
}
|
||||
|
||||
return owner;
|
||||
}
|
||||
|
||||
if (memoryResizeIndexes.Count != 0)
|
||||
{
|
||||
// Get the smallest resizable instance, and reallocate a larger buffer.
|
||||
var resizeIndex = memoryResizeIndexes.OrderBy(x => x.Value).First();
|
||||
var resizable = _free[resizeIndex.Key];
|
||||
|
||||
resizable.Resize(base.AllocateInternal(length, out bytesAllocated));
|
||||
|
||||
_used.Add(resizable);
|
||||
_free.RemoveAt(resizeIndex.Key);
|
||||
|
||||
return resizable;
|
||||
}
|
||||
|
||||
// New allocation, should only be called a few times when we start using the allocator
|
||||
owner = new PandasMemoryOwner(base.AllocateInternal(length, out bytesAllocated));
|
||||
_used.Add(owner);
|
||||
|
||||
return owner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees the underlying memory buffers so that they can be re-used
|
||||
/// </summary>
|
||||
public void Free()
|
||||
{
|
||||
foreach (var used in _used)
|
||||
{
|
||||
_free.Add(used);
|
||||
}
|
||||
|
||||
_used.Clear();
|
||||
}
|
||||
|
||||
private class PandasMemoryOwner : IMemoryOwner<byte>
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Original memory owner containing the full-length byte-array
|
||||
/// we initially allocated.
|
||||
/// </summary>
|
||||
public IMemoryOwner<byte> Original { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Slice of the original memory owner containing the contents of
|
||||
/// the buffer Arrow will use. We slice the original memory so
|
||||
/// that Arrow doesn't panic when it receives a slice with a length
|
||||
/// longer than it expects when serializing its internal buffer.
|
||||
/// </summary>
|
||||
public Memory<byte> Memory { get; private set; }
|
||||
|
||||
public PandasMemoryOwner(IMemoryOwner<byte> memory)
|
||||
{
|
||||
Original = memory;
|
||||
Memory = Original.Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a slice of the original MemoryOwner and stores the result in <see cref="Memory"/>
|
||||
/// </summary>
|
||||
/// <param name="start">Index start of the slice</param>
|
||||
/// <param name="length">Length of the slice</param>
|
||||
public void Slice(int start, int length)
|
||||
{
|
||||
Memory = Original.Memory.Slice(start, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the <see cref="Memory"/> slice to its initial value
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
Memory = null;
|
||||
Memory = Original.Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the instance to the new memory size
|
||||
/// </summary>
|
||||
/// <param name="newMemory"></param>
|
||||
public void Resize(IMemoryOwner<byte> newMemory)
|
||||
{
|
||||
Original.Dispose();
|
||||
Original = newMemory;
|
||||
Memory = null;
|
||||
Memory = Original.Memory;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
Original.Dispose();
|
||||
Memory = null;
|
||||
Original = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// no-op dispose because we want to re-use the MemoryOwner instance after we dispose of a RecordBatch.
|
||||
/// To dispose of the resources this class owns, use <see cref="Free"/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("PandasArrowMemoryAllocator has already been disposed");
|
||||
}
|
||||
foreach (var free in _free)
|
||||
{
|
||||
free.Free();
|
||||
}
|
||||
foreach (var used in _used)
|
||||
{
|
||||
used.Free();
|
||||
}
|
||||
|
||||
_free.Clear();
|
||||
_used.Clear();
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
1531
Common/Python/PandasConverter.cs
Normal file → Executable file
1531
Common/Python/PandasConverter.cs
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@@ -1,609 +0,0 @@
|
||||
/*
|
||||
* 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 Python.Runtime;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Util;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace QuantConnect.Python
|
||||
{
|
||||
/// <summary>
|
||||
/// Organizes a list of data to create pandas.DataFrames
|
||||
/// </summary>
|
||||
public class PandasData
|
||||
{
|
||||
private static dynamic _pandas;
|
||||
private readonly static HashSet<string> _baseDataProperties = typeof(BaseData).GetProperties().ToHashSet(x => x.Name.ToLowerInvariant());
|
||||
private readonly static ConcurrentDictionary<Type, List<MemberInfo>> _membersByType = new ConcurrentDictionary<Type, List<MemberInfo>>();
|
||||
|
||||
private readonly Symbol _symbol;
|
||||
private readonly Dictionary<string, Tuple<List<DateTime>, List<object>>> _series;
|
||||
|
||||
private readonly List<MemberInfo> _members;
|
||||
|
||||
/// <summary>
|
||||
/// Gets true if this is a custom data request, false for normal QC data
|
||||
/// </summary>
|
||||
public bool IsCustomData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Implied levels of a multi index pandas.Series (depends on the security type)
|
||||
/// </summary>
|
||||
public int Levels { get; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="PandasData"/>
|
||||
/// </summary>
|
||||
public PandasData(object data)
|
||||
{
|
||||
if (_pandas == null)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
// this python Remapper class will work as a proxy and adjust the
|
||||
// input to its methods using the provided 'mapper' callable object
|
||||
_pandas = PythonEngine.ModuleFromString("remapper",
|
||||
@"import pandas as pd
|
||||
from pandas.core.resample import Resampler, DatetimeIndexResampler, PeriodIndexResampler, TimedeltaIndexResampler
|
||||
from pandas.core.groupby.generic import DataFrameGroupBy, SeriesGroupBy
|
||||
from pandas.core.indexes.frozen import FrozenList as pdFrozenList
|
||||
from pandas.core.window import Expanding, EWM, Rolling, Window
|
||||
from pandas.core.computation.ops import UndefinedVariableError
|
||||
from inspect import getmembers, isfunction, isgenerator
|
||||
from functools import partial
|
||||
from sys import modules
|
||||
|
||||
from clr import AddReference
|
||||
AddReference(""QuantConnect.Common"")
|
||||
from QuantConnect import *
|
||||
|
||||
def mapper(key):
|
||||
'''Maps a Symbol object or a Symbol Ticker (string) to the string representation of
|
||||
Symbol SecurityIdentifier. If cannot map, returns the object
|
||||
'''
|
||||
keyType = type(key)
|
||||
if keyType is Symbol:
|
||||
return str(key.ID)
|
||||
if keyType is str:
|
||||
kvp = SymbolCache.TryGetSymbol(key, None)
|
||||
if kvp[0]:
|
||||
return str(kvp[1].ID)
|
||||
if keyType is list:
|
||||
return [mapper(x) for x in key]
|
||||
if keyType is tuple:
|
||||
return tuple([mapper(x) for x in key])
|
||||
if keyType is dict:
|
||||
return {k:mapper(v) for k,v in key.items()}
|
||||
return key
|
||||
|
||||
def try_wrap_as_index(obj):
|
||||
'''Tries to wrap object if it is one of pandas' index objects.'''
|
||||
|
||||
objType = type(obj)
|
||||
|
||||
if objType is pd.Index:
|
||||
return True, Index(obj)
|
||||
|
||||
if objType is pd.MultiIndex:
|
||||
result = object.__new__(MultiIndex)
|
||||
result._set_levels(obj.levels, copy=obj.copy, validate=False)
|
||||
result._set_codes(obj.codes, copy=obj.copy, validate=False)
|
||||
result._set_names(obj.names)
|
||||
result.sortorder = obj.sortorder
|
||||
return True, result
|
||||
|
||||
if objType is pdFrozenList:
|
||||
return True, FrozenList(obj)
|
||||
|
||||
return False, obj
|
||||
|
||||
def try_wrap_as_pandas(obj):
|
||||
'''Tries to wrap object if it is a pandas' object.'''
|
||||
|
||||
success, obj = try_wrap_as_index(obj)
|
||||
if success:
|
||||
return success, obj
|
||||
|
||||
objType = type(obj)
|
||||
|
||||
if objType is pd.DataFrame:
|
||||
return True, DataFrame(data=obj)
|
||||
|
||||
if objType is pd.Series:
|
||||
return True, Series(data=obj)
|
||||
|
||||
if objType is tuple:
|
||||
anySuccess = False
|
||||
results = list()
|
||||
for item in obj:
|
||||
success, result = try_wrap_as_pandas(item)
|
||||
anySuccess |= success
|
||||
results.append(result)
|
||||
if anySuccess:
|
||||
return True, tuple(results)
|
||||
|
||||
return False, obj
|
||||
|
||||
def try_wrap_resampler(obj, self):
|
||||
'''Tries to wrap object if it is a pandas' Resampler object.'''
|
||||
|
||||
if not isinstance(obj, Resampler):
|
||||
return False, obj
|
||||
|
||||
klass = CreateWrapperClass(type(obj))
|
||||
return True, klass(self, groupby=obj.groupby, kind=obj.kind, axis=obj.axis)
|
||||
|
||||
def wrap_function(f):
|
||||
'''Wraps function f with g.
|
||||
Function g converts the args/kwargs to use alternative index keys
|
||||
and the result of the f function call to the wrapper objects
|
||||
'''
|
||||
def g(*args, **kwargs):
|
||||
|
||||
if len(args) > 1:
|
||||
args = mapper(args)
|
||||
if len(kwargs) > 0:
|
||||
kwargs = mapper(kwargs)
|
||||
|
||||
try:
|
||||
result = f(*args, **kwargs)
|
||||
except UndefinedVariableError as e:
|
||||
# query/eval methods needs to look for a scope variable at a higher level
|
||||
# since the wrapper classes are children of pandas classes
|
||||
kwargs['level'] = kwargs.pop('level', 0) + 1
|
||||
result = f(*args, **kwargs)
|
||||
|
||||
success, result = try_wrap_as_pandas(result)
|
||||
if success:
|
||||
return result
|
||||
|
||||
success, result = try_wrap_resampler(result, args[0])
|
||||
if success:
|
||||
return result
|
||||
|
||||
if isgenerator(result):
|
||||
return ( (k, try_wrap_as_pandas(v)[1]) for k, v in result)
|
||||
|
||||
return result
|
||||
|
||||
g.__name__ = f.__name__
|
||||
return g
|
||||
|
||||
def wrap_special_function(name, cls, fcls, gcls = None):
|
||||
'''Replaces the special function of a given class by g that wraps fcls
|
||||
This is how pandas implements them.
|
||||
gcls represents an alternative for fcls
|
||||
if the keyword argument has 'win_type' key for the Rolling/Window case
|
||||
'''
|
||||
fcls = CreateWrapperClass(fcls)
|
||||
if gcls is not None:
|
||||
gcls = CreateWrapperClass(fcls)
|
||||
|
||||
def g(*args, **kwargs):
|
||||
if kwargs.get('win_type', None):
|
||||
return gcls(*args, **kwargs)
|
||||
return fcls(*args, **kwargs)
|
||||
g.__name__ = name
|
||||
setattr(cls, g.__name__, g)
|
||||
|
||||
def CreateWrapperClass(cls: type):
|
||||
'''Creates wrapper classes.
|
||||
Members of the original class are wrapped to allow alternative index look-up
|
||||
'''
|
||||
# Define a new class
|
||||
klass = type(f'{cls.__name__}', (cls,) + cls.__bases__, dict(cls.__dict__))
|
||||
|
||||
def g(self, name):
|
||||
'''Wrap '__getattribute__' to handle indices
|
||||
Only need to wrap columns, index and levels attributes
|
||||
'''
|
||||
attr = object.__getattribute__(self, name)
|
||||
if name in ['columns', 'index', 'levels']:
|
||||
_, attr = try_wrap_as_index(attr)
|
||||
return attr
|
||||
g.__name__ = '__getattribute__'
|
||||
g.__qualname__ = g.__name__
|
||||
setattr(klass, g.__name__, g)
|
||||
|
||||
def wrap_union(f):
|
||||
'''Wraps function f (union) with g.
|
||||
Special case: The union method from index objects needs to
|
||||
receive pandas' index objects to avoid infity recursion.
|
||||
Function g converts the args/kwargs objects to one of pandas index objects
|
||||
and the result of the f function call back to wrapper indexes objects
|
||||
'''
|
||||
def unwrap_index(obj):
|
||||
'''Tries to unwrap object if it is one of this module wrapper's index objects.'''
|
||||
objType = type(obj)
|
||||
|
||||
if objType is Index:
|
||||
return pd.Index(obj)
|
||||
|
||||
if objType is MultiIndex:
|
||||
result = object.__new__(pd.MultiIndex)
|
||||
result._set_levels(obj.levels, copy=obj.copy, validate=False)
|
||||
result._set_codes(obj.codes, copy=obj.copy, validate=False)
|
||||
result._set_names(obj.names)
|
||||
result.sortorder = obj.sortorder
|
||||
return result
|
||||
|
||||
if objType is FrozenList:
|
||||
return pdFrozenList(obj)
|
||||
|
||||
return obj
|
||||
|
||||
def g(*args, **kwargs):
|
||||
|
||||
args = tuple([unwrap_index(x) for x in args])
|
||||
result = f(*args, **kwargs)
|
||||
_, result = try_wrap_as_index(result)
|
||||
return result
|
||||
|
||||
g.__name__ = f.__name__
|
||||
return g
|
||||
|
||||
# We allow the wraopping of slot methods that are not inherited from object
|
||||
# It will include operation methods like __add__ and __contains__
|
||||
allow_list = set(x for x in dir(klass) if x.startswith('__')) - set(dir(object))
|
||||
|
||||
# Wrap class members of the newly created class
|
||||
for name, member in getmembers(klass):
|
||||
if name.startswith('_') and name not in allow_list:
|
||||
continue
|
||||
|
||||
if isfunction(member):
|
||||
if name == 'union':
|
||||
member = wrap_union(member)
|
||||
else:
|
||||
member = wrap_function(member)
|
||||
setattr(klass, name, member)
|
||||
|
||||
elif type(member) is property:
|
||||
if type(member.fget) is partial:
|
||||
func = CreateWrapperClass(member.fget.func)
|
||||
fget = partial(func, name)
|
||||
else:
|
||||
fget = wrap_function(member.fget)
|
||||
member = property(fget, member.fset, member.fdel, member.__doc__)
|
||||
setattr(klass, name, member)
|
||||
|
||||
return klass
|
||||
|
||||
FrozenList = CreateWrapperClass(pdFrozenList)
|
||||
Index = CreateWrapperClass(pd.Index)
|
||||
MultiIndex = CreateWrapperClass(pd.MultiIndex)
|
||||
Series = CreateWrapperClass(pd.Series)
|
||||
DataFrame = CreateWrapperClass(pd.DataFrame)
|
||||
|
||||
wrap_special_function('groupby', Series, SeriesGroupBy)
|
||||
wrap_special_function('groupby', DataFrame, DataFrameGroupBy)
|
||||
wrap_special_function('ewm', Series, EWM)
|
||||
wrap_special_function('ewm', DataFrame, EWM)
|
||||
wrap_special_function('expanding', Series, Expanding)
|
||||
wrap_special_function('expanding', DataFrame, Expanding)
|
||||
wrap_special_function('rolling', Series, Rolling, Window)
|
||||
wrap_special_function('rolling', DataFrame, Rolling, Window)
|
||||
|
||||
CreateSeries = pd.Series
|
||||
|
||||
setattr(modules[__name__], 'concat', wrap_function(pd.concat))");
|
||||
}
|
||||
}
|
||||
|
||||
var enumerable = data as IEnumerable;
|
||||
if (enumerable != null)
|
||||
{
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
data = item;
|
||||
}
|
||||
}
|
||||
|
||||
var type = data.GetType();
|
||||
IsCustomData = type.Namespace != typeof(Bar).Namespace;
|
||||
_members = new List<MemberInfo>();
|
||||
_symbol = ((IBaseData)data).Symbol;
|
||||
|
||||
if (_symbol.SecurityType == SecurityType.Future) Levels = 3;
|
||||
if (_symbol.SecurityType == SecurityType.Option) Levels = 5;
|
||||
|
||||
var columns = new HashSet<string>
|
||||
{
|
||||
"open", "high", "low", "close", "lastprice", "volume",
|
||||
"askopen", "askhigh", "asklow", "askclose", "askprice", "asksize", "quantity", "suspicious",
|
||||
"bidopen", "bidhigh", "bidlow", "bidclose", "bidprice", "bidsize", "exchange", "openinterest"
|
||||
};
|
||||
|
||||
if (IsCustomData)
|
||||
{
|
||||
var keys = (data as DynamicData)?.GetStorageDictionary().ToHashSet(x => x.Key);
|
||||
|
||||
// C# types that are not DynamicData type
|
||||
if (keys == null)
|
||||
{
|
||||
if (_membersByType.TryGetValue(type, out _members))
|
||||
{
|
||||
keys = _members.ToHashSet(x => x.Name.ToLowerInvariant());
|
||||
}
|
||||
else
|
||||
{
|
||||
var members = type.GetMembers().Where(x => x.MemberType == MemberTypes.Field || x.MemberType == MemberTypes.Property).ToList();
|
||||
|
||||
var duplicateKeys = members.GroupBy(x => x.Name.ToLowerInvariant()).Where(x => x.Count() > 1).Select(x => x.Key);
|
||||
foreach (var duplicateKey in duplicateKeys)
|
||||
{
|
||||
throw new ArgumentException($"PandasData.ctor(): More than one \'{duplicateKey}\' member was found in \'{type.FullName}\' class.");
|
||||
}
|
||||
|
||||
// If the custom data derives from a Market Data (e.g. Tick, TradeBar, QuoteBar), exclude its keys
|
||||
keys = members.ToHashSet(x => x.Name.ToLowerInvariant());
|
||||
keys.ExceptWith(_baseDataProperties);
|
||||
keys.ExceptWith(GetPropertiesNames(typeof(QuoteBar), type));
|
||||
keys.ExceptWith(GetPropertiesNames(typeof(TradeBar), type));
|
||||
keys.ExceptWith(GetPropertiesNames(typeof(Tick), type));
|
||||
keys.Add("value");
|
||||
|
||||
_members = members.Where(x => keys.Contains(x.Name.ToLowerInvariant())).ToList();
|
||||
_membersByType.TryAdd(type, _members);
|
||||
}
|
||||
}
|
||||
|
||||
columns.Add("value");
|
||||
columns.UnionWith(keys);
|
||||
}
|
||||
|
||||
_series = columns.ToDictionary(k => k, v => Tuple.Create(new List<DateTime>(), new List<object>()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds security data object to the end of the lists
|
||||
/// </summary>
|
||||
/// <param name="baseData"><see cref="IBaseData"/> object that contains security data</param>
|
||||
public void Add(object baseData)
|
||||
{
|
||||
foreach (var member in _members)
|
||||
{
|
||||
var key = member.Name.ToLowerInvariant();
|
||||
var endTime = ((IBaseData) baseData).EndTime;
|
||||
var propertyMember = member as PropertyInfo;
|
||||
if (propertyMember != null)
|
||||
{
|
||||
AddToSeries(key, endTime, propertyMember.GetValue(baseData));
|
||||
continue;
|
||||
}
|
||||
var fieldMember = member as FieldInfo;
|
||||
if (fieldMember != null)
|
||||
{
|
||||
AddToSeries(key, endTime, fieldMember.GetValue(baseData));
|
||||
}
|
||||
}
|
||||
|
||||
var storage = (baseData as DynamicData)?.GetStorageDictionary();
|
||||
if (storage != null)
|
||||
{
|
||||
var endTime = ((IBaseData) baseData).EndTime;
|
||||
var value = ((IBaseData) baseData).Value;
|
||||
AddToSeries("value", endTime, value);
|
||||
|
||||
foreach (var kvp in storage)
|
||||
{
|
||||
AddToSeries(kvp.Key, endTime, kvp.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var ticks = new List<Tick> { baseData as Tick };
|
||||
var tradeBar = baseData as TradeBar;
|
||||
var quoteBar = baseData as QuoteBar;
|
||||
Add(ticks, tradeBar, quoteBar);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Lean data objects to the end of the lists
|
||||
/// </summary>
|
||||
/// <param name="ticks">List of <see cref="Tick"/> object that contains tick information of the security</param>
|
||||
/// <param name="tradeBar"><see cref="TradeBar"/> object that contains trade bar information of the security</param>
|
||||
/// <param name="quoteBar"><see cref="QuoteBar"/> object that contains quote bar information of the security</param>
|
||||
public void Add(IEnumerable<Tick> ticks, TradeBar tradeBar, QuoteBar quoteBar)
|
||||
{
|
||||
if (tradeBar != null)
|
||||
{
|
||||
var time = tradeBar.EndTime;
|
||||
AddToSeries("open", time, tradeBar.Open);
|
||||
AddToSeries("high", time, tradeBar.High);
|
||||
AddToSeries("low", time, tradeBar.Low);
|
||||
AddToSeries("close", time, tradeBar.Close);
|
||||
AddToSeries("volume", time, tradeBar.Volume);
|
||||
}
|
||||
if (quoteBar != null)
|
||||
{
|
||||
var time = quoteBar.EndTime;
|
||||
if (tradeBar == null)
|
||||
{
|
||||
AddToSeries("open", time, quoteBar.Open);
|
||||
AddToSeries("high", time, quoteBar.High);
|
||||
AddToSeries("low", time, quoteBar.Low);
|
||||
AddToSeries("close", time, quoteBar.Close);
|
||||
}
|
||||
if (quoteBar.Ask != null)
|
||||
{
|
||||
AddToSeries("askopen", time, quoteBar.Ask.Open);
|
||||
AddToSeries("askhigh", time, quoteBar.Ask.High);
|
||||
AddToSeries("asklow", time, quoteBar.Ask.Low);
|
||||
AddToSeries("askclose", time, quoteBar.Ask.Close);
|
||||
AddToSeries("asksize", time, quoteBar.LastAskSize);
|
||||
}
|
||||
if (quoteBar.Bid != null)
|
||||
{
|
||||
AddToSeries("bidopen", time, quoteBar.Bid.Open);
|
||||
AddToSeries("bidhigh", time, quoteBar.Bid.High);
|
||||
AddToSeries("bidlow", time, quoteBar.Bid.Low);
|
||||
AddToSeries("bidclose", time, quoteBar.Bid.Close);
|
||||
AddToSeries("bidsize", time, quoteBar.LastBidSize);
|
||||
}
|
||||
}
|
||||
if (ticks != null)
|
||||
{
|
||||
foreach (var tick in ticks)
|
||||
{
|
||||
if (tick == null) continue;
|
||||
|
||||
var time = tick.EndTime;
|
||||
var column = tick.TickType == TickType.OpenInterest
|
||||
? "openinterest"
|
||||
: "lastprice";
|
||||
|
||||
if (tick.TickType == TickType.Quote)
|
||||
{
|
||||
AddToSeries("askprice", time, tick.AskPrice);
|
||||
AddToSeries("asksize", time, tick.AskSize);
|
||||
AddToSeries("bidprice", time, tick.BidPrice);
|
||||
AddToSeries("bidsize", time, tick.BidSize);
|
||||
}
|
||||
AddToSeries("exchange", time, tick.Exchange);
|
||||
AddToSeries("suspicious", time, tick.Suspicious);
|
||||
AddToSeries("quantity", time, tick.Quantity);
|
||||
AddToSeries(column, time, tick.LastPrice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the pandas.DataFrame of the current <see cref="PandasData"/> state
|
||||
/// </summary>
|
||||
/// <param name="levels">Number of levels of the multi index</param>
|
||||
/// <returns>pandas.DataFrame object</returns>
|
||||
public PyObject ToPandasDataFrame(int levels = 2)
|
||||
{
|
||||
var empty = new PyString(string.Empty);
|
||||
var list = Enumerable.Repeat<PyObject>(empty, 5).ToList();
|
||||
list[3] = _symbol.ID.ToString().ToPython();
|
||||
|
||||
if (_symbol.SecurityType == SecurityType.Future)
|
||||
{
|
||||
list[0] = _symbol.ID.Date.ToPython();
|
||||
list[3] = _symbol.ID.ToString().ToPython();
|
||||
}
|
||||
if (_symbol.SecurityType == SecurityType.Option)
|
||||
{
|
||||
list[0] = _symbol.ID.Date.ToPython();
|
||||
list[1] = _symbol.ID.StrikePrice.ToPython();
|
||||
list[2] = _symbol.ID.OptionRight.ToString().ToPython();
|
||||
list[3] = _symbol.ID.ToString().ToPython();
|
||||
}
|
||||
|
||||
// Create the index labels
|
||||
var names = "expiry,strike,type,symbol,time";
|
||||
if (levels == 2)
|
||||
{
|
||||
names = "symbol,time";
|
||||
list.RemoveRange(0, 3);
|
||||
}
|
||||
if (levels == 3)
|
||||
{
|
||||
names = "expiry,symbol,time";
|
||||
list.RemoveRange(1, 2);
|
||||
}
|
||||
|
||||
Func<object, bool> filter = x =>
|
||||
{
|
||||
var isNaNOrZero = x is double && ((double)x).IsNaNOrZero();
|
||||
var isNullOrWhiteSpace = x is string && string.IsNullOrWhiteSpace((string)x);
|
||||
var isFalse = x is bool && !(bool)x;
|
||||
return x == null || isNaNOrZero || isNullOrWhiteSpace || isFalse;
|
||||
};
|
||||
Func<DateTime, PyTuple> selector = x =>
|
||||
{
|
||||
list[list.Count - 1] = x.ToPython();
|
||||
return new PyTuple(list.ToArray());
|
||||
};
|
||||
// creating the pandas MultiIndex is expensive so we keep a cash
|
||||
var indexCache = new Dictionary<List<DateTime>, dynamic>(new ListComparer<DateTime>());
|
||||
using (Py.GIL())
|
||||
{
|
||||
// Returns a dictionary keyed by column name where values are pandas.Series objects
|
||||
var pyDict = new PyDict();
|
||||
var splitNames = names.Split(',');
|
||||
foreach (var kvp in _series)
|
||||
{
|
||||
var values = kvp.Value.Item2;
|
||||
if (values.All(filter)) continue;
|
||||
|
||||
dynamic index;
|
||||
if (!indexCache.TryGetValue(kvp.Value.Item1, out index))
|
||||
{
|
||||
var tuples = kvp.Value.Item1.Select(selector).ToArray();
|
||||
index = _pandas.MultiIndex.from_tuples(tuples, names: splitNames);
|
||||
indexCache[kvp.Value.Item1] = index;
|
||||
}
|
||||
|
||||
// Adds pandas.Series value keyed by the column name
|
||||
// CreateSeries will create an original pandas.Series
|
||||
// We are not using the wrapper class to avoid unnecessary and expensive
|
||||
// index wrapping operations when the Series are packed into a DataFrame
|
||||
pyDict.SetItem(kvp.Key, _pandas.CreateSeries(values, index));
|
||||
}
|
||||
_series.Clear();
|
||||
|
||||
// Create a DataFrame with wrapper class.
|
||||
// This is the starting point. The types of all DataFrame and Series that result from any operation will
|
||||
// be wrapper classes. Index and MultiIndex will be converted when required by index operations such as
|
||||
// stack, unstack, merge, union, etc.
|
||||
return _pandas.DataFrame(pyDict);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds data to dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to get</param>
|
||||
/// <param name="time"><see cref="DateTime"/> object to add to the value associated with the specific key</param>
|
||||
/// <param name="input"><see cref="Object"/> to add to the value associated with the specific key. Can be null.</param>
|
||||
private void AddToSeries(string key, DateTime time, object input)
|
||||
{
|
||||
Tuple<List<DateTime>, List<object>> value;
|
||||
if (_series.TryGetValue(key, out value))
|
||||
{
|
||||
value.Item1.Add(time);
|
||||
value.Item2.Add(input is decimal ? input.ConvertInvariant<double>() : input);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"PandasData.AddToSeries(): {key} key does not exist in series dictionary.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the lower-invariant name of properties of the type that a another type is assignable from
|
||||
/// </summary>
|
||||
/// <param name="baseType">The type that is assignable from</param>
|
||||
/// <param name="type">The type that is assignable by</param>
|
||||
/// <returns>List of string. Empty list if not assignable from</returns>
|
||||
private static IEnumerable<string> GetPropertiesNames(Type baseType, Type type)
|
||||
{
|
||||
return baseType.IsAssignableFrom(type)
|
||||
? baseType.GetProperties().Select(x => x.Name.ToLowerInvariant())
|
||||
: Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<CodeAnalysisRuleSet>..\QuantConnect.ruleset</CodeAnalysisRuleSet>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -106,6 +107,9 @@
|
||||
<CodeAnalysisRuleSet>..\QuantConnect.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Apache.Arrow, Version=2.0.0.0, Culture=neutral, PublicKeyToken=204f54e5a45c07df, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\gsalaz98.Unofficial.Apache.Arrow.2.0.1-SNAPSHOT\lib\net461\Apache.Arrow.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CloneExtensions, Version=1.3.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CloneExtensions.1.3.0\lib\net461\CloneExtensions.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -124,6 +128,11 @@
|
||||
<Reference Include="Microsoft.IO.RecyclableMemoryStream, Version=1.3.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.IO.RecyclableMemoryStream.1.3.5\lib\net46\Microsoft.IO.RecyclableMemoryStream.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<HintPath>..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -166,6 +175,10 @@
|
||||
<Reference Include="System.ServiceModel.Primitives, Version=4.7.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ServiceModel.Primitives.4.7.0\lib\net461\System.ServiceModel.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
@@ -235,6 +248,7 @@
|
||||
<Compile Include="Algorithm\Framework\Alphas\InsightScoreType.cs" />
|
||||
<Compile Include="Algorithm\Framework\Portfolio\PortfolioTargetCollection.cs" />
|
||||
<Compile Include="AlphaRuntimeStatistics.cs" />
|
||||
<Compile Include="Api\Account.cs" />
|
||||
<Compile Include="Api\AuthenticationResponse.cs" />
|
||||
<Compile Include="Api\Backtest.cs" />
|
||||
<Compile Include="Api\Compile.cs" />
|
||||
@@ -256,6 +270,8 @@
|
||||
<Compile Include="Benchmarks\FuncBenchmark.cs" />
|
||||
<Compile Include="Benchmarks\IBenchmark.cs" />
|
||||
<Compile Include="Benchmarks\SecurityBenchmark.cs" />
|
||||
<Compile Include="BinaryComparison.cs" />
|
||||
<Compile Include="BinaryComparisonExtensions.cs" />
|
||||
<Compile Include="Brokerages\AlpacaBrokerageModel.cs" />
|
||||
<Compile Include="Brokerages\AlphaStreamsBrokerageModel.cs" />
|
||||
<Compile Include="Brokerages\BinanceBrokerageModel.cs" />
|
||||
@@ -398,6 +414,7 @@
|
||||
<Compile Include="Packets\LeakyBucketControlParameters.cs" />
|
||||
<Compile Include="Packets\LiveResultParameters.cs" />
|
||||
<Compile Include="Python\BrokerageMessageHandlerPythonWrapper.cs" />
|
||||
<Compile Include="Python\PandasArrowMemoryAllocator.cs" />
|
||||
<Compile Include="Python\PythonConsolidator.cs" />
|
||||
<Compile Include="Python\MarginCallModelPythonWrapper.cs" />
|
||||
<Compile Include="Python\PythonInitializer.cs" />
|
||||
@@ -411,8 +428,41 @@
|
||||
<Compile Include="Securities\BuyingPowerModelExtensions.cs" />
|
||||
<Compile Include="Securities\BuyingPowerParameters.cs" />
|
||||
<Compile Include="Securities\ContractSecurityFilterUniverse.cs" />
|
||||
<Compile Include="Securities\FutureOption\Api\CMEOptionChainQuotes.cs" />
|
||||
<Compile Include="Securities\FutureOption\Api\CMEOptionsCategoryList.cs" />
|
||||
<Compile Include="Securities\FutureOption\Api\CMEProductSlateV2.cs" />
|
||||
<Compile Include="Securities\FutureOption\Api\CMEStrikePriceScalingFactors.cs" />
|
||||
<Compile Include="Securities\FutureOption\FutureOption.cs" />
|
||||
<Compile Include="Securities\FutureOption\FutureOptionSymbol.cs" />
|
||||
<Compile Include="Securities\FutureOption\FuturesOptionsMarginModel.cs" />
|
||||
<Compile Include="Securities\FutureOption\FuturesOptionsExpiryFunctions.cs" />
|
||||
<Compile Include="Securities\FutureOption\FuturesOptionsSymbolMappings.cs" />
|
||||
<Compile Include="Securities\Future\FutureSymbol.cs" />
|
||||
<Compile Include="Securities\GetMaximumOrderQuantityForDeltaBuyingPowerParameters.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\AbsoluteRiskOptionPositionCollectionEnumerator.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\ConstantOptionStrategyLegPredicateReferenceValue.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\DefaultOptionPositionCollectionEnumerator.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\DescendingByLegCountOptionStrategyDefinitionEnumerator.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\FunctionalOptionPositionCollectionEnumerator.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\IdentityOptionStrategyDefinitionEnumerator.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\IOptionPositionCollectionEnumerator.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\IOptionStrategyDefinitionEnumerator.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\IOptionStrategyLegPredicateReferenceValue.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\IOptionStrategyMatchObjectiveFunction.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyDefinition.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyDefinitionMatch.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyDefinitions.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyLegDefinitionMatch.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyLegPredicateReferenceValue.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionPosition.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionPositionCollection.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyLegDefinition.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyLegPredicate.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyMatch.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyMatcher.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyMatcherOptions.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\PredicateTargetValue.cs" />
|
||||
<Compile Include="Securities\Option\StrategyMatcher\UnmatchedPositionCountOptionStrategyMatchObjectiveFunction.cs" />
|
||||
<Compile Include="Securities\RegisteredSecurityDataTypesProvider.cs" />
|
||||
<Compile Include="Securities\ErrorCurrencyConverter.cs" />
|
||||
<Compile Include="Securities\GetMaximumOrderQuantityForTargetBuyingPowerParameters.cs" />
|
||||
@@ -479,7 +529,6 @@
|
||||
<Compile Include="Python\PythonActivator.cs" />
|
||||
<Compile Include="Python\PythonSlice.cs" />
|
||||
<Compile Include="Python\VolatilityModelPythonWrapper.cs" />
|
||||
<Compile Include="Python\PandasData.cs" />
|
||||
<Compile Include="Python\SecurityInitializerPythonWrapper.cs" />
|
||||
<Compile Include="Python\SlippageModelPythonWrapper.cs" />
|
||||
<Compile Include="Python\FillModelPythonWrapper.cs" />
|
||||
@@ -839,6 +888,7 @@
|
||||
<Compile Include="Util\BusyBlockingCollection.cs" />
|
||||
<Compile Include="Util\BusyCollection.cs" />
|
||||
<Compile Include="Util\CircularQueue.cs" />
|
||||
<Compile Include="Util\ComparisonOperator.cs" />
|
||||
<Compile Include="Util\Composer.cs" />
|
||||
<Compile Include="Util\ConcurrentSet.cs" />
|
||||
<Compile Include="Util\DateTimeJsonConverter.cs" />
|
||||
@@ -850,6 +900,7 @@
|
||||
<Compile Include="Util\FixedSizeHashQueue.cs" />
|
||||
<Compile Include="Util\FuncTextWriter.cs" />
|
||||
<Compile Include="Util\JsonRoundingConverter.cs" />
|
||||
<Compile Include="Util\ComparisonOperatorTypes.cs" />
|
||||
<Compile Include="Util\RateLimit\BusyWaitSleepStrategy.cs" />
|
||||
<Compile Include="Util\RateLimit\FixedIntervalRefillStrategy.cs" />
|
||||
<Compile Include="Util\RateLimit\IRefillStrategy.cs" />
|
||||
@@ -871,6 +922,7 @@
|
||||
<Compile Include="Util\SeriesJsonConverter.cs" />
|
||||
<Compile Include="Util\StreamReaderEnumerable.cs" />
|
||||
<Compile Include="Util\StreamReaderExtensions.cs" />
|
||||
<Compile Include="Util\StringDecimalJsonConverter.cs" />
|
||||
<Compile Include="Util\TypeChangeJsonConverter.cs" />
|
||||
<Compile Include="Util\LinqExtensions.cs" />
|
||||
<Compile Include="Util\MemoizingEnumerable.cs" />
|
||||
@@ -912,7 +964,6 @@
|
||||
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.3\analyzers\dotnet\cs\Microsoft.NetFramework.Analyzers.dll" />
|
||||
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.3\analyzers\dotnet\cs\Microsoft.NetFramework.CSharp.Analyzers.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
|
||||
<Import Project="..\packages\QuantConnect.pythonnet.1.0.5.30\build\QuantConnect.pythonnet.targets" Condition="Exists('..\packages\QuantConnect.pythonnet.1.0.5.30\build\QuantConnect.pythonnet.targets')" />
|
||||
@@ -934,4 +985,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -338,15 +338,15 @@ namespace QuantConnect.Securities.Future
|
||||
/// Gets the number of months between the contract month and the expiry date.
|
||||
/// </summary>
|
||||
/// <param name="underlying">The future symbol ticker</param>
|
||||
/// <param name="expiryDate">Expiry date to use to look up contract month delta. Only used for dairy, since we need to lookup its contract month in a pre-defined table.</param>
|
||||
/// <param name="futureExpiryDate">Expiry date to use to look up contract month delta. Only used for dairy, since we need to lookup its contract month in a pre-defined table.</param>
|
||||
/// <returns>The number of months between the contract month and the contract expiry</returns>
|
||||
public static int GetDeltaBetweenContractMonthAndContractExpiry(string underlying, DateTime? expiryDate = null)
|
||||
public static int GetDeltaBetweenContractMonthAndContractExpiry(string underlying, DateTime? futureExpiryDate = null)
|
||||
{
|
||||
int value;
|
||||
if (expiryDate != null && _dairyUnderlying.Contains(underlying))
|
||||
if (futureExpiryDate != null && _dairyUnderlying.Contains(underlying))
|
||||
{
|
||||
// Dairy can expire in the month following the contract month.
|
||||
var dairyReportDate = expiryDate.Value.Date.AddDays(1);
|
||||
var dairyReportDate = futureExpiryDate.Value.Date.AddDays(1);
|
||||
if (_reverseDairyReportDates.ContainsKey(dairyReportDate))
|
||||
{
|
||||
var contractMonth = _reverseDairyReportDates[dairyReportDate];
|
||||
|
||||
45
Common/Securities/FutureOption/Api/CMEOptionChainQuotes.cs
Normal file
45
Common/Securities/FutureOption/Api/CMEOptionChainQuotes.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Securities.FutureOption.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// CME Option Chain Quotes API call root response
|
||||
/// </summary>
|
||||
public class CMEOptionChainQuotes
|
||||
{
|
||||
/// <summary>
|
||||
/// The future options contracts with/without settlements
|
||||
/// </summary>
|
||||
[JsonProperty("optionContractQuotes")]
|
||||
public List<CMEOptionChainQuoteEntry> Quotes { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Option chain entry quotes, containing strike price
|
||||
/// </summary>
|
||||
public class CMEOptionChainQuoteEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Strike price of the future option quote entry
|
||||
/// </summary>
|
||||
[JsonProperty("strikePrice"), JsonConverter(typeof(StringDecimalJsonConverter), true)]
|
||||
public decimal StrikePrice { get; private set; }
|
||||
}
|
||||
}
|
||||
138
Common/Securities/FutureOption/Api/CMEOptionsCategoryList.cs
Normal file
138
Common/Securities/FutureOption/Api/CMEOptionsCategoryList.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Securities.FutureOption.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// CME options trades, dates, and expiration list API call root response
|
||||
/// </summary>
|
||||
/// <remarks>Returned as a List of this class</remarks>
|
||||
public class CMEOptionsTradeDatesAndExpiration
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the type of future option this entry is
|
||||
/// </summary>
|
||||
[JsonProperty("label")]
|
||||
public string Label { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the product
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Option type. "AME" for American, "EUR" for European.
|
||||
/// Note that there are other types such as weekly, but we
|
||||
/// only support American options for now.
|
||||
/// </summary>
|
||||
[JsonProperty("optionType")]
|
||||
public string OptionType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Product ID of the option
|
||||
/// </summary>
|
||||
[JsonProperty("productId")]
|
||||
public int ProductId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is Daily option
|
||||
/// </summary>
|
||||
[JsonProperty("daily")]
|
||||
public bool Daily { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ???
|
||||
/// </summary>
|
||||
[JsonProperty("sto")]
|
||||
public bool Sto { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is weekly option
|
||||
/// </summary>
|
||||
[JsonProperty("weekly")]
|
||||
public bool Weekly { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Expirations of the future option
|
||||
/// </summary>
|
||||
[JsonProperty("expirations")]
|
||||
public List<CMEOptionsExpiration> Expirations { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Future options Expiration entries. These are useful because we can derive the
|
||||
/// future chain from this data, since FOP and FUT share a 1-1 expiry code.
|
||||
/// </summary>
|
||||
public class CMEOptionsExpiration
|
||||
{
|
||||
/// <summary>
|
||||
/// Date of expiry
|
||||
/// </summary>
|
||||
[JsonProperty("label")]
|
||||
public string Label { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Product ID of the expiring asset (usually future option)
|
||||
/// </summary>
|
||||
[JsonProperty("productId")]
|
||||
public int ProductId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contract ID of the asset
|
||||
/// </summary>
|
||||
/// <remarks>Used to search settlements for the option chain</remarks>
|
||||
[JsonProperty("contractId")]
|
||||
public string ContractId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contract month code formatted as [FUTURE_MONTH_LETTER(1)][YEAR(1)]
|
||||
/// </summary>
|
||||
[JsonProperty("expiration")]
|
||||
public CMEOptionExpirationEntry Expiration { get; private set; }
|
||||
}
|
||||
|
||||
public class CMEOptionExpirationEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Month of expiry
|
||||
/// </summary>
|
||||
[JsonProperty("month")]
|
||||
public int Month { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Year of expiry
|
||||
/// </summary>
|
||||
[JsonProperty("year")]
|
||||
public int Year { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Expiration code (two letter)
|
||||
/// </summary>
|
||||
[JsonProperty("code")]
|
||||
public string Code { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Expiration code (three letter)
|
||||
/// </summary>
|
||||
[JsonProperty("twoDigitsCode")]
|
||||
public string TwoDigitsCode { get; private set; }
|
||||
}
|
||||
}
|
||||
98
Common/Securities/FutureOption/Api/CMEProductSlateV2.cs
Normal file
98
Common/Securities/FutureOption/Api/CMEProductSlateV2.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace QuantConnect.Securities.FutureOption.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Product slate API call root response
|
||||
/// </summary>
|
||||
public class CMEProductSlateV2ListResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Products matching the search criteria
|
||||
/// </summary>
|
||||
[JsonProperty("products")]
|
||||
public List<CMEProductSlateV2ListEntry> Products { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Product entry describing the asset matching the search criteria
|
||||
/// </summary>
|
||||
public class CMEProductSlateV2ListEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// CME ID for the asset
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the product (e.g. E-mini NASDAQ futures)
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Clearing code
|
||||
/// </summary>
|
||||
[JsonProperty("clearing")]
|
||||
public string Clearing { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// GLOBEX ticker
|
||||
/// </summary>
|
||||
[JsonProperty("globex")]
|
||||
public string Globex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is traded in the GLOBEX venue
|
||||
/// </summary>
|
||||
[JsonProperty("globexTraded")]
|
||||
public bool GlobexTraded { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Venues this asset trades on
|
||||
/// </summary>
|
||||
[JsonProperty("venues")]
|
||||
public string Venues { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Asset type this product is cleared as (i.e. "Futures", "Options")
|
||||
/// </summary>
|
||||
[JsonProperty("cleared")]
|
||||
public string Cleared { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Exchange the asset trades on (i.e. CME, NYMEX, COMEX, CBOT)
|
||||
/// </summary>
|
||||
[JsonProperty("exch")]
|
||||
public string Exchange { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Asset class group ID - describes group of asset class (e.g. equities, agriculture, etc.)
|
||||
/// </summary>
|
||||
[JsonProperty("groupId")]
|
||||
public int GroupId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// More specific ID describing product
|
||||
/// </summary>
|
||||
[JsonProperty("subGroupId")]
|
||||
public int subGroupId { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Securities.FutureOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a means to get the scaling factor for CME's quotes API
|
||||
/// </summary>
|
||||
public class CMEStrikePriceScalingFactors
|
||||
{
|
||||
/// <summary>
|
||||
/// CME's option chain quotes strike price scaling factor
|
||||
/// </summary>
|
||||
private static readonly IReadOnlyDictionary<string, decimal> _scalingFactors = new Dictionary<string, decimal>
|
||||
{
|
||||
{ "ES", 100m },
|
||||
{ "NQ", 100m },
|
||||
{ "HG", 100m },
|
||||
{ "SI", 100m },
|
||||
{ "CL", 100m },
|
||||
{ "NG", 1000m },
|
||||
{ "DC", 100m }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the option chain strike price scaling factor for the quote response from CME
|
||||
/// </summary>
|
||||
/// <param name="underlyingFuture">Underlying future Symbol to normalize</param>
|
||||
/// <returns>Scaling factor for the strike price</returns>
|
||||
public static decimal GetScaleFactor(Symbol underlyingFuture)
|
||||
{
|
||||
return _scalingFactors.ContainsKey(underlyingFuture.ID.Symbol)
|
||||
? _scalingFactors[underlyingFuture.ID.Symbol]
|
||||
: 1m;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Common/Securities/FutureOption/FutureOption.cs
Normal file
65
Common/Securities/FutureOption/FutureOption.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Orders.Fills;
|
||||
using QuantConnect.Orders.Slippage;
|
||||
using QuantConnect.Securities.Option;
|
||||
|
||||
namespace QuantConnect.Securities.FutureOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Futures Options security
|
||||
/// </summary>
|
||||
public class FutureOption : Option.Option
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for the future option security
|
||||
/// </summary>
|
||||
/// <param name="symbol">Symbol of the future option</param>
|
||||
/// <param name="exchangeHours">Exchange hours of the future option</param>
|
||||
/// <param name="quoteCurrency">Quoted currency of the future option</param>
|
||||
/// <param name="symbolProperties">Symbol properties of the future option</param>
|
||||
/// <param name="currencyConverter">Currency converter</param>
|
||||
/// <param name="registeredTypes">Provides all data types registered to the algorithm</param>
|
||||
/// <param name="securityCache">Cache of security objects</param>
|
||||
public FutureOption(Symbol symbol,
|
||||
SecurityExchangeHours exchangeHours,
|
||||
Cash quoteCurrency,
|
||||
OptionSymbolProperties symbolProperties,
|
||||
ICurrencyConverter currencyConverter,
|
||||
IRegisteredSecurityDataTypesProvider registeredTypes,
|
||||
SecurityCache securityCache)
|
||||
: base(symbol,
|
||||
quoteCurrency,
|
||||
symbolProperties,
|
||||
new OptionExchange(exchangeHours),
|
||||
securityCache,
|
||||
new OptionPortfolioModel(),
|
||||
new ImmediateFillModel(),
|
||||
new InteractiveBrokersFeeModel(),
|
||||
new ConstantSlippageModel(0),
|
||||
new ImmediateSettlementModel(),
|
||||
Securities.VolatilityModel.Null,
|
||||
new FuturesOptionsMarginModel(),
|
||||
new OptionDataFilter(),
|
||||
new SecurityPriceVariationModel(),
|
||||
currencyConverter,
|
||||
registeredTypes
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user