Compare commits
46 Commits
9864
...
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 | ||
|
|
fa94bcc46a | ||
|
|
2988f7ec3c | ||
|
|
b785877d33 | ||
|
|
5dbb0db2f9 | ||
|
|
a489436743 |
@@ -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"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "498372354"}
|
||||
{"OrderListHash", "-1575550889"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,12 +222,12 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Information Ratio", "0"},
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$85.33"},
|
||||
{"Total Fees", "$85.34"},
|
||||
{"Fitness Score", "0.5"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-43.937"},
|
||||
{"Return Over Maximum Drawdown", "-43.943"},
|
||||
{"Portfolio Turnover", "1.028"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
@@ -242,7 +242,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "415415696"}
|
||||
{"OrderListHash", "956597072"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +151,8 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-30.28"},
|
||||
{"Portfolio Turnover", "1.029"},
|
||||
{"Return Over Maximum Drawdown", "-30.158"},
|
||||
{"Portfolio Turnover", "1.033"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
@@ -166,7 +166,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "737971736"}
|
||||
{"OrderListHash", "1349023435"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,13 +90,13 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Information Ratio", "0"},
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$14.91"},
|
||||
{"Total Fees", "$14.92"},
|
||||
{"Fitness Score", "0.258"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-27.251"},
|
||||
{"Portfolio Turnover", "0.515"},
|
||||
{"Return Over Maximum Drawdown", "-27.228"},
|
||||
{"Portfolio Turnover", "0.516"},
|
||||
{"Total Insights Generated", "1"},
|
||||
{"Total Insights Closed", "1"},
|
||||
{"Total Insights Analysis Completed", "1"},
|
||||
@@ -110,7 +110,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "221046152"}
|
||||
{"OrderListHash", "1296183675"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-15.574"},
|
||||
{"Portfolio Turnover", "2.056"},
|
||||
{"Portfolio Turnover", "2.057"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
@@ -193,7 +193,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1311542155"}
|
||||
{"OrderListHash", "-1116140375"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Total Trades", "6"},
|
||||
{"Average Win", "6.02%"},
|
||||
{"Average Loss", "-2.40%"},
|
||||
{"Compounding Annual Return", "915.481%"},
|
||||
{"Compounding Annual Return", "915.480%"},
|
||||
{"Drawdown", "5.500%"},
|
||||
{"Expectancy", "1.338"},
|
||||
{"Net Profit", "11.400%"},
|
||||
@@ -117,7 +117,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Information Ratio", "9.507"},
|
||||
{"Tracking Error", "0.507"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$2651.00"},
|
||||
{"Total Fees", "$2651.01"},
|
||||
{"Fitness Score", "0.467"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
@@ -137,7 +137,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1241317053"}
|
||||
{"OrderListHash", "-89452746"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -254,13 +254,13 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Information Ratio", "0"},
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$48.56"},
|
||||
{"Total Fees", "$48.58"},
|
||||
{"Fitness Score", "0.5"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-141.917"},
|
||||
{"Portfolio Turnover", "2.001"},
|
||||
{"Return Over Maximum Drawdown", "-141.877"},
|
||||
{"Portfolio Turnover", "2.002"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
@@ -274,7 +274,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-22119963"}
|
||||
{"OrderListHash", "-263077697"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -105,8 +105,8 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-315.532"},
|
||||
{"Portfolio Turnover", "0.998"},
|
||||
{"Return Over Maximum Drawdown", "-315.48"},
|
||||
{"Portfolio Turnover", "0.999"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
@@ -120,7 +120,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1318619937"}
|
||||
{"OrderListHash", "1703396395"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -895,7 +897,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
/// </summary>
|
||||
public override void Disconnect()
|
||||
{
|
||||
_client.ClientSocket.eDisconnect();
|
||||
_client?.ClientSocket.eDisconnect();
|
||||
|
||||
if (_messageProcessingThread != null)
|
||||
{
|
||||
@@ -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.31\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.31\build\QuantConnect.IBAutomater.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\QuantConnect.IBAutomater.1.0.31\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.31\build\QuantConnect.IBAutomater.targets" Condition="Exists('..\packages\QuantConnect.IBAutomater.1.0.31\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.31" 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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user