Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3917e87614 | ||
|
|
809faa24de | ||
|
|
ad5aa263c0 | ||
|
|
a1bb907e03 | ||
|
|
71540f5015 | ||
|
|
a0055a3695 | ||
|
|
0b285df496 | ||
|
|
4d37096b3f | ||
|
|
7c42ea795f | ||
|
|
f2f1d06237 |
@@ -38,6 +38,18 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
AddEquity("IBM");
|
||||
AddCommand<BoolCommand>();
|
||||
AddCommand<VoidCommand>();
|
||||
|
||||
var potentialCommand = new VoidCommand
|
||||
{
|
||||
Target = new[] { "BAC" },
|
||||
Quantity = 10,
|
||||
Parameters = new() { { "tag", "Signal X" } }
|
||||
};
|
||||
var commandLink = Link(potentialCommand);
|
||||
Notify.Email("email@address", "Trade Command Event", $"Signal X trade\nFollow link to trigger: {commandLink}");
|
||||
|
||||
var commandLink2 = Link(new { Symbol = "SPY", Parameters = new Dictionary<string, int>() { { "Quantity", 10 } } });
|
||||
Notify.Email("email@address", "Untyped Command Event", $"Signal Y trade\nFollow link to trigger: {commandLink2}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.Securities.Option;
|
||||
using QuantConnect.Securities.Option.StrategyMatcher;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression algorithm exercising an equity Long Call Backspread option strategy and asserting it's being detected by Lean and works as expected
|
||||
/// </summary>
|
||||
public class OptionEquityCallBackspreadRegressionAlgorithm : OptionEquityBaseStrategyRegressionAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
|
||||
/// </summary>
|
||||
/// <param name="slice">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice slice)
|
||||
{
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
OptionChain chain;
|
||||
if (IsMarketOpen(_optionSymbol) && slice.OptionChains.TryGetValue(_optionSymbol, out chain))
|
||||
{
|
||||
var callContracts = chain
|
||||
.Where(contract => contract.Right == OptionRight.Call);
|
||||
var expiry = callContracts.Min(x => x.Expiry);
|
||||
callContracts = callContracts.Where(x => x.Expiry == expiry)
|
||||
.OrderBy(x => x.Strike)
|
||||
.ToList();
|
||||
|
||||
var strike = callContracts.Select(x => x.Strike).Distinct();
|
||||
if (strike.Count() < 2) return;
|
||||
|
||||
var lowStrikeCall = callContracts.First();
|
||||
var highStrikeCall = callContracts.First(contract => contract.Strike > lowStrikeCall.Strike && contract.Expiry == expiry);
|
||||
|
||||
var initialMargin = Portfolio.MarginRemaining;
|
||||
|
||||
var optionStrategy = OptionStrategies.CallBackspread(_optionSymbol, lowStrikeCall.Strike, highStrikeCall.Strike, expiry);
|
||||
Buy(optionStrategy, 5);
|
||||
var freeMarginPostTrade = Portfolio.MarginRemaining;
|
||||
|
||||
// It is a combination of bear call spread and long call
|
||||
AssertOptionStrategyIsPresent(OptionStrategyDefinitions.BearCallSpread.Name, 5);
|
||||
AssertOptionStrategyIsPresent(OptionStrategyDefinitions.NakedCall.Name, 5);
|
||||
|
||||
// Should only involve the bear call spread part
|
||||
var expectedMarginUsage = (highStrikeCall.Strike - lowStrikeCall.Strike) * Securities[highStrikeCall.Symbol].SymbolProperties.ContractMultiplier * 5;
|
||||
|
||||
if (expectedMarginUsage != Portfolio.TotalMarginUsed)
|
||||
{
|
||||
throw new Exception($"Unexpect margin used!:{Portfolio.TotalMarginUsed}");
|
||||
}
|
||||
|
||||
// we payed the ask and value using the assets price
|
||||
var priceLadderDifference = GetPriceSpreadDifference(lowStrikeCall.Symbol, highStrikeCall.Symbol);
|
||||
if (initialMargin != (freeMarginPostTrade + expectedMarginUsage + _paidFees - priceLadderDifference))
|
||||
{
|
||||
throw new Exception("Unexpect margin remaining!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of all timeslices of algorithm
|
||||
/// </summary>
|
||||
public override long DataPoints => 15023;
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of the algorithm history
|
||||
/// </summary>
|
||||
public override int AlgorithmHistoryDataPoints => 0;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public override Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Orders", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "0%"},
|
||||
{"Drawdown", "0%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Start Equity", "200000"},
|
||||
{"End Equity", "198565.25"},
|
||||
{"Net Profit", "0%"},
|
||||
{"Sharpe Ratio", "0"},
|
||||
{"Sortino 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", "0"},
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$9.75"},
|
||||
{"Estimated Strategy Capacity", "$47000.00"},
|
||||
{"Lowest Capacity Asset", "GOOCV W78ZERHAOVVQ|GOOCV VP83T1ZUHROL"},
|
||||
{"Portfolio Turnover", "11.81%"},
|
||||
{"OrderListHash", "6ece6c59826ea66fa7b0a1094a0021c7"}
|
||||
};
|
||||
}
|
||||
}
|
||||
128
Algorithm.CSharp/OptionEquityPutBackspreadRegressionAlgorithm.cs
Normal file
128
Algorithm.CSharp/OptionEquityPutBackspreadRegressionAlgorithm.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.Securities.Option;
|
||||
using QuantConnect.Securities.Option.StrategyMatcher;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression algorithm exercising an equity Long Put Backspread option strategy and asserting it's being detected by Lean and works as expected
|
||||
/// </summary>
|
||||
public class OptionEquityPutBackspreadRegressionAlgorithm : OptionEquityBaseStrategyRegressionAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
|
||||
/// </summary>
|
||||
/// <param name="slice">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice slice)
|
||||
{
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
OptionChain chain;
|
||||
if (IsMarketOpen(_optionSymbol) && slice.OptionChains.TryGetValue(_optionSymbol, out chain))
|
||||
{
|
||||
var putContracts = chain
|
||||
.Where(contract => contract.Right == OptionRight.Put);
|
||||
var expiry = putContracts.Min(x => x.Expiry);
|
||||
putContracts = putContracts.Where(x => x.Expiry == expiry)
|
||||
.OrderBy(x => x.Strike)
|
||||
.ToList();
|
||||
|
||||
var strike = putContracts.Select(x => x.Strike).Distinct();
|
||||
if (strike.Count() < 2) return;
|
||||
|
||||
var lowStrikePut = putContracts.First();
|
||||
var highStrikePut = putContracts.First(contract => contract.Strike > lowStrikePut.Strike && contract.Expiry == lowStrikePut.Expiry);
|
||||
|
||||
var initialMargin = Portfolio.MarginRemaining;
|
||||
|
||||
var optionStrategy = OptionStrategies.PutBackspread(_optionSymbol, highStrikePut.Strike, lowStrikePut.Strike, expiry);
|
||||
Buy(optionStrategy, 5);
|
||||
var freeMarginPostTrade = Portfolio.MarginRemaining;
|
||||
|
||||
// It is a combination of bull put spread and long put
|
||||
AssertOptionStrategyIsPresent(OptionStrategyDefinitions.BullPutSpread.Name, 5);
|
||||
AssertOptionStrategyIsPresent(OptionStrategyDefinitions.NakedPut.Name, 5);
|
||||
|
||||
// Should only involve the bull put spread part
|
||||
var expectedMarginUsage = (highStrikePut.Strike - lowStrikePut.Strike) * Securities[highStrikePut.Symbol].SymbolProperties.ContractMultiplier * 5;
|
||||
|
||||
if (expectedMarginUsage != Portfolio.TotalMarginUsed)
|
||||
{
|
||||
throw new Exception("Unexpect margin used!");
|
||||
}
|
||||
|
||||
// we payed the ask and value using the assets price
|
||||
var priceLadderDifference = GetPriceSpreadDifference(lowStrikePut.Symbol, highStrikePut.Symbol);
|
||||
if (initialMargin != (freeMarginPostTrade + expectedMarginUsage + _paidFees - priceLadderDifference))
|
||||
{
|
||||
throw new Exception("Unexpect margin remaining!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of all timeslices of algorithm
|
||||
/// </summary>
|
||||
public override long DataPoints => 15023;
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of the algorithm history
|
||||
/// </summary>
|
||||
public override int AlgorithmHistoryDataPoints => 0;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public override Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Orders", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "0%"},
|
||||
{"Drawdown", "0%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Start Equity", "200000"},
|
||||
{"End Equity", "199015.25"},
|
||||
{"Net Profit", "0%"},
|
||||
{"Sharpe Ratio", "0"},
|
||||
{"Sortino 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", "0"},
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$9.75"},
|
||||
{"Estimated Strategy Capacity", "$1100000.00"},
|
||||
{"Lowest Capacity Asset", "GOOCV 306CZL2DIL4G6|GOOCV VP83T1ZUHROL"},
|
||||
{"Portfolio Turnover", "9.15%"},
|
||||
{"OrderListHash", "1a51f04db9201f960dc04668b7f5d41d"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.Securities;
|
||||
using QuantConnect.Securities.Option;
|
||||
using QuantConnect.Securities.Option.StrategyMatcher;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression algorithm exercising an equity Short Call Backspread option strategy and asserting it's being detected by Lean and works as expected
|
||||
/// </summary>
|
||||
public class OptionEquityShortCallBackspreadRegressionAlgorithm : OptionEquityBaseStrategyRegressionAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
|
||||
/// </summary>
|
||||
/// <param name="slice">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice slice)
|
||||
{
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
OptionChain chain;
|
||||
if (IsMarketOpen(_optionSymbol) && slice.OptionChains.TryGetValue(_optionSymbol, out chain))
|
||||
{
|
||||
var callContracts = chain
|
||||
.Where(contract => contract.Right == OptionRight.Call);
|
||||
var expiry = callContracts.Min(x => x.Expiry);
|
||||
callContracts = callContracts.Where(x => x.Expiry == expiry)
|
||||
.OrderBy(x => x.Strike)
|
||||
.ToList();
|
||||
|
||||
var strike = callContracts.Select(x => x.Strike).Distinct();
|
||||
if (strike.Count() < 2) return;
|
||||
|
||||
var lowStrikeCall = callContracts.First();
|
||||
var highStrikeCall = callContracts.First(contract => contract.Strike > lowStrikeCall.Strike && contract.Expiry == lowStrikeCall.Expiry);
|
||||
|
||||
var initialMargin = Portfolio.MarginRemaining;
|
||||
|
||||
var optionStrategy = OptionStrategies.ShortCallBackspread(_optionSymbol, lowStrikeCall.Strike, highStrikeCall.Strike, expiry);
|
||||
Buy(optionStrategy, 5);
|
||||
var freeMarginPostTrade = Portfolio.MarginRemaining;
|
||||
|
||||
// It is a combination of bull call spread and naked call
|
||||
AssertOptionStrategyIsPresent(OptionStrategyDefinitions.BullCallSpread.Name, 5);
|
||||
AssertOptionStrategyIsPresent(OptionStrategyDefinitions.NakedCall.Name, 5);
|
||||
|
||||
// Should only involve the naked call part
|
||||
var security = Securities[highStrikeCall.Symbol];
|
||||
var expectedMarginUsage = security.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, -5)).Value;
|
||||
|
||||
if (expectedMarginUsage != Portfolio.TotalMarginUsed)
|
||||
{
|
||||
throw new Exception("Unexpect margin used!");
|
||||
}
|
||||
|
||||
// we payed the ask and value using the assets price
|
||||
var priceLadderDifference = GetPriceSpreadDifference(lowStrikeCall.Symbol, highStrikeCall.Symbol);
|
||||
if (initialMargin != (freeMarginPostTrade + expectedMarginUsage + _paidFees - priceLadderDifference))
|
||||
{
|
||||
throw new Exception("Unexpect margin remaining!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of all timeslices of algorithm
|
||||
/// </summary>
|
||||
public override long DataPoints => 15023;
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of the algorithm history
|
||||
/// </summary>
|
||||
public override int AlgorithmHistoryDataPoints => 0;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public override Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Orders", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "0%"},
|
||||
{"Drawdown", "0%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Start Equity", "200000"},
|
||||
{"End Equity", "199915.25"},
|
||||
{"Net Profit", "0%"},
|
||||
{"Sharpe Ratio", "0"},
|
||||
{"Sortino 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", "0"},
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$9.75"},
|
||||
{"Estimated Strategy Capacity", "$53000.00"},
|
||||
{"Lowest Capacity Asset", "GOOCV W78ZERHAOVVQ|GOOCV VP83T1ZUHROL"},
|
||||
{"Portfolio Turnover", "11.48%"},
|
||||
{"OrderListHash", "357f13ed9e71c4dd8bb8e51e339ba7c5"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.Securities;
|
||||
using QuantConnect.Securities.Option;
|
||||
using QuantConnect.Securities.Option.StrategyMatcher;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression algorithm exercising an equity Short Put Backspread option strategy and asserting it's being detected by Lean and works as expected
|
||||
/// </summary>
|
||||
public class OptionEquityShortPutBackspreadRegressionAlgorithm : OptionEquityBaseStrategyRegressionAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
|
||||
/// </summary>
|
||||
/// <param name="slice">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice slice)
|
||||
{
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
OptionChain chain;
|
||||
if (IsMarketOpen(_optionSymbol) && slice.OptionChains.TryGetValue(_optionSymbol, out chain))
|
||||
{
|
||||
var putContracts = chain
|
||||
.Where(contract => contract.Right == OptionRight.Put);
|
||||
var expiry = putContracts.Min(x => x.Expiry);
|
||||
putContracts = putContracts.Where(x => x.Expiry == expiry)
|
||||
.OrderBy(x => x.Strike)
|
||||
.ToList();
|
||||
|
||||
var strike = putContracts.Select(x => x.Strike).Distinct();
|
||||
if (strike.Count() < 2) return;
|
||||
|
||||
var lowStrikePut = putContracts.First();
|
||||
var highStrikePut = putContracts.First(contract => contract.Strike > lowStrikePut.Strike && contract.Expiry == lowStrikePut.Expiry);
|
||||
|
||||
var initialMargin = Portfolio.MarginRemaining;
|
||||
|
||||
var optionStrategy = OptionStrategies.ShortPutBackspread(_optionSymbol, highStrikePut.Strike, lowStrikePut.Strike, expiry);
|
||||
Buy(optionStrategy, 5);
|
||||
var freeMarginPostTrade = Portfolio.MarginRemaining;
|
||||
|
||||
// It is a combination of bear put spread and naked put
|
||||
AssertOptionStrategyIsPresent(OptionStrategyDefinitions.BearPutSpread.Name, 5);
|
||||
AssertOptionStrategyIsPresent(OptionStrategyDefinitions.NakedPut.Name, 5);
|
||||
|
||||
// Should only involve the naked put part
|
||||
var security = Securities[lowStrikePut.Symbol];
|
||||
var expectedMarginUsage = security.BuyingPowerModel.GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, -5)).Value;
|
||||
|
||||
if (expectedMarginUsage != Portfolio.TotalMarginUsed)
|
||||
{
|
||||
throw new Exception("Unexpect margin used!");
|
||||
}
|
||||
|
||||
// we payed the ask and value using the assets price
|
||||
var priceLadderDifference = GetPriceSpreadDifference(lowStrikePut.Symbol, highStrikePut.Symbol);
|
||||
if (initialMargin != (freeMarginPostTrade + expectedMarginUsage + _paidFees - priceLadderDifference))
|
||||
{
|
||||
throw new Exception("Unexpect margin remaining!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of all timeslices of algorithm
|
||||
/// </summary>
|
||||
public override long DataPoints => 15023;
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of the algorithm history
|
||||
/// </summary>
|
||||
public override int AlgorithmHistoryDataPoints => 0;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public override Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Orders", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "0%"},
|
||||
{"Drawdown", "0%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Start Equity", "200000"},
|
||||
{"End Equity", "199165.25"},
|
||||
{"Net Profit", "0%"},
|
||||
{"Sharpe Ratio", "0"},
|
||||
{"Sortino 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", "0"},
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$9.75"},
|
||||
{"Estimated Strategy Capacity", "$1200000.00"},
|
||||
{"Lowest Capacity Asset", "GOOCV 306CZL2DIL4G6|GOOCV VP83T1ZUHROL"},
|
||||
{"Portfolio Turnover", "8.84%"},
|
||||
{"OrderListHash", "7294da06231632975e97c57721d26442"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ class BoolCommand(Command):
|
||||
def run(self, algo: QCAlgorithm) -> bool | None:
|
||||
trade_ibm = self.my_custom_method()
|
||||
if trade_ibm:
|
||||
algo.debug(f"BoolCommand.run: {str(self)}")
|
||||
algo.buy("IBM", 1)
|
||||
return trade_ibm
|
||||
|
||||
@@ -68,6 +69,18 @@ class CallbackCommandRegressionAlgorithm(QCAlgorithm):
|
||||
if not threw_exception:
|
||||
raise ValueError('InvalidCommand did not throw!')
|
||||
|
||||
potential_command = VoidCommand()
|
||||
potential_command.target = [ "BAC" ]
|
||||
potential_command.quantity = 10
|
||||
potential_command.parameters = { "tag": "Signal X" }
|
||||
|
||||
command_link = self.link(potential_command)
|
||||
self.notify.email("email@address", "Trade Command Event", f"Signal X trade\nFollow link to trigger: {command_link}")
|
||||
|
||||
untyped_command_link = self.link({ "symbol": "SPY", "parameters": { "quantity": 10 } })
|
||||
self.notify.email("email@address", "Untyped Command Event", f"Signal Y trade\nFollow link to trigger: {untyped_command_link}")
|
||||
|
||||
def on_command(self, data):
|
||||
self.debug(f"on_command: {str(data)}")
|
||||
self.buy(data.symbol, data.parameters["quantity"])
|
||||
return True # False, None
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace QuantConnect.Algorithm
|
||||
var startTimeUtc = CreateBarCountHistoryRequests(symbols, _warmupBarCount.Value, Settings.WarmupResolution)
|
||||
.DefaultIfEmpty()
|
||||
.Min(request => request == null ? default : request.StartTimeUtc);
|
||||
if(startTimeUtc != default)
|
||||
if (startTimeUtc != default)
|
||||
{
|
||||
result = startTimeUtc.ConvertFromUtc(TimeZone);
|
||||
return true;
|
||||
@@ -353,7 +353,7 @@ namespace QuantConnect.Algorithm
|
||||
/// <returns>An enumerable of slice containing the requested historical data</returns>
|
||||
[DocumentationAttribute(HistoricalData)]
|
||||
public IEnumerable<DataDictionary<T>> History<T>(TimeSpan span, Resolution? resolution = null, bool? fillForward = null,
|
||||
bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode ? dataNormalizationMode = null,
|
||||
bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null, DataNormalizationMode? dataNormalizationMode = null,
|
||||
int? contractDepthOffset = null)
|
||||
where T : IBaseData
|
||||
{
|
||||
@@ -518,7 +518,7 @@ namespace QuantConnect.Algorithm
|
||||
{
|
||||
resolution = GetResolution(symbol, resolution, typeof(T));
|
||||
CheckPeriodBasedHistoryRequestResolution(new[] { symbol }, resolution, typeof(T));
|
||||
var requests = CreateBarCountHistoryRequests(new [] { symbol }, typeof(T), periods, resolution, fillForward, extendedMarketHours,
|
||||
var requests = CreateBarCountHistoryRequests(new[] { symbol }, typeof(T), periods, resolution, fillForward, extendedMarketHours,
|
||||
dataMappingMode, dataNormalizationMode, contractDepthOffset);
|
||||
return GetDataTypedHistory<T>(requests, symbol);
|
||||
}
|
||||
@@ -948,9 +948,19 @@ namespace QuantConnect.Algorithm
|
||||
Resolution? resolution = null, bool? fillForward = null, bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null,
|
||||
DataNormalizationMode? dataNormalizationMode = null, int? contractDepthOffset = null)
|
||||
{
|
||||
var arrayOfSymbols = symbols.ToArray();
|
||||
return CreateDateRangeHistoryRequests(symbols, Extensions.GetCustomDataTypeFromSymbols(arrayOfSymbols) ?? typeof(BaseData), startAlgoTz, endAlgoTz, resolution, fillForward, extendedMarketHours,
|
||||
dataMappingMode, dataNormalizationMode, contractDepthOffset);
|
||||
// Materialize the symbols to avoid multiple enumeration
|
||||
var symbolsArray = symbols.ToArray();
|
||||
return CreateDateRangeHistoryRequests(
|
||||
symbolsArray,
|
||||
Extensions.GetCustomDataTypeFromSymbols(symbolsArray),
|
||||
startAlgoTz,
|
||||
endAlgoTz,
|
||||
resolution,
|
||||
fillForward,
|
||||
extendedMarketHours,
|
||||
dataMappingMode,
|
||||
dataNormalizationMode,
|
||||
contractDepthOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -982,8 +992,18 @@ namespace QuantConnect.Algorithm
|
||||
bool? fillForward = null, bool? extendedMarketHours = null, DataMappingMode? dataMappingMode = null,
|
||||
DataNormalizationMode? dataNormalizationMode = null, int? contractDepthOffset = null)
|
||||
{
|
||||
return CreateBarCountHistoryRequests(symbols, typeof(BaseData), periods, resolution, fillForward, extendedMarketHours, dataMappingMode,
|
||||
dataNormalizationMode, contractDepthOffset);
|
||||
// Materialize the symbols to avoid multiple enumeration
|
||||
var symbolsArray = symbols.ToArray();
|
||||
return CreateBarCountHistoryRequests(
|
||||
symbolsArray,
|
||||
Extensions.GetCustomDataTypeFromSymbols(symbolsArray),
|
||||
periods,
|
||||
resolution,
|
||||
fillForward,
|
||||
extendedMarketHours,
|
||||
dataMappingMode,
|
||||
dataNormalizationMode,
|
||||
contractDepthOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -995,20 +1015,26 @@ namespace QuantConnect.Algorithm
|
||||
{
|
||||
return symbols.Where(HistoryRequestValid).SelectMany(symbol =>
|
||||
{
|
||||
var res = GetResolution(symbol, resolution, requestedType);
|
||||
var exchange = GetExchangeHours(symbol, requestedType);
|
||||
// Match or create configs for the symbol
|
||||
var configs = GetMatchingSubscriptions(symbol, requestedType, resolution).ToList();
|
||||
if (configs.Count == 0)
|
||||
{
|
||||
return Enumerable.Empty<HistoryRequest>();
|
||||
}
|
||||
|
||||
var config = configs.First();
|
||||
var start = _historyRequestFactory.GetStartTimeAlgoTz(symbol, periods, res, exchange, config.DataTimeZone, config.Type, extendedMarketHours);
|
||||
var end = Time;
|
||||
return configs.Select(config =>
|
||||
{
|
||||
// If no requested type was passed, use the config type to get the resolution (if not provided) and the exchange hours
|
||||
var type = requestedType ?? config.Type;
|
||||
var res = GetResolution(symbol, resolution, type);
|
||||
var exchange = GetExchangeHours(symbol, type);
|
||||
var start = _historyRequestFactory.GetStartTimeAlgoTz(symbol, periods, res, exchange, config.DataTimeZone,
|
||||
config.Type, extendedMarketHours);
|
||||
var end = Time;
|
||||
|
||||
return configs.Select(config => _historyRequestFactory.CreateHistoryRequest(config, start, end, exchange, res, fillForward,
|
||||
extendedMarketHours, dataMappingMode, dataNormalizationMode, contractDepthOffset));
|
||||
return _historyRequestFactory.CreateHistoryRequest(config, start, end, exchange, res, fillForward,
|
||||
extendedMarketHours, dataMappingMode, dataNormalizationMode, contractDepthOffset);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1045,7 +1071,7 @@ namespace QuantConnect.Algorithm
|
||||
|
||||
// if we have any user defined subscription configuration we use it, else we use internal ones if any
|
||||
List<SubscriptionDataConfig> configs = null;
|
||||
if(userConfig.Count != 0)
|
||||
if (userConfig.Count != 0)
|
||||
{
|
||||
configs = userConfig;
|
||||
}
|
||||
@@ -1078,13 +1104,14 @@ namespace QuantConnect.Algorithm
|
||||
}
|
||||
else
|
||||
{
|
||||
var entry = MarketHoursDatabase.GetEntry(symbol, new []{ type });
|
||||
resolution = GetResolution(symbol, resolution, type);
|
||||
|
||||
if (!LeanData.IsCommonLeanDataType(type) && !type.IsAbstract)
|
||||
// If type was specified and not a lean data type and also not abstract, we create a new subscription
|
||||
if (type != null && !LeanData.IsCommonLeanDataType(type) && !type.IsAbstract)
|
||||
{
|
||||
// we already know it's not a common lean data type
|
||||
var isCustom = Extensions.IsCustomDataType(symbol, type);
|
||||
var entry = MarketHoursDatabase.GetEntry(symbol, new[] { type });
|
||||
|
||||
// we were giving a specific type let's fetch it
|
||||
return new[] { new SubscriptionDataConfig(
|
||||
@@ -1105,19 +1132,26 @@ namespace QuantConnect.Algorithm
|
||||
return SubscriptionManager
|
||||
.LookupSubscriptionConfigDataTypes(symbol.SecurityType, resolution.Value, symbol.IsCanonical())
|
||||
.Where(tuple => SubscriptionDataConfigTypeFilter(type, tuple.Item1))
|
||||
.Select(x => new SubscriptionDataConfig(
|
||||
x.Item1,
|
||||
symbol,
|
||||
resolution.Value,
|
||||
entry.DataTimeZone,
|
||||
entry.ExchangeHours.TimeZone,
|
||||
UniverseSettings.FillForward,
|
||||
UniverseSettings.ExtendedMarketHours,
|
||||
true,
|
||||
false,
|
||||
x.Item2,
|
||||
true,
|
||||
UniverseSettings.GetUniverseNormalizationModeOrDefault(symbol.SecurityType)))
|
||||
.Select(x =>
|
||||
{
|
||||
var configType = x.Item1;
|
||||
// Use the config type to get an accurate mhdb entry
|
||||
var entry = MarketHoursDatabase.GetEntry(symbol, new[] { configType });
|
||||
|
||||
return new SubscriptionDataConfig(
|
||||
configType,
|
||||
symbol,
|
||||
resolution.Value,
|
||||
entry.DataTimeZone,
|
||||
entry.ExchangeHours.TimeZone,
|
||||
UniverseSettings.FillForward,
|
||||
UniverseSettings.ExtendedMarketHours,
|
||||
true,
|
||||
false,
|
||||
x.Item2,
|
||||
true,
|
||||
UniverseSettings.GetUniverseNormalizationModeOrDefault(symbol.SecurityType));
|
||||
})
|
||||
// lets make sure to respect the order of the data types, if used on a history request will affect outcome when using pushthrough for example
|
||||
.OrderByDescending(config => GetTickTypeOrder(config.SecurityType, config.TickType));
|
||||
}
|
||||
@@ -1130,6 +1164,11 @@ namespace QuantConnect.Algorithm
|
||||
/// This is useful to filter OpenInterest by default from history requests unless it's explicitly requested</remarks>
|
||||
private bool SubscriptionDataConfigTypeFilter(Type targetType, Type configType)
|
||||
{
|
||||
if (targetType == null)
|
||||
{
|
||||
return configType != typeof(OpenInterest);
|
||||
}
|
||||
|
||||
var targetIsGenericType = targetType == typeof(BaseData);
|
||||
|
||||
return targetType.IsAssignableFrom(configType) && (!targetIsGenericType || configType != typeof(OpenInterest));
|
||||
@@ -1188,7 +1227,7 @@ namespace QuantConnect.Algorithm
|
||||
}
|
||||
else
|
||||
{
|
||||
if(resolution != null)
|
||||
if (resolution != null)
|
||||
{
|
||||
return resolution.Value;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ using Python.Runtime;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Data.Fundamental;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Scheduling;
|
||||
using QuantConnect.Util;
|
||||
@@ -1638,6 +1639,23 @@ namespace QuantConnect.Algorithm
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an authenticated link to execute the given command instance
|
||||
/// </summary>
|
||||
/// <param name="command">The target command</param>
|
||||
/// <returns>The authenticated link</returns>
|
||||
public string Link(PyObject command)
|
||||
{
|
||||
using var _ = Py.GIL();
|
||||
|
||||
var strResult = CommandPythonWrapper.Serialize(command);
|
||||
using var pyType = command.GetPythonType();
|
||||
var wrappedType = Extensions.CreateType(pyType);
|
||||
|
||||
var payload = JsonConvert.DeserializeObject<Dictionary<string, object>>(strResult);
|
||||
return CommandLink(wrappedType.Name, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets indicator base type
|
||||
/// </summary>
|
||||
|
||||
@@ -3390,6 +3390,21 @@ namespace QuantConnect.Algorithm
|
||||
return new DataHistory<OptionUniverse>(optionChain, new Lazy<PyObject>(() => PandasConverter.GetDataFrame(optionChain)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an authenticated link to execute the given command instance
|
||||
/// </summary>
|
||||
/// <param name="command">The target command</param>
|
||||
/// <returns>The authenticated link</returns>
|
||||
public string Link(object command)
|
||||
{
|
||||
var typeName = command.GetType().Name;
|
||||
if (command is Command || typeName.Contains("AnonymousType", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return CommandLink(typeName, command);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a command type to be used
|
||||
/// </summary>
|
||||
@@ -3446,6 +3461,16 @@ namespace QuantConnect.Algorithm
|
||||
return true;
|
||||
}
|
||||
|
||||
private string CommandLink(string typeName, object command)
|
||||
{
|
||||
var payload = new Dictionary<string, dynamic> { { "projectId", ProjectId }, { "command", command } };
|
||||
if (_registeredCommands.ContainsKey(typeName))
|
||||
{
|
||||
payload["command[$type]"] = typeName;
|
||||
}
|
||||
return Api.Authentication.Link("live/commands/create", payload);
|
||||
}
|
||||
|
||||
private static Symbol GetCanonicalOptionSymbol(Symbol symbol)
|
||||
{
|
||||
// We got the underlying
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace QuantConnect.Algorithm.Selection
|
||||
/// <param name="configuration">The universe configuration to use</param>
|
||||
/// <param name="universeSettings">The universe settings to use</param>
|
||||
public OptionContractUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings)
|
||||
: base(configuration, universeSettings, Time.EndOfTimeTimeSpan,
|
||||
: base(AdjustUniverseConfiguration(configuration), universeSettings, Time.EndOfTimeTimeSpan,
|
||||
// Argument isn't used since we override 'SelectSymbols'
|
||||
Enumerable.Empty<Symbol>())
|
||||
{
|
||||
@@ -95,5 +95,13 @@ namespace QuantConnect.Algorithm.Selection
|
||||
|
||||
return new Symbol(sid, ticker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure the configuration of the universe is what we want
|
||||
/// </summary>
|
||||
private static SubscriptionDataConfig AdjustUniverseConfiguration(SubscriptionDataConfig input)
|
||||
{
|
||||
return new SubscriptionDataConfig(input, fillForward: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
126
Common/Api/Authentication.cs
Normal file
126
Common/Api/Authentication.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.Web;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace QuantConnect.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for api authentication and interaction
|
||||
/// </summary>
|
||||
public static class Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate a secure hash for the authorization headers.
|
||||
/// </summary>
|
||||
/// <returns>Time based hash of user token and timestamp.</returns>
|
||||
public static string Hash(int timestamp)
|
||||
{
|
||||
return Hash(timestamp, Globals.UserToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a secure hash for the authorization headers.
|
||||
/// </summary>
|
||||
/// <returns>Time based hash of user token and timestamp.</returns>
|
||||
public static string Hash(int timestamp, string token)
|
||||
{
|
||||
// Create a new hash using current UTC timestamp.
|
||||
// Hash must be generated fresh each time.
|
||||
var data = $"{token}:{timestamp.ToStringInvariant()}";
|
||||
return data.ToSHA256();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an authenticated link for the target endpoint using the optional given payload
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The endpoint</param>
|
||||
/// <param name="payload">The payload</param>
|
||||
/// <returns>The authenticated link to trigger the request</returns>
|
||||
public static string Link(string endpoint, IEnumerable<KeyValuePair<string, object>> payload = null)
|
||||
{
|
||||
var queryString = HttpUtility.ParseQueryString(string.Empty);
|
||||
|
||||
var timestamp = (int)Time.TimeStamp();
|
||||
queryString.Add("authorization", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Globals.UserId}:{Hash(timestamp)}")));
|
||||
queryString.Add("timestamp", timestamp.ToStringInvariant());
|
||||
|
||||
PopulateQueryString(queryString, payload);
|
||||
|
||||
return $"{Globals.Api}{endpoint.RemoveFromStart("/").RemoveFromEnd("/")}?{queryString}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to populate a query string with the given payload
|
||||
/// </summary>
|
||||
/// <remarks>Useful for testing purposes</remarks>
|
||||
public static void PopulateQueryString(NameValueCollection queryString, IEnumerable<KeyValuePair<string, object>> payload = null)
|
||||
{
|
||||
if (payload != null)
|
||||
{
|
||||
foreach (var kv in payload)
|
||||
{
|
||||
AddToQuery(queryString, kv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will add the given key value pairs to the query encoded as xform data
|
||||
/// </summary>
|
||||
private static void AddToQuery(NameValueCollection queryString, KeyValuePair<string, object> keyValuePairs)
|
||||
{
|
||||
var objectType = keyValuePairs.Value.GetType();
|
||||
if (objectType.IsValueType || objectType == typeof(string))
|
||||
{
|
||||
// straight
|
||||
queryString.Add(keyValuePairs.Key, keyValuePairs.Value.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
// let's take advantage of json to load the properties we should include
|
||||
var serialized = JsonConvert.SerializeObject(keyValuePairs.Value);
|
||||
foreach (var jObject in JObject.Parse(serialized))
|
||||
{
|
||||
var subKey = $"{keyValuePairs.Key}[{jObject.Key}]";
|
||||
if (jObject.Value is JObject)
|
||||
{
|
||||
// inception
|
||||
AddToQuery(queryString, new KeyValuePair<string, object>(subKey, jObject.Value.ToObject<object>()));
|
||||
}
|
||||
else if(jObject.Value is JArray jArray)
|
||||
{
|
||||
var counter = 0;
|
||||
foreach (var value in jArray.ToObject<List<object>>())
|
||||
{
|
||||
queryString.Add($"{subKey}[{counter++}]", value.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queryString.Add(subKey, jObject.Value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@ namespace QuantConnect.Brokerages
|
||||
/// </summary>
|
||||
private readonly Dictionary<SecurityType, HashSet<OrderType>> _supportOrderTypeBySecurityType = new()
|
||||
{
|
||||
{ SecurityType.Equity, new HashSet<OrderType> { OrderType.Market, OrderType.Limit, OrderType.StopMarket, OrderType.StopLimit, OrderType.TrailingStop } },
|
||||
{ SecurityType.Equity, new HashSet<OrderType> { OrderType.Market, OrderType.Limit, OrderType.StopMarket, OrderType.StopLimit,
|
||||
OrderType.TrailingStop, OrderType.MarketOnOpen, OrderType.MarketOnClose } },
|
||||
// Market and limit order types see https://docs.alpaca.markets/docs/options-trading-overview
|
||||
{ SecurityType.Option, new HashSet<OrderType> { OrderType.Market, OrderType.Limit } },
|
||||
{ SecurityType.Crypto, new HashSet<OrderType> { OrderType.Market, OrderType.Limit, OrderType.StopLimit }}
|
||||
|
||||
@@ -50,6 +50,8 @@ namespace QuantConnect.Brokerages
|
||||
OrderType.StopLimit,
|
||||
OrderType.ComboMarket,
|
||||
OrderType.ComboLimit,
|
||||
OrderType.MarketOnOpen,
|
||||
OrderType.MarketOnClose
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace QuantConnect.Commands
|
||||
/// </summary>
|
||||
public abstract class BaseCommandHandler : ICommandHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Command json settings
|
||||
/// </summary>
|
||||
protected static readonly JsonSerializerSettings Settings = new() { TypeNameHandling = TypeNameHandling.All };
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,13 +14,15 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Dynamic;
|
||||
using Newtonsoft.Json;
|
||||
using QuantConnect.Data;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using QuantConnect.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace QuantConnect.Commands
|
||||
{
|
||||
@@ -34,12 +36,17 @@ namespace QuantConnect.Commands
|
||||
|
||||
private readonly Dictionary<string, object> _storage = new(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Useful to string representation in python
|
||||
/// </summary>
|
||||
protected string PayloadData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the metaObject required for Dynamism.
|
||||
/// </summary>
|
||||
public sealed override DynamicMetaObject GetMetaObject(Expression parameter)
|
||||
{
|
||||
return new GetSetPropertyDynamicMetaObject(parameter, this, SetPropertyMethodInfo, GetPropertyMethodInfo);
|
||||
return new SerializableDynamicMetaObject(parameter, this, SetPropertyMethodInfo, GetPropertyMethodInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,6 +80,20 @@ namespace QuantConnect.Commands
|
||||
{
|
||||
if (!_storage.TryGetValue(name, out var value))
|
||||
{
|
||||
var type = GetType();
|
||||
if (type != typeof(Command))
|
||||
{
|
||||
var propertyInfo = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
return propertyInfo.GetValue(this, null);
|
||||
}
|
||||
var fieldInfo = type.GetField(name, BindingFlags.Public | BindingFlags.Instance);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
return fieldInfo.GetValue(this);
|
||||
}
|
||||
}
|
||||
throw new KeyNotFoundException($"Property with name \'{name}\' does not exist. Properties: {string.Join(", ", _storage.Keys)}");
|
||||
}
|
||||
return value;
|
||||
@@ -87,5 +108,36 @@ namespace QuantConnect.Commands
|
||||
{
|
||||
throw new NotImplementedException($"Please implement the 'def run(algorithm) -> bool | None:' method");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The string representation of this command
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(PayloadData))
|
||||
{
|
||||
return PayloadData;
|
||||
}
|
||||
return JsonConvert.SerializeObject(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class so we can serialize a command
|
||||
/// </summary>
|
||||
private class SerializableDynamicMetaObject : GetSetPropertyDynamicMetaObject
|
||||
{
|
||||
private readonly Command _object;
|
||||
public SerializableDynamicMetaObject(Expression expression, object value, MethodInfo setPropertyMethodInfo, MethodInfo getPropertyMethodInfo)
|
||||
: base(expression, value, setPropertyMethodInfo, getPropertyMethodInfo)
|
||||
{
|
||||
_object = (Command)value;
|
||||
}
|
||||
public override IEnumerable<string> GetDynamicMemberNames()
|
||||
{
|
||||
return _object._storage.Keys.Concat(_object.GetType()
|
||||
.GetMembers(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(x => x.MemberType == MemberTypes.Field || x.MemberType == MemberTypes.Property).Select(x => x.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace QuantConnect.Data
|
||||
var isUniverseData = path.Contains("coarse", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.Contains("universe", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (e.Succeded)
|
||||
if (e.Succeeded)
|
||||
{
|
||||
WriteLineToFile(_succeededDataRequestsWriter, path, _succeededDataRequestsFileName);
|
||||
Interlocked.Increment(ref _succeededDataRequestsCount);
|
||||
@@ -105,7 +105,7 @@ namespace QuantConnect.Data
|
||||
|
||||
if (Logging.Log.DebuggingEnabled)
|
||||
{
|
||||
Logging.Log.Debug($"DataMonitor.OnNewDataRequest(): Data from {path} could not be fetched");
|
||||
Logging.Log.Debug($"DataMonitor.OnNewDataRequest(): Data from {path} could not be fetched, error: {e.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ namespace QuantConnect
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base api url address to use
|
||||
/// </summary>
|
||||
public static string Api { get; } = "https://www.quantconnect.com/api/v2/";
|
||||
|
||||
/// <summary>
|
||||
/// The user Id
|
||||
/// </summary>
|
||||
|
||||
@@ -30,17 +30,24 @@ namespace QuantConnect.Interfaces
|
||||
/// <summary>
|
||||
/// Whether the data was fetched successfully
|
||||
/// </summary>
|
||||
public bool Succeded { get; }
|
||||
public bool Succeeded { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Any error message that occurred during the fetch
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataProviderNewDataRequestEventArgs"/> class
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the fetched data</param>
|
||||
/// <param name="succeded">Whether the data was fetched successfully</param>
|
||||
public DataProviderNewDataRequestEventArgs(string path, bool succeded)
|
||||
/// <param name="succeeded">Whether the data was fetched successfully</param>
|
||||
/// <param name="errorMessage">Any error message that occured during the fetch</param>
|
||||
public DataProviderNewDataRequestEventArgs(string path, bool succeeded, string errorMessage)
|
||||
{
|
||||
Path = path;
|
||||
Succeded = succeded;
|
||||
Succeeded = succeeded;
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Util;
|
||||
using static QuantConnect.StringExtensions;
|
||||
|
||||
namespace QuantConnect
|
||||
{
|
||||
@@ -36,22 +35,6 @@ namespace QuantConnect
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Algo cancellation controls - cancellation token for algorithm thread.
|
||||
/// </summary>
|
||||
public CancellationToken CancellationToken
|
||||
{
|
||||
get { return CancellationTokenSource.Token; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this task isolator is cancelled, and exit the analysis
|
||||
/// </summary>
|
||||
public bool IsCancellationRequested
|
||||
{
|
||||
get { return CancellationTokenSource.IsCancellationRequested; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Isolator"/> class
|
||||
/// </summary>
|
||||
@@ -117,7 +100,7 @@ namespace QuantConnect
|
||||
memoryCap *= 1024 * 1024;
|
||||
var spikeLimit = memoryCap*2;
|
||||
|
||||
while (!task.IsCompleted && utcNow < end)
|
||||
while (!task.IsCompleted && !CancellationTokenSource.IsCancellationRequested && utcNow < end)
|
||||
{
|
||||
// if over 80% allocation force GC then sample
|
||||
var sample = Convert.ToDouble(GC.GetTotalMemory(memoryUsed > memoryCap * 0.8));
|
||||
@@ -166,15 +149,26 @@ namespace QuantConnect
|
||||
utcNow = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if (task.IsCompleted == false && string.IsNullOrEmpty(message))
|
||||
if (task.IsCompleted == false)
|
||||
{
|
||||
message = Messages.Isolator.MemoryUsageMonitorTaskTimedOut(timeSpan);
|
||||
Log.Trace($"Isolator.ExecuteWithTimeLimit(): {message}");
|
||||
if (CancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
Log.Trace($"Isolator.ExecuteWithTimeLimit(): Operation was canceled");
|
||||
throw new OperationCanceledException("Operation was canceled");
|
||||
}
|
||||
else if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
message = Messages.Isolator.MemoryUsageMonitorTaskTimedOut(timeSpan);
|
||||
Log.Trace($"Isolator.ExecuteWithTimeLimit(): {message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
CancellationTokenSource.Cancel();
|
||||
if (!CancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
CancellationTokenSource.Cancel();
|
||||
}
|
||||
Log.Error($"Security.ExecuteWithTimeLimit(): {message}");
|
||||
throw new TimeoutException(message);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace QuantConnect.Python
|
||||
/// </summary>
|
||||
public class CommandPythonWrapper : BasePythonWrapper<Command>
|
||||
{
|
||||
private static PyObject _linkSerializationMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for initialising the <see cref="CommandPythonWrapper"/> class with wrapped <see cref="PyObject"/> object
|
||||
/// </summary>
|
||||
@@ -40,8 +42,13 @@ namespace QuantConnect.Python
|
||||
var instance = type.Invoke();
|
||||
|
||||
SetPythonInstance(instance);
|
||||
if (data != null)
|
||||
if (!string.IsNullOrEmpty(data))
|
||||
{
|
||||
if (HasAttr("PayloadData"))
|
||||
{
|
||||
SetProperty("PayloadData", data);
|
||||
}
|
||||
|
||||
foreach (var kvp in JsonConvert.DeserializeObject<Dictionary<string, object>>(data))
|
||||
{
|
||||
if (kvp.Value is JArray jArray)
|
||||
@@ -70,5 +77,32 @@ namespace QuantConnect.Python
|
||||
var result = InvokeMethod(nameof(Run), algorithm);
|
||||
return result.GetAndDispose<bool?>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to serialize a command instance
|
||||
/// </summary>
|
||||
public static string Serialize(PyObject command)
|
||||
{
|
||||
if (command == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (_linkSerializationMethod == null)
|
||||
{
|
||||
var module = PyModule.FromString("python_serialization", @"from json import dumps
|
||||
def serialize(target):
|
||||
if not hasattr(target, '__dict__'):
|
||||
# for example dictionaries
|
||||
return dumps(target)
|
||||
return dumps(target.__dict__)
|
||||
");
|
||||
_linkSerializationMethod = module.GetAttr("serialize");
|
||||
}
|
||||
using var _ = Py.GIL();
|
||||
using var strResult = _linkSerializationMethod.Invoke(command);
|
||||
|
||||
return strResult.As<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,10 @@ namespace QuantConnect.Python
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (member.Name is "ToString")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
missingMembers.Add(member.Name);
|
||||
}
|
||||
|
||||
@@ -1151,6 +1151,129 @@ namespace QuantConnect.Securities.Option
|
||||
return InvertStrategy(BearPutLadder(canonicalOption, higherStrike, middleStrike, lowerStrike, expiration), OptionStrategyDefinitions.BullPutLadder.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method creates new Long Call Backspread strategy, that consists of two calls with the same expiration but different strikes.
|
||||
/// It involves selling the lower strike call, while buying twice the number of the higher strike call.
|
||||
/// </summary>
|
||||
/// <param name="canonicalOption">Option symbol</param>
|
||||
/// <param name="lowerStrike">The strike price of the short call</param>
|
||||
/// <param name="higherStrike">The strike price of the long call</param>
|
||||
/// <param name="expiration">Option expiration date</param>
|
||||
/// <returns>Option strategy specification</returns>
|
||||
public static OptionStrategy CallBackspread(
|
||||
Symbol canonicalOption,
|
||||
decimal lowerStrike,
|
||||
decimal higherStrike,
|
||||
DateTime expiration
|
||||
)
|
||||
{
|
||||
CheckCanonicalOptionSymbol(canonicalOption, "CallBackspread");
|
||||
CheckExpirationDate(expiration, "CallBackspread", nameof(expiration));
|
||||
|
||||
if (lowerStrike >= higherStrike)
|
||||
{
|
||||
throw new ArgumentException($"CallBackspread: strike prices must be in ascending order, {nameof(lowerStrike)}, {nameof(higherStrike)}");
|
||||
}
|
||||
|
||||
return new OptionStrategy
|
||||
{
|
||||
Name = "Call Backspread",
|
||||
Underlying = canonicalOption.Underlying,
|
||||
CanonicalOption = canonicalOption,
|
||||
OptionLegs = new List<OptionStrategy.OptionLegData>
|
||||
{
|
||||
new OptionStrategy.OptionLegData
|
||||
{
|
||||
Right = OptionRight.Call, Strike = lowerStrike, Quantity = -1, Expiration = expiration
|
||||
},
|
||||
new OptionStrategy.OptionLegData
|
||||
{
|
||||
Right = OptionRight.Call, Strike = higherStrike, Quantity = 2, Expiration = expiration
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method creates new Long Put Backspread strategy, that consists of two puts with the same expiration but different strikes.
|
||||
/// It involves selling the higher strike put, while buying twice the number of the lower strike put.
|
||||
/// </summary>
|
||||
/// <param name="canonicalOption">Option symbol</param>
|
||||
/// <param name="higherStrike">The strike price of the short put</param>
|
||||
/// <param name="lowerStrike">The strike price of the long put</param>
|
||||
/// <param name="expiration">Option expiration date</param>
|
||||
/// <returns>Option strategy specification</returns>
|
||||
public static OptionStrategy PutBackspread(
|
||||
Symbol canonicalOption,
|
||||
decimal higherStrike,
|
||||
decimal lowerStrike,
|
||||
DateTime expiration
|
||||
)
|
||||
{
|
||||
CheckCanonicalOptionSymbol(canonicalOption, "PutBackspread");
|
||||
CheckExpirationDate(expiration, "PutBackspread", nameof(expiration));
|
||||
|
||||
if (higherStrike <= lowerStrike)
|
||||
{
|
||||
throw new ArgumentException($"PutBackspread: strike prices must be in descending order, {nameof(higherStrike)}, {nameof(lowerStrike)}");
|
||||
}
|
||||
|
||||
return new OptionStrategy
|
||||
{
|
||||
Name = "Put Backspread",
|
||||
Underlying = canonicalOption.Underlying,
|
||||
CanonicalOption = canonicalOption,
|
||||
OptionLegs = new List<OptionStrategy.OptionLegData>
|
||||
{
|
||||
new OptionStrategy.OptionLegData
|
||||
{
|
||||
Right = OptionRight.Put, Strike = higherStrike, Quantity = -1, Expiration = expiration
|
||||
},
|
||||
new OptionStrategy.OptionLegData
|
||||
{
|
||||
Right = OptionRight.Put, Strike = lowerStrike, Quantity = 2, Expiration = expiration
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method creates new Short Call Backspread strategy, that consists of two calls with the same expiration but different strikes.
|
||||
/// It involves buying the lower strike call, while shorting twice the number of the higher strike call.
|
||||
/// </summary>
|
||||
/// <param name="canonicalOption">Option symbol</param>
|
||||
/// <param name="lowerStrike">The strike price of the long call</param>
|
||||
/// <param name="higherStrike">The strike price of the short call</param>
|
||||
/// <param name="expiration">Option expiration date</param>
|
||||
public static OptionStrategy ShortCallBackspread(
|
||||
Symbol canonicalOption,
|
||||
decimal lowerStrike,
|
||||
decimal higherStrike,
|
||||
DateTime expiration
|
||||
)
|
||||
{
|
||||
return InvertStrategy(CallBackspread(canonicalOption, lowerStrike, higherStrike, expiration), "Short Call Backspread");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method creates new Short Put Backspread strategy, that consists of two puts with the same expiration but different strikes.
|
||||
/// It involves buying the higher strike put, while selling twice the number of the lower strike put.
|
||||
/// </summary>
|
||||
/// <param name="canonicalOption">Option symbol</param>
|
||||
/// <param name="higherStrike">The strike price of the long put</param>
|
||||
/// <param name="lowerStrike">The strike price of the short put</param>
|
||||
/// <param name="expiration">Option expiration date</param>
|
||||
/// <returns>Option strategy specification</returns>
|
||||
public static OptionStrategy ShortPutBackspread(
|
||||
Symbol canonicalOption,
|
||||
decimal higherStrike,
|
||||
decimal lowerStrike,
|
||||
DateTime expiration
|
||||
)
|
||||
{
|
||||
return InvertStrategy(PutBackspread(canonicalOption, higherStrike, lowerStrike, expiration), "Short Put Backspread");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that canonical option symbol is valid
|
||||
/// </summary>
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace QuantConnect.Securities.Option
|
||||
|
||||
return new MaintenanceMargin(inAccountCurrency);
|
||||
}
|
||||
else if(_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
|
||||
{
|
||||
// MAX[In-the-money amount + Margin(long stock evaluated at min(mark price, strike(short call))), min(stock value, max(call value, long stock margin))]
|
||||
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
|
||||
@@ -208,7 +208,7 @@ namespace QuantConnect.Securities.Option
|
||||
var result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
|
||||
return new MaintenanceMargin(result);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
|
||||
_optionStrategy.Name == OptionStrategyDefinitions.ShortIronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortIronButterfly.Name)
|
||||
{
|
||||
var result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
|
||||
@@ -239,7 +239,7 @@ namespace QuantConnect.Securities.Option
|
||||
var orderCosts = shortCallSecurity.AskPrice - longCallSecurity.BidPrice + shortPutSecurity.AskPrice - longPutSecurity.BidPrice;
|
||||
var multiplier = Math.Abs(longCallPosition.Quantity) * longCallSecurity.ContractUnitOfTrade;
|
||||
var closeCost = commissionFees + orderCosts * multiplier;
|
||||
|
||||
|
||||
var strikeDifference = longCallPosition.Symbol.ID.StrikePrice - shortCallPosition.Symbol.ID.StrikePrice;
|
||||
|
||||
var result = Math.Max(1.02m * closeCost, strikeDifference * multiplier);
|
||||
@@ -253,7 +253,7 @@ namespace QuantConnect.Securities.Option
|
||||
// long calendar spread part has no margin requirement due to same strike
|
||||
// only the short calendar spread's short option has margin requirement
|
||||
var furtherExpiry = parameters.PositionGroup.Positions.Max(position => position.Symbol.ID.Date);
|
||||
var shortCalendarSpreadShortLeg = parameters.PositionGroup.Positions.Single(position =>
|
||||
var shortCalendarSpreadShortLeg = parameters.PositionGroup.Positions.Single(position =>
|
||||
position.Quantity < 0 && position.Symbol.ID.Date == furtherExpiry);
|
||||
var shortCalendarSpreadShortLegSecurity = (Option)parameters.Portfolio.Securities[shortCalendarSpreadShortLeg.Symbol];
|
||||
var result = Math.Abs(shortCalendarSpreadShortLegSecurity.BuyingPowerModel.GetMaintenanceMargin(
|
||||
@@ -302,7 +302,7 @@ namespace QuantConnect.Securities.Option
|
||||
result = Math.Abs(underlyingSecurity.BuyingPowerModel.GetInitialMarginRequirement(underlyingSecurity, underlyingPosition.Quantity));
|
||||
result = parameters.Portfolio.CashBook.ConvertToAccountCurrency(result, underlyingSecurity.QuoteCurrency.Symbol);
|
||||
}
|
||||
else if(_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.CoveredCall.Name)
|
||||
{
|
||||
// Max(Call Value, Long Stock Initial Margin)
|
||||
var optionPosition = parameters.PositionGroup.Positions.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
|
||||
@@ -386,7 +386,7 @@ namespace QuantConnect.Securities.Option
|
||||
{
|
||||
result = GetMiddleAndLowStrikeDifference(parameters.PositionGroup, parameters.Portfolio);
|
||||
}
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
|
||||
else if (_optionStrategy.Name == OptionStrategyDefinitions.IronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.IronButterfly.Name ||
|
||||
_optionStrategy.Name == OptionStrategyDefinitions.ShortIronCondor.Name || _optionStrategy.Name == OptionStrategyDefinitions.ShortIronButterfly.Name)
|
||||
{
|
||||
result = GetShortPutLongPutStrikeDifferenceMargin(parameters.PositionGroup.Positions, parameters.Portfolio, parameters.PositionGroup.Quantity);
|
||||
@@ -595,7 +595,7 @@ namespace QuantConnect.Securities.Option
|
||||
private static decimal GetCollarConversionInitialMargin(IPositionGroup positionGroup, SecurityPortfolioManager portfolio, OptionRight optionRight)
|
||||
{
|
||||
// Initial Stock Margin Requirement + In the Money Call/Put Amount
|
||||
var optionPosition = positionGroup.Positions.Single(position =>
|
||||
var optionPosition = positionGroup.Positions.Single(position =>
|
||||
position.Symbol.SecurityType.IsOption() && position.Symbol.ID.OptionRight == optionRight);
|
||||
var underlyingPosition = positionGroup.Positions.Single(position => !position.Symbol.SecurityType.IsOption());
|
||||
var optionSecurity = (Option)portfolio.Securities[optionPosition.Symbol];
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace QuantConnect.Securities.Option.StrategyMatcher
|
||||
typeof(OptionStrategyDefinitions)
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(property => property.PropertyType == typeof(OptionStrategyDefinition))
|
||||
.Select(property => (OptionStrategyDefinition) property.GetValue(null))
|
||||
.Select(property => (OptionStrategyDefinition)property.GetValue(null))
|
||||
.ToImmutableList()
|
||||
);
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace QuantConnect.Lean.Engine
|
||||
private IAlgorithm _algorithm;
|
||||
private readonly object _lock;
|
||||
private readonly bool _liveMode;
|
||||
private bool _cancelRequested;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Publicly accessible algorithm status
|
||||
@@ -111,14 +113,17 @@ namespace QuantConnect.Lean.Engine
|
||||
/// <param name="results">Result handler object</param>
|
||||
/// <param name="realtime">Realtime processing object</param>
|
||||
/// <param name="leanManager">ILeanManager implementation that is updated periodically with the IAlgorithm instance</param>
|
||||
/// <param name="token">Cancellation token</param>
|
||||
/// <param name="cancellationTokenSource">Cancellation token source to monitor</param>
|
||||
/// <remarks>Modify with caution</remarks>
|
||||
public void Run(AlgorithmNodePacket job, IAlgorithm algorithm, ISynchronizer synchronizer, ITransactionHandler transactions, IResultHandler results, IRealTimeHandler realtime, ILeanManager leanManager, CancellationToken token)
|
||||
public void Run(AlgorithmNodePacket job, IAlgorithm algorithm, ISynchronizer synchronizer, ITransactionHandler transactions, IResultHandler results, IRealTimeHandler realtime, ILeanManager leanManager, CancellationTokenSource cancellationTokenSource)
|
||||
{
|
||||
//Initialize:
|
||||
DataPoints = 0;
|
||||
_algorithm = algorithm;
|
||||
|
||||
var token = cancellationTokenSource.Token;
|
||||
_cancellationTokenSource = cancellationTokenSource;
|
||||
|
||||
var backtestMode = (job.Type == PacketType.BacktestNode);
|
||||
var methodInvokers = new Dictionary<Type, MethodInvoker>();
|
||||
var marginCallFrequency = TimeSpan.FromMinutes(5);
|
||||
@@ -607,6 +612,22 @@ namespace QuantConnect.Lean.Engine
|
||||
{
|
||||
_algorithm.SetStatus(state);
|
||||
}
|
||||
|
||||
if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested && !_cancelRequested)
|
||||
{
|
||||
if (state == AlgorithmStatus.Deleted)
|
||||
{
|
||||
_cancelRequested = true;
|
||||
// if the algorithm was deleted, let's give the algorithm a few seconds to shutdown and cancel it out
|
||||
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
else if (state == AlgorithmStatus.Stopped)
|
||||
{
|
||||
_cancelRequested = true;
|
||||
// if the algorithm was stopped, let's give the algorithm a few seconds to shutdown and cancel it out
|
||||
_cancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace QuantConnect.Lean.Engine.DataFeeds
|
||||
/// </summary>
|
||||
public class DefaultDataProvider : IDataProvider, IDisposable
|
||||
{
|
||||
private bool _oneTimeWarningLog;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised each time data fetch is finished (successfully or not)
|
||||
/// </summary>
|
||||
@@ -37,6 +39,7 @@ namespace QuantConnect.Lean.Engine.DataFeeds
|
||||
public virtual Stream Fetch(string key)
|
||||
{
|
||||
var success = true;
|
||||
var errorMessage = string.Empty;
|
||||
try
|
||||
{
|
||||
return new FileStream(FileExtension.ToNormalizedPath(key), FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
@@ -44,8 +47,17 @@ namespace QuantConnect.Lean.Engine.DataFeeds
|
||||
catch (Exception exception)
|
||||
{
|
||||
success = false;
|
||||
if (exception is DirectoryNotFoundException
|
||||
|| exception is FileNotFoundException)
|
||||
errorMessage = exception.Message;
|
||||
if (exception is DirectoryNotFoundException)
|
||||
{
|
||||
if (!_oneTimeWarningLog)
|
||||
{
|
||||
_oneTimeWarningLog = true;
|
||||
Logging.Log.Debug($"DefaultDataProvider.Fetch(): DirectoryNotFoundException: please review data paths, current 'Globals.DataFolder': {Globals.DataFolder}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else if (exception is FileNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -54,7 +66,7 @@ namespace QuantConnect.Lean.Engine.DataFeeds
|
||||
}
|
||||
finally
|
||||
{
|
||||
OnNewDataRequest(new DataProviderNewDataRequestEventArgs(key, success));
|
||||
OnNewDataRequest(new DataProviderNewDataRequestEventArgs(key, success, errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -196,11 +196,6 @@ namespace QuantConnect.Lean.Engine.DataFeeds
|
||||
factory = new TimeTriggeredUniverseSubscriptionEnumeratorFactory(request.Universe as ITimeTriggeredUniverse,
|
||||
_marketHoursDatabase,
|
||||
_timeProvider);
|
||||
|
||||
if (request.Universe is UserDefinedUniverse)
|
||||
{
|
||||
return factory.CreateEnumerator(request, _dataProvider);
|
||||
}
|
||||
}
|
||||
else if (request.Configuration.Type == typeof(FundamentalUniverse))
|
||||
{
|
||||
|
||||
@@ -334,7 +334,7 @@ namespace QuantConnect.Lean.Engine
|
||||
// -> Using this Data Feed,
|
||||
// -> Send Orders to this TransactionHandler,
|
||||
// -> Send Results to ResultHandler.
|
||||
algorithmManager.Run(job, algorithm, synchronizer, AlgorithmHandlers.Transactions, AlgorithmHandlers.Results, AlgorithmHandlers.RealTime, SystemHandlers.LeanManager, isolator.CancellationToken);
|
||||
algorithmManager.Run(job, algorithm, synchronizer, AlgorithmHandlers.Transactions, AlgorithmHandlers.Results, AlgorithmHandlers.RealTime, SystemHandlers.LeanManager, isolator.CancellationTokenSource);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace QuantConnect.Lean.Engine.RealTime
|
||||
/// </summary>
|
||||
public override void Exit()
|
||||
{
|
||||
_realTimeThread.StopSafely(TimeSpan.FromMinutes(5), _cancellationTokenSource);
|
||||
_realTimeThread.StopSafely(TimeSpan.FromMinutes(1), _cancellationTokenSource);
|
||||
_cancellationTokenSource.DisposeSafely();
|
||||
base.Exit();
|
||||
}
|
||||
|
||||
@@ -208,18 +208,19 @@
|
||||
"tt-log-fix-messages": false,
|
||||
|
||||
// Trade Station configuration
|
||||
"trade-station-api-key": "",
|
||||
"trade-station-api-secret": "",
|
||||
"trade-station-code-from-url": "",
|
||||
"trade-station-client-id": "",
|
||||
"trade-station-client-secret": "",
|
||||
"trade-station-redirect-url": "http://localhost",
|
||||
"trade-station-refresh-token": "",
|
||||
"trade-station-api-url": "https://sim-api.tradestation.com",
|
||||
"trade-station-account-type": "Cash|Margin|Futures",
|
||||
// [Optional] Trade Station Proxy Settings
|
||||
"trade-station-use-proxy": false,
|
||||
"trade-station-proxy-address-port": "",
|
||||
"trade-station-proxy-username": "",
|
||||
"trade-station-proxy-password": "",
|
||||
"trade-station-account-id": "",
|
||||
|
||||
// Alpaca configuration
|
||||
"alpaca-api-key": "",
|
||||
"alpaca-api-secret": "",
|
||||
"alpaca-access-token": "",
|
||||
"alpaca-paper-trading": true,
|
||||
|
||||
// Exante trading configuration
|
||||
// client-id, application-id, shared-key are required to access Exante REST API
|
||||
@@ -532,6 +533,34 @@
|
||||
"history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
|
||||
},
|
||||
|
||||
"live-trade-station": {
|
||||
"live-mode": true,
|
||||
|
||||
// real brokerage implementations require the BrokerageTransactionHandler
|
||||
"live-mode-brokerage": "TradeStationBrokerage",
|
||||
"data-queue-handler": [ "TradeStationBrokerage" ],
|
||||
"setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
|
||||
"result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
|
||||
"data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
|
||||
"real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
|
||||
"transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
|
||||
"history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
|
||||
},
|
||||
|
||||
"live-alpaca": {
|
||||
"live-mode": true,
|
||||
|
||||
// real brokerage implementations require the BrokerageTransactionHandler
|
||||
"live-mode-brokerage": "AlpacaBrokerage",
|
||||
"data-queue-handler": [ "AlpacaBrokerage" ],
|
||||
"setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
|
||||
"result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
|
||||
"data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
|
||||
"real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
|
||||
"transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
|
||||
"history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
|
||||
},
|
||||
|
||||
"live-futures-bybit": {
|
||||
"live-mode": true,
|
||||
|
||||
|
||||
@@ -415,62 +415,135 @@ def getTickHistory(algorithm, symbol, start, end):
|
||||
Assert.AreEqual(expectedCount == 1 ? TickType.Trade : TickType.Quote, _testHistoryProvider.HistryRequests.First().TickType);
|
||||
}
|
||||
|
||||
[TestCase(Resolution.Second, Language.CSharp, true)]
|
||||
[TestCase(Resolution.Minute, Language.CSharp, true)]
|
||||
[TestCase(Resolution.Hour, Language.CSharp, true)]
|
||||
[TestCase(Resolution.Daily, Language.CSharp, true)]
|
||||
[TestCase(Resolution.Second, Language.Python, true)]
|
||||
[TestCase(Resolution.Minute, Language.Python, true)]
|
||||
[TestCase(Resolution.Hour, Language.Python, true)]
|
||||
[TestCase(Resolution.Daily, Language.Python, true)]
|
||||
[TestCase(Resolution.Second, Language.CSharp, false)]
|
||||
[TestCase(Resolution.Minute, Language.CSharp, false)]
|
||||
[TestCase(Resolution.Hour, Language.CSharp, false)]
|
||||
[TestCase(Resolution.Daily, Language.CSharp, false)]
|
||||
[TestCase(Resolution.Second, Language.Python, false)]
|
||||
[TestCase(Resolution.Minute, Language.Python, false)]
|
||||
[TestCase(Resolution.Hour, Language.Python, false)]
|
||||
[TestCase(Resolution.Daily, Language.Python, false)]
|
||||
public void BarCountHistoryRequestIsCorrectlyBuilt(Resolution resolution, Language language, bool symbolAlreadyAdded)
|
||||
private static IEnumerable<TestCaseData> BarCountHistoryRequestTestCases
|
||||
{
|
||||
_algorithm.SetStartDate(2013, 10, 07);
|
||||
get
|
||||
{
|
||||
var spyDate = new DateTime(2013, 10, 07);
|
||||
|
||||
yield return new TestCaseData(Resolution.Second, Language.CSharp, Symbols.SPY, true, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Minute, Language.CSharp, Symbols.SPY, true, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Hour, Language.CSharp, Symbols.SPY, true, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.CSharp, Symbols.SPY, true, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Second, Language.Python, Symbols.SPY, true, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Minute, Language.Python, Symbols.SPY, true, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Hour, Language.Python, Symbols.SPY, true, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.Python, Symbols.SPY, true, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Second, Language.CSharp, Symbols.SPY, false, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Minute, Language.CSharp, Symbols.SPY, false, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Hour, Language.CSharp, Symbols.SPY, false, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.CSharp, Symbols.SPY, false, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Second, Language.Python, Symbols.SPY, false, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Minute, Language.Python, Symbols.SPY, false, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Hour, Language.Python, Symbols.SPY, false, spyDate, null, false);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.Python, Symbols.SPY, false, spyDate, null, false);
|
||||
|
||||
yield return new TestCaseData(Resolution.Second, Language.CSharp, Symbols.SPY, true, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Minute, Language.CSharp, Symbols.SPY, true, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Hour, Language.CSharp, Symbols.SPY, true, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.CSharp, Symbols.SPY, true, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Second, Language.Python, Symbols.SPY, true, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Minute, Language.Python, Symbols.SPY, true, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Hour, Language.Python, Symbols.SPY, true, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.Python, Symbols.SPY, true, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Second, Language.CSharp, Symbols.SPY, false, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Minute, Language.CSharp, Symbols.SPY, false, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Hour, Language.CSharp, Symbols.SPY, false, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.CSharp, Symbols.SPY, false, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Second, Language.Python, Symbols.SPY, false, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Minute, Language.Python, Symbols.SPY, false, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Hour, Language.Python, Symbols.SPY, false, spyDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.Python, Symbols.SPY, false, spyDate, null, true);
|
||||
|
||||
var spxCanonicalOption = Symbol.CreateCanonicalOption(Symbols.SPX);
|
||||
var spxDate = new DateTime(2021, 01, 12);
|
||||
|
||||
yield return new TestCaseData(Resolution.Daily, Language.CSharp, spxCanonicalOption, true, spxDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.Python, spxCanonicalOption, true, spxDate, null, true);
|
||||
yield return new TestCaseData(null, Language.CSharp, spxCanonicalOption, true, spxDate, Resolution.Daily, true);
|
||||
yield return new TestCaseData(null, Language.Python, spxCanonicalOption, true, spxDate, Resolution.Daily, true);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.CSharp, spxCanonicalOption, false, spxDate, null, true);
|
||||
yield return new TestCaseData(Resolution.Daily, Language.Python, spxCanonicalOption, false, spxDate, null, true);
|
||||
yield return new TestCaseData(null, Language.CSharp, spxCanonicalOption, false, spxDate, Resolution.Daily, true);
|
||||
yield return new TestCaseData(null, Language.Python, spxCanonicalOption, false, spxDate, Resolution.Daily, true);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(BarCountHistoryRequestTestCases))]
|
||||
public void BarCountHistoryRequestIsCorrectlyBuilt(Resolution? resolution, Language language, Symbol symbol,
|
||||
bool symbolAlreadyAdded, DateTime dateTime, Resolution? defaultResolution, bool multiSymbol)
|
||||
{
|
||||
_algorithm.SetStartDate(dateTime);
|
||||
|
||||
var symbol = Symbols.SPY;
|
||||
if (symbolAlreadyAdded)
|
||||
{
|
||||
// it should not matter
|
||||
_algorithm.AddEquity("SPY");
|
||||
_algorithm.AddSecurity(symbol);
|
||||
}
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
_algorithm.History(symbol, 10, resolution);
|
||||
if (multiSymbol)
|
||||
{
|
||||
_algorithm.History(new[] { symbol }, 10, resolution);
|
||||
}
|
||||
else
|
||||
{
|
||||
_algorithm.History(symbol, 10, resolution);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
_algorithm.SetPandasConverter();
|
||||
_algorithm.History(symbol.ToPython(), 10, resolution);
|
||||
if (multiSymbol)
|
||||
{
|
||||
using var pySymbols = new[] { symbol }.ToPyListUnSafe();
|
||||
_algorithm.History(pySymbols, 10, resolution);
|
||||
|
||||
pySymbols[0].Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
using var pySymbol = symbol.ToPython();
|
||||
_algorithm.History(pySymbol, 10, resolution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Resolution? fillForwardResolution = null;
|
||||
if (resolution != Resolution.Tick)
|
||||
{
|
||||
fillForwardResolution = resolution;
|
||||
fillForwardResolution = resolution ?? defaultResolution;
|
||||
}
|
||||
|
||||
var expectedCount = resolution == Resolution.Hour || resolution == Resolution.Daily ? 1 : 2;
|
||||
Assert.AreEqual(expectedCount, _testHistoryProvider.HistryRequests.Count);
|
||||
Assert.AreEqual(Symbols.SPY, _testHistoryProvider.HistryRequests.First().Symbol);
|
||||
Assert.AreEqual(resolution, _testHistoryProvider.HistryRequests.First().Resolution);
|
||||
Assert.IsFalse(_testHistoryProvider.HistryRequests.First().IncludeExtendedMarketHours);
|
||||
Assert.IsFalse(_testHistoryProvider.HistryRequests.First().IsCustomData);
|
||||
Assert.AreEqual(fillForwardResolution, _testHistoryProvider.HistryRequests.First().FillForwardResolution);
|
||||
Assert.AreEqual(DataNormalizationMode.Adjusted, _testHistoryProvider.HistryRequests.First().DataNormalizationMode);
|
||||
if (symbol.SecurityType == SecurityType.Equity)
|
||||
{
|
||||
var expectedCount = resolution == Resolution.Hour || resolution == Resolution.Daily ? 1 : 2;
|
||||
Assert.AreEqual(expectedCount, _testHistoryProvider.HistryRequests.Count);
|
||||
var request = _testHistoryProvider.HistryRequests.First();
|
||||
Assert.AreEqual(symbol, request.Symbol);
|
||||
Assert.AreEqual(resolution, request.Resolution);
|
||||
Assert.IsFalse(request.IncludeExtendedMarketHours);
|
||||
Assert.IsFalse(request.IsCustomData);
|
||||
Assert.AreEqual(fillForwardResolution, request.FillForwardResolution);
|
||||
Assert.AreEqual(DataNormalizationMode.Adjusted, request.DataNormalizationMode);
|
||||
|
||||
Assert.AreEqual(expectedCount == 1 ? TickType.Trade : TickType.Quote, _testHistoryProvider.HistryRequests.First().TickType);
|
||||
Assert.AreEqual(expectedCount == 1 ? TickType.Trade : TickType.Quote, request.TickType);
|
||||
}
|
||||
else if (symbol.SecurityType == SecurityType.IndexOption)
|
||||
{
|
||||
Assert.AreEqual(1, _testHistoryProvider.HistryRequests.Count);
|
||||
var request = _testHistoryProvider.HistryRequests.Single();
|
||||
Assert.AreEqual(symbol, request.Symbol);
|
||||
Assert.AreEqual(resolution ?? defaultResolution, request.Resolution);
|
||||
Assert.AreEqual(typeof(OptionUniverse), request.DataType);
|
||||
Assert.IsFalse(request.IncludeExtendedMarketHours);
|
||||
Assert.IsFalse(request.IsCustomData);
|
||||
// For OptionUniverse, exchange and data time zones are set to the same value
|
||||
Assert.AreEqual(request.ExchangeHours.TimeZone, request.DataTimeZone);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, true)]
|
||||
@@ -783,12 +856,15 @@ def getOpenInterestHistory(algorithm, symbol, start, end, resolution):
|
||||
{
|
||||
var result = _algorithm.History(new[] { optionSymbol }, start, end, historyResolution, fillForward:false).ToList();
|
||||
|
||||
Assert.AreEqual(53, result.Count);
|
||||
Assert.IsTrue(result.Any(slice => slice.ContainsKey(optionSymbol)));
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(53, result.Count);
|
||||
Assert.IsTrue(result.Any(slice => slice.ContainsKey(optionSymbol)));
|
||||
|
||||
var openInterests = result.Select(slice => slice.Get(typeof(OpenInterest)) as DataDictionary<OpenInterest>).Where(dataDictionary => dataDictionary.Count > 0).ToList();
|
||||
var openInterests = result.Select(slice => slice.Get(typeof(OpenInterest)) as DataDictionary<OpenInterest>).Where(dataDictionary => dataDictionary.Count > 0).ToList();
|
||||
|
||||
Assert.AreEqual(0, openInterests.Count);
|
||||
Assert.AreEqual(0, openInterests.Count);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
51
Tests/Api/AuthenticationTests.cs
Normal file
51
Tests/Api/AuthenticationTests.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Web;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Api;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
[TestFixture]
|
||||
public class AuthenticationTests
|
||||
{
|
||||
[Test]
|
||||
public void Link()
|
||||
{
|
||||
var link = Authentication.Link("authenticate");
|
||||
|
||||
var response = link.DownloadData();
|
||||
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
var jobject = JObject.Parse(response);
|
||||
Assert.IsTrue(jobject["success"].ToObject<bool>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PopulateQueryString()
|
||||
{
|
||||
var payload = new { SomeArray = new[] { 1, 2, 3 }, Symbol = "SPY", Parameters = new Dictionary<string, int>() { { "Quantity", 10 } } };
|
||||
|
||||
var queryString = HttpUtility.ParseQueryString(string.Empty);
|
||||
Authentication.PopulateQueryString(queryString, new[] { new KeyValuePair<string, object>("command", payload) });
|
||||
|
||||
Assert.AreEqual("command[SomeArray][0]=1&command[SomeArray][1]=2&command[SomeArray][2]=3&command[Symbol]=SPY&command[Parameters][Quantity]=10", queryString.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,7 @@ namespace QuantConnect.Tests.Brokerages.Paper
|
||||
var realTime = new BacktestingRealTimeHandler();
|
||||
using var nullLeanManager = new AlgorithmManagerTests.NullLeanManager();
|
||||
|
||||
using var tokenSource = new CancellationTokenSource();
|
||||
// run algorithm manager
|
||||
manager.Run(job,
|
||||
algorithm,
|
||||
@@ -138,7 +139,7 @@ namespace QuantConnect.Tests.Brokerages.Paper
|
||||
results,
|
||||
realTime,
|
||||
nullLeanManager,
|
||||
new CancellationToken()
|
||||
tokenSource
|
||||
);
|
||||
|
||||
var postDividendCash = algorithm.Portfolio.CashBook[Currencies.USD].Amount;
|
||||
|
||||
@@ -13,21 +13,75 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Web;
|
||||
using NUnit.Framework;
|
||||
using Newtonsoft.Json;
|
||||
using QuantConnect.Commands;
|
||||
using QuantConnect.Statistics;
|
||||
using QuantConnect.Configuration;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Algorithm.CSharp;
|
||||
using QuantConnect.Lean.Engine.Server;
|
||||
using System;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Common.Commands
|
||||
{
|
||||
[TestFixture]
|
||||
public class CallbackCommandTests
|
||||
{
|
||||
[Test]
|
||||
public void BaseTypedLink()
|
||||
{
|
||||
var algorithmStub = new AlgorithmStub
|
||||
{
|
||||
ProjectId = 19542033
|
||||
};
|
||||
algorithmStub.AddCommand<MyCommand>();
|
||||
var commandInstance = new MyCommand
|
||||
{
|
||||
Quantity = 0.1m,
|
||||
Target = "BTCUSD"
|
||||
};
|
||||
var link = algorithmStub.Link(commandInstance);
|
||||
|
||||
var parse = HttpUtility.ParseQueryString(link);
|
||||
Assert.IsFalse(string.IsNullOrEmpty(link));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ComplexTypedLink()
|
||||
{
|
||||
var algorithmStub = new AlgorithmStub
|
||||
{
|
||||
ProjectId = 19542033
|
||||
};
|
||||
algorithmStub.AddCommand<MyCommand2>();
|
||||
var commandInstance = new MyCommand2
|
||||
{
|
||||
Parameters = new Dictionary<string, object> { { "quantity", 0.1 } },
|
||||
Target = new[] { "BTCUSD", "AAAA" }
|
||||
};
|
||||
var link = algorithmStub.Link(commandInstance);
|
||||
|
||||
var parse = HttpUtility.ParseQueryString(link);
|
||||
Assert.IsFalse(string.IsNullOrEmpty(link));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UntypedLink()
|
||||
{
|
||||
var algorithmStub = new AlgorithmStub
|
||||
{
|
||||
ProjectId = 19542033
|
||||
};
|
||||
var link = algorithmStub.Link(new { quantity = -0.1, target = "BTCUSD" });
|
||||
|
||||
var parse = HttpUtility.ParseQueryString(link);
|
||||
Assert.IsFalse(string.IsNullOrEmpty(link));
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void CommanCallback(Language language)
|
||||
@@ -125,5 +179,17 @@ namespace QuantConnect.Tests.Common.Commands
|
||||
SetCommandHandler();
|
||||
}
|
||||
}
|
||||
|
||||
private class MyCommand2 : Command
|
||||
{
|
||||
public string[] Target { get; set; }
|
||||
public Dictionary<string, object> Parameters;
|
||||
}
|
||||
|
||||
private class MyCommand : Command
|
||||
{
|
||||
public string Target { get; set; }
|
||||
public decimal Quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
@@ -21,7 +21,7 @@ using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Common
|
||||
{
|
||||
[TestFixture]
|
||||
[TestFixture, Parallelizable(ParallelScope.All)]
|
||||
public class IsolatorTests
|
||||
{
|
||||
[Test]
|
||||
@@ -45,6 +45,38 @@ namespace QuantConnect.Tests.Common
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cancellation()
|
||||
{
|
||||
var isolator = new Isolator();
|
||||
var executed = false;
|
||||
var ended = false;
|
||||
var canceled = false;
|
||||
var result = false;
|
||||
isolator.CancellationTokenSource.CancelAfter(TimeSpan.FromMilliseconds(100));
|
||||
try
|
||||
{
|
||||
result = isolator.ExecuteWithTimeLimit(
|
||||
TimeSpan.FromSeconds(5),
|
||||
() => {
|
||||
executed = true;
|
||||
Thread.Sleep(5000);
|
||||
ended = true;
|
||||
},
|
||||
5000,
|
||||
sleepIntervalMillis: 10
|
||||
);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
canceled = true;
|
||||
}
|
||||
Assert.IsTrue(canceled);
|
||||
Assert.IsFalse(result);
|
||||
Assert.IsTrue(executed);
|
||||
Assert.IsFalse(ended);
|
||||
}
|
||||
|
||||
[TestCase(Language.Python, true)]
|
||||
[TestCase(Language.Python, false)]
|
||||
[TestCase(Language.CSharp, true)]
|
||||
@@ -98,4 +130,4 @@ namespace QuantConnect.Tests.Common
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1412,5 +1412,173 @@ namespace QuantConnect.Tests.Common.Securities.Options
|
||||
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
||||
Assert.AreEqual(1, lowStrikeLeg.Quantity);
|
||||
}
|
||||
|
||||
[TestCase(325, 300)]
|
||||
[TestCase(300, 300)]
|
||||
public void FailsBuildingCallBackspreadStrategy(decimal strike1, decimal strike2)
|
||||
{
|
||||
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
||||
var underlying = Symbols.SPY;
|
||||
var expiration = new DateTime(2023, 08, 18);
|
||||
|
||||
// Unordered and repeated strikes
|
||||
Assert.Throws<ArgumentException>(
|
||||
() => OptionStrategies.CallBackspread(canonicalOptionSymbol, strike1, strike2, expiration));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildCallBackspreadStrategy()
|
||||
{
|
||||
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
||||
var underlying = Symbols.SPY;
|
||||
var strike1 = 300m;
|
||||
var strike2 = 325m;
|
||||
var expiration = new DateTime(2023, 08, 18);
|
||||
|
||||
var strategy = OptionStrategies.CallBackspread(canonicalOptionSymbol, strike1, strike2, expiration);
|
||||
|
||||
Assert.AreEqual("Call Backspread", strategy.Name);
|
||||
Assert.AreEqual(underlying, strategy.Underlying);
|
||||
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
||||
|
||||
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
||||
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
||||
|
||||
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
||||
Assert.AreEqual(OptionRight.Call, lowStrikeLeg.Right);
|
||||
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
||||
Assert.AreEqual(-1, lowStrikeLeg.Quantity);
|
||||
|
||||
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
||||
Assert.AreEqual(OptionRight.Call, highStrikeLeg.Right);
|
||||
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
||||
Assert.AreEqual(2, highStrikeLeg.Quantity);
|
||||
}
|
||||
|
||||
[TestCase(325, 350)]
|
||||
[TestCase(300, 300)]
|
||||
public void FailsBuildingPutBackspreadStrategy(decimal strike1, decimal strike2)
|
||||
{
|
||||
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
||||
var underlying = Symbols.SPY;
|
||||
var expiration = new DateTime(2023, 08, 18);
|
||||
|
||||
// Unordered and repeated strikes
|
||||
Assert.Throws<ArgumentException>(
|
||||
() => OptionStrategies.PutBackspread(canonicalOptionSymbol, strike1, strike2, expiration));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildsPutBackspreadStrategy()
|
||||
{
|
||||
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
||||
var underlying = Symbols.SPY;
|
||||
var strike1 = 350m;
|
||||
var strike2 = 325m;
|
||||
var expiration = new DateTime(2023, 08, 18);
|
||||
|
||||
var strategy = OptionStrategies.PutBackspread(canonicalOptionSymbol, strike1, strike2, expiration);
|
||||
|
||||
Assert.AreEqual("Put Backspread", strategy.Name);
|
||||
Assert.AreEqual(underlying, strategy.Underlying);
|
||||
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
||||
|
||||
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
||||
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
||||
|
||||
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
||||
Assert.AreEqual(OptionRight.Put, highStrikeLeg.Right);
|
||||
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
||||
Assert.AreEqual(-1, highStrikeLeg.Quantity);
|
||||
|
||||
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
||||
Assert.AreEqual(OptionRight.Put, lowStrikeLeg.Right);
|
||||
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
||||
Assert.AreEqual(2, lowStrikeLeg.Quantity);
|
||||
}
|
||||
|
||||
[TestCase(325, 300)]
|
||||
[TestCase(300, 300)]
|
||||
public void FailsBuildingShortCallBackspreadStrategy(decimal strike1, decimal strike2)
|
||||
{
|
||||
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
||||
var underlying = Symbols.SPY;
|
||||
var expiration = new DateTime(2023, 08, 18);
|
||||
|
||||
// Unordered and repeated strikes
|
||||
Assert.Throws<ArgumentException>(
|
||||
() => OptionStrategies.ShortCallBackspread(canonicalOptionSymbol, strike1, strike2, expiration));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildsShortCallBackspreadStrategy()
|
||||
{
|
||||
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
||||
var underlying = Symbols.SPY;
|
||||
var strike1 = 300m;
|
||||
var strike2 = 325m;
|
||||
var expiration = new DateTime(2023, 08, 18);
|
||||
|
||||
var strategy = OptionStrategies.ShortCallBackspread(canonicalOptionSymbol, strike1, strike2, expiration);
|
||||
|
||||
Assert.AreEqual("Short Call Backspread", strategy.Name);
|
||||
Assert.AreEqual(underlying, strategy.Underlying);
|
||||
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
||||
|
||||
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
||||
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
||||
|
||||
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
||||
Assert.AreEqual(OptionRight.Call, lowStrikeLeg.Right);
|
||||
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
||||
Assert.AreEqual(1, lowStrikeLeg.Quantity);
|
||||
|
||||
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
||||
Assert.AreEqual(OptionRight.Call, highStrikeLeg.Right);
|
||||
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
||||
Assert.AreEqual(-2, highStrikeLeg.Quantity);
|
||||
}
|
||||
|
||||
[TestCase(325, 350)]
|
||||
[TestCase(300, 300)]
|
||||
public void FailsBuildingShortPutBackspreadStrategy(decimal strike1, decimal strike2)
|
||||
{
|
||||
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
||||
var underlying = Symbols.SPY;
|
||||
var expiration = new DateTime(2023, 08, 18);
|
||||
|
||||
// Unordered and repeated strikes
|
||||
Assert.Throws<ArgumentException>(
|
||||
() => OptionStrategies.ShortPutBackspread(canonicalOptionSymbol, strike1, strike2, expiration));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildsShortPutBackspreadStrategy()
|
||||
{
|
||||
var canonicalOptionSymbol = Symbols.SPY_Option_Chain;
|
||||
var underlying = Symbols.SPY;
|
||||
var strike1 = 350m;
|
||||
var strike2 = 300m;
|
||||
var expiration = new DateTime(2023, 08, 18);
|
||||
|
||||
var strategy = OptionStrategies.ShortPutBackspread(canonicalOptionSymbol, strike1, strike2, expiration);
|
||||
|
||||
Assert.AreEqual("Short Put Backspread", strategy.Name);
|
||||
Assert.AreEqual(underlying, strategy.Underlying);
|
||||
Assert.AreEqual(canonicalOptionSymbol, strategy.CanonicalOption);
|
||||
|
||||
Assert.AreEqual(2, strategy.OptionLegs.Count);
|
||||
Assert.AreEqual(0, strategy.UnderlyingLegs.Count);
|
||||
|
||||
var highStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike1);
|
||||
Assert.AreEqual(OptionRight.Put, highStrikeLeg.Right);
|
||||
Assert.AreEqual(expiration, highStrikeLeg.Expiration);
|
||||
Assert.AreEqual(1, highStrikeLeg.Quantity);
|
||||
|
||||
var lowStrikeLeg = strategy.OptionLegs.Single(x => x.Strike == strike2);
|
||||
Assert.AreEqual(OptionRight.Put, lowStrikeLeg.Right);
|
||||
Assert.AreEqual(expiration, lowStrikeLeg.Expiration);
|
||||
Assert.AreEqual(-2, lowStrikeLeg.Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,6 @@ namespace QuantConnect.Tests.Engine
|
||||
var results = new BacktestingResultHandler();
|
||||
var realtime = new BacktestingRealTimeHandler();
|
||||
using var leanManager = new NullLeanManager();
|
||||
var token = new CancellationToken();
|
||||
var nullSynchronizer = new NullSynchronizer(algorithm);
|
||||
|
||||
algorithm.Initialize();
|
||||
@@ -136,7 +135,8 @@ namespace QuantConnect.Tests.Engine
|
||||
|
||||
Log.Trace("Starting algorithm manager loop to process " + nullSynchronizer.Count + " time slices");
|
||||
var sw = Stopwatch.StartNew();
|
||||
algorithmManager.Run(job, algorithm, nullSynchronizer, transactions, results, realtime, leanManager, token);
|
||||
using var tokenSource = new CancellationTokenSource();
|
||||
algorithmManager.Run(job, algorithm, nullSynchronizer, transactions, results, realtime, leanManager, tokenSource);
|
||||
sw.Stop();
|
||||
|
||||
realtime.Exit();
|
||||
|
||||
Reference in New Issue
Block a user