Compare commits

...

10 Commits
16640 ... 16656

Author SHA1 Message Date
Martin Molinero
3917e87614 Enhance command support
- Enhance command support, adding link helper methods. Adding and
  expanding new tests
- Minor improvement for command str representation
2024-09-30 14:18:09 -03:00
Louis Szeto
809faa24de Add Backspread Option Strategies (#8312)
* Call and put backspread definitions

* Margin model

* regression tests

* strategy unit test

* refactor

* Use option strategies to order
2024-09-26 16:57:20 -03:00
Jhonathan Abreu
ad5aa263c0 Infer data type from configs in bar count history requests (#8344)
* Infer data type from configs in bar count history requests

* Infer data type from configs in bar count history requests

* Minor changes

* Minor fix

* Cleanup
2024-09-26 16:51:19 -03:00
Roman Yavnikov
a1bb907e03 Feature:Alpaca: support MarketOnOpen and MarketOnClose (#8341)
* feat: missed Alpaca config in Launcher

* feature: support of new OrderTypes for Equtity in AlpacaBrokerage model
2024-09-25 14:30:27 -03:00
Roman Yavnikov
71540f5015 Feature: support MarketOnOpen and MarketOnClose (#8340)
* remove: not used proxy config TradeStation

* feat: missed config of TradeStation in Launcher

* feat: support new OrderTypes in TradeStationBrokerageModel
2024-09-24 15:11:36 -03:00
Kevin Wheeler
a0055a3695 Improve data not found error messages. (#8295)
* Improve data not found error messages.

* Minor tweaks

---------

Co-authored-by: Kevin Wheeler <spreadlove5683@gmail.com>
Co-authored-by: Martin Molinero <martin.molinero1@gmail.com>
2024-09-20 16:56:58 -03:00
Martin-Molinero
0b285df496 Minor fix for option contract universe (#8337) 2024-09-20 13:16:20 -03:00
Martin Molinero
4d37096b3f Reduce realtime shutdown timeout 2024-09-20 10:04:58 -03:00
Martin Molinero
7c42ea795f Minor improvement for live trading shutdown 2024-09-19 20:09:21 -03:00
Martin-Molinero
f2f1d06237 Improve shutdown (#8335) 2024-09-19 19:32:29 -03:00
37 changed files with 1569 additions and 136 deletions

View File

@@ -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>

View 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 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"}
};
}
}

View 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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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);
}
}
}

View 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());
}
}
}
}
}
}

View File

@@ -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 }}

View File

@@ -50,6 +50,8 @@ namespace QuantConnect.Brokerages
OrderType.StopLimit,
OrderType.ComboMarket,
OrderType.ComboLimit,
OrderType.MarketOnOpen,
OrderType.MarketOnClose
});
/// <summary>

View File

@@ -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>

View File

@@ -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));
}
}
}
}

View File

@@ -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}");
}
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -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>();
}
}
}

View File

@@ -55,6 +55,10 @@ namespace QuantConnect.Python
{
continue;
}
else if (member.Name is "ToString")
{
continue;
}
}
missingMembers.Add(member.Name);
}

View File

@@ -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>

View File

@@ -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];

View File

@@ -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()
);

View File

@@ -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));
}
}
}
}

View File

@@ -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));
}
}

View File

@@ -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))
{

View File

@@ -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)
{

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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
{

View 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());
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();