diff --git a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
index 03c383412bbb..cff7e2348078 100644
--- a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
+++ b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
@@ -34,7 +34,7 @@
portable
-
+
@@ -60,4 +60,4 @@
-
\ No newline at end of file
+
diff --git a/Algorithm.Framework/Portfolio/PortfolioOptimizerPythonWrapper.cs b/Algorithm.Framework/Portfolio/PortfolioOptimizerPythonWrapper.cs
index 29b6229ab7c9..5bb22c7ffd0e 100644
--- a/Algorithm.Framework/Portfolio/PortfolioOptimizerPythonWrapper.cs
+++ b/Algorithm.Framework/Portfolio/PortfolioOptimizerPythonWrapper.cs
@@ -22,17 +22,15 @@ namespace QuantConnect.Algorithm.Framework.Portfolio
///
/// Python wrapper for custom portfolio optimizer
///
- public class PortfolioOptimizerPythonWrapper : IPortfolioOptimizer
+ public class PortfolioOptimizerPythonWrapper : BasePythonWrapper, IPortfolioOptimizer
{
- private readonly dynamic _portfolioOptimizer;
-
///
/// Creates a new instance
///
/// The python model to wrapp
public PortfolioOptimizerPythonWrapper(PyObject portfolioOptimizer)
+ : base(portfolioOptimizer)
{
- _portfolioOptimizer = portfolioOptimizer.ValidateImplementationOf();
}
///
@@ -44,10 +42,7 @@ public PortfolioOptimizerPythonWrapper(PyObject portfolioOptimizer)
/// Array of double with the portfolio weights (size: K x 1)
public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
{
- using (Py.GIL())
- {
- return _portfolioOptimizer.Optimize(historicalReturns, expectedReturns, covariance);
- }
+ return InvokeMethod(nameof(Optimize), historicalReturns, expectedReturns, covariance);
}
}
}
diff --git a/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj b/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj
index b3ce44c5f62a..4c31c8534df9 100644
--- a/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj
+++ b/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj
@@ -29,7 +29,7 @@
LICENSE
-
+
diff --git a/Algorithm.Python/CustomModelsPEP8Algorithm.py b/Algorithm.Python/CustomModelsPEP8Algorithm.py
new file mode 100644
index 000000000000..8833ffbb14df
--- /dev/null
+++ b/Algorithm.Python/CustomModelsPEP8Algorithm.py
@@ -0,0 +1,178 @@
+# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
+# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from AlgorithmImports import *
+import random
+
+###
+### Demonstration of using custom fee, slippage, fill, and buying power models for modeling transactions in backtesting.
+### QuantConnect allows you to model all orders as deeply and accurately as you need.
+### This example illustrates how Lean exports its API to Python conforming to PEP8 style guide.
+###
+###
+###
+###
+###
+###
+###
+class CustomModelsPEP8Algorithm(QCAlgorithm):
+ '''Demonstration of using custom fee, slippage, fill, and buying power models for modeling transactions in backtesting.
+ QuantConnect allows you to model all orders as deeply and accurately as you need.'''
+
+ def initialize(self):
+ self.set_start_date(2013,10,1) # Set Start Date
+ self.set_end_date(2013,10,31) # Set End Date
+ self.security = self.add_equity("SPY", Resolution.HOUR)
+ self.spy = self.security.symbol
+
+ # set our models
+ self.security.set_fee_model(CustomFeeModelPEP8(self))
+ self.security.set_fill_model(CustomFillModelPEP8(self))
+ self.security.set_slippage_model(CustomSlippageModelPEP8(self))
+ self.security.set_buying_power_model(CustomBuyingPowerModelPEP8(self))
+
+ def on_data(self, data):
+ open_orders = self.transactions.get_open_orders(self.spy)
+ if len(open_orders) != 0: return
+ if self.time.day > 10 and self.security.holdings.quantity <= 0:
+ quantity = self.calculate_order_quantity(self.spy, .5)
+ self.log(f"MarketOrder: {quantity}")
+ self.market_order(self.spy, quantity, True) # async needed for partial fill market orders
+ elif self.time.day > 20 and self.security.holdings.quantity >= 0:
+ quantity = self.calculate_order_quantity(self.spy, -.5)
+ self.log(f"MarketOrder: {quantity}")
+ self.market_order(self.spy, quantity, True) # async needed for partial fill market orders
+
+class CustomFillModelPEP8(ImmediateFillModel):
+ def __init__(self, algorithm):
+ super().__init__()
+ self.algorithm = algorithm
+ self.absolute_remaining_by_order_id = {}
+ self.random = Random(387510346)
+
+ def market_fill(self, asset, order):
+ absolute_remaining = order.absolute_quantity
+
+ if order.id in self.absolute_remaining_by_order_id.keys():
+ absolute_remaining = self.absolute_remaining_by_order_id[order.id]
+
+ fill = super().market_fill(asset, order)
+ absolute_fill_quantity = int(min(absolute_remaining, self.random.next(0, 2*int(order.absolute_quantity))))
+ fill.fill_quantity = np.sign(order.quantity) * absolute_fill_quantity
+
+ if absolute_remaining == absolute_fill_quantity:
+ fill.status = OrderStatus.FILLED
+ if self.absolute_remaining_by_order_id.get(order.id):
+ self.absolute_remaining_by_order_id.pop(order.id)
+ else:
+ absolute_remaining = absolute_remaining - absolute_fill_quantity
+ self.absolute_remaining_by_order_id[order.id] = absolute_remaining
+ fill.status = OrderStatus.PARTIALLY_FILLED
+ self.algorithm.log(f"CustomFillModel: {fill}")
+ return fill
+
+class CustomFeeModelPEP8(FeeModel):
+ def __init__(self, algorithm):
+ super().__init__()
+ self.algorithm = algorithm
+
+ def get_order_fee(self, parameters):
+ # custom fee math
+ fee = max(1, parameters.security.price
+ * parameters.order.absolute_quantity
+ * 0.00001)
+ self.algorithm.log(f"CustomFeeModel: {fee}")
+ return OrderFee(CashAmount(fee, "USD"))
+
+class CustomSlippageModelPEP8:
+ def __init__(self, algorithm):
+ self.algorithm = algorithm
+
+ def get_slippage_approximation(self, asset, order):
+ # custom slippage math
+ slippage = asset.price * 0.0001 * np.log10(2*float(order.absolute_quantity))
+ self.algorithm.log(f"CustomSlippageModel: {slippage}")
+ return slippage
+
+class CustomBuyingPowerModelPEP8(BuyingPowerModel):
+ def __init__(self, algorithm):
+ super().__init__()
+ self.algorithm = algorithm
+
+ def has_sufficient_buying_power_for_order(self, parameters):
+ # custom behavior: this model will assume that there is always enough buying power
+ has_sufficient_buying_power_for_order_result = HasSufficientBuyingPowerForOrderResult(True)
+ self.algorithm.log(f"CustomBuyingPowerModel: {has_sufficient_buying_power_for_order_result.is_sufficient}")
+ return has_sufficient_buying_power_for_order_result
+
+class SimpleCustomFillModelPEP8(FillModel):
+ def __init__(self):
+ super().__init()
+
+ def _create_order_event(self, asset, order):
+ utc_time = Extensions.convert_to_utc(asset.local_time, asset.exchange.time_zone)
+ return OrderEvent(order, utc_time, OrderFee.zero)
+
+ def _set_order_event_to_filled(self, fill, fill_price, fill_quantity):
+ fill.status = OrderStatus.FILLED
+ fill.fill_quantity = fill_quantity
+ fill.fill_price = fill_price
+ return fill
+
+ def _get_trade_bar(self, asset, order_direction):
+ trade_bar = asset.cache.get_data[TradeBar]()
+ if trade_bar:
+ return trade_bar
+
+ price = asset.price
+ return TradeBar(asset.local_time, asset.symbol, price, price, price, price, 0)
+
+ def market_fill(self, asset, order):
+ fill = self._create_order_event(asset, order)
+ if order.status == OrderStatus.CANCELED:
+ return fill
+
+ fill_price = asset.cache.ask_price if order.direction == OrderDirection.BUY else asset.cache.bid_price
+ return self._set_order_event_to_filled(fill, fill_price, order.quantity)
+
+ def stop_market_fill(self, asset, order):
+ fill = self._create_order_event(asset, order)
+ if order.status == OrderStatus.CANCELED:
+ return fill
+
+ stop_price = order.stop_price
+ trade_bar = self._get_trade_bar(asset, order.direction)
+
+ if order.direction == OrderDirection.SELL and trade_bar.low < stop_price:
+ return self._set_order_event_to_filled(fill, stop_price, order.quantity)
+
+ if order.direction == OrderDirection.BUY and trade_bar.high > stop_price:
+ return self._set_order_event_to_filled(fill, stop_price, order.quantity)
+
+ return fill
+
+ def limit_fill(self, asset, order):
+ fill = self._create_order_event(asset, order)
+ if order.status == OrderStatus.CANCELED:
+ return fill
+
+ limit_price = order.limit_price
+ trade_bar = self._get_trade_bar(asset, order.direction)
+
+ if order.direction == OrderDirection.SELL and trade_bar.high > limit_price:
+ return self._set_order_event_to_filled(fill, limit_price, order.quantity)
+
+ if order.direction == OrderDirection.BUY and trade_bar.low < limit_price:
+ return self._set_order_event_to_filled(fill, limit_price, order.quantity)
+
+ return fill
diff --git a/Algorithm.Python/FundamentalRegressionAlgorithm.py b/Algorithm.Python/FundamentalRegressionAlgorithm.py
index a6ed432f28a0..fa5eb5aac295 100644
--- a/Algorithm.Python/FundamentalRegressionAlgorithm.py
+++ b/Algorithm.Python/FundamentalRegressionAlgorithm.py
@@ -28,7 +28,7 @@ def Initialize(self):
self.UniverseSettings.Resolution = Resolution.Daily
- self.universe = self.AddUniverse(self.SelectionFunction)
+ self._universe = self.AddUniverse(self.SelectionFunction)
# before we add any symbol
self.AssertFundamentalUniverseData()
@@ -69,7 +69,7 @@ def Initialize(self):
def AssertFundamentalUniverseData(self):
# Case A
- universeDataPerTime = self.History(self.universe.DataType, [self.universe.Symbol], TimeSpan(2, 0, 0, 0))
+ universeDataPerTime = self.History(self._universe.DataType, [self._universe.Symbol], TimeSpan(2, 0, 0, 0))
if len(universeDataPerTime) != 2:
raise ValueError(f"Unexpected Fundamentals history count {len(universeDataPerTime)}! Expected 2")
@@ -77,7 +77,7 @@ def AssertFundamentalUniverseData(self):
self.AssertFundamentalEnumerator(universeDataCollection, "A")
# Case B (sugar on A)
- universeDataPerTime = self.History(self.universe, TimeSpan(2, 0, 0, 0))
+ universeDataPerTime = self.History(self._universe, TimeSpan(2, 0, 0, 0))
if len(universeDataPerTime) != 2:
raise ValueError(f"Unexpected Fundamentals history count {len(universeDataPerTime)}! Expected 2")
@@ -85,9 +85,9 @@ def AssertFundamentalUniverseData(self):
self.AssertFundamentalEnumerator(universeDataCollection, "B")
# Case C: Passing through the unvierse type and symbol
- enumerableOfDataDictionary = self.History[self.universe.DataType]([self.universe.Symbol], 100)
+ enumerableOfDataDictionary = self.History[self._universe.DataType]([self._universe.Symbol], 100)
for selectionCollectionForADay in enumerableOfDataDictionary:
- self.AssertFundamentalEnumerator(selectionCollectionForADay[self.universe.Symbol], "C")
+ self.AssertFundamentalEnumerator(selectionCollectionForADay[self._universe.Symbol], "C")
def AssertFundamentalEnumerator(self, enumerable, caseName):
dataPointCount = 0
diff --git a/Algorithm.Python/PEP8StyleBasicAlgorithm.py b/Algorithm.Python/PEP8StyleBasicAlgorithm.py
new file mode 100644
index 000000000000..805a18eef47c
--- /dev/null
+++ b/Algorithm.Python/PEP8StyleBasicAlgorithm.py
@@ -0,0 +1,48 @@
+# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
+# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from AlgorithmImports import *
+
+class PEP8StyleBasicAlgorithm(QCAlgorithm):
+
+ def initialize(self):
+ self.set_start_date(2013,10, 7)
+ self.set_end_date(2013,10,11)
+ self.set_cash(100000)
+
+ self.spy = self.add_equity("SPY", Resolution.MINUTE, extended_market_hours=False, fill_forward=True).symbol
+
+ # Test accessing a constant (QCAlgorithm.MaxTagsCount)
+ self.debug("MaxTagsCount: " + str(self.MAX_TAGS_COUNT))
+
+ def on_data(self, slice):
+ if not self.portfolio.invested:
+ self.set_holdings(self.spy, 1)
+ self.debug("Purchased Stock")
+
+ def on_order_event(self, order_event):
+ self.log(f"{self.time} :: {order_event}")
+
+ def on_end_of_algorithm(self):
+ self.log("Algorithm ended!")
+
+ if not self.portfolio.invested:
+ raise Exception("Algorithm should have been invested at the end of the algorithm")
+
+ # let's do some logging to do more pep8 style testing
+ self.log("-----------------------------------------------------------------------------------------")
+ self.log(f"{self.spy.value} last price: {self.securities[self.spy].price}")
+ self.log(f"{self.spy.value} holdings: "
+ f"{self.securities[self.spy].holdings.quantity}@{self.securities[self.spy].holdings.price}="
+ f"{self.securities[self.spy].holdings.holdings_value}")
+ self.log("-----------------------------------------------------------------------------------------")
diff --git a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
index 6b7b4d48b9d2..8506a3d2f823 100644
--- a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
+++ b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
@@ -39,7 +39,7 @@
-
+
@@ -276,4 +276,4 @@
LICENSE
-
\ No newline at end of file
+
diff --git a/Algorithm.Python/SelectUniverseSymbolsFromIDRegressionAlgorithm.py b/Algorithm.Python/SelectUniverseSymbolsFromIDRegressionAlgorithm.py
index c2c5e7c57b0b..4c5c2969fdcb 100644
--- a/Algorithm.Python/SelectUniverseSymbolsFromIDRegressionAlgorithm.py
+++ b/Algorithm.Python/SelectUniverseSymbolsFromIDRegressionAlgorithm.py
@@ -26,7 +26,7 @@ def Initialize(self):
self.SetEndDate(2014, 3, 26)
self.SetCash(100000)
- self.securities = []
+ self._securities = []
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.select_symbol)
@@ -49,8 +49,8 @@ def select_symbol(self, fundamental):
return security_ids
def OnSecuritiesChanged(self, changes):
- self.securities.extend(changes.AddedSecurities)
+ self._securities.extend(changes.AddedSecurities)
def OnEndOfAlgorithm(self):
- if not self.securities:
+ if not self._securities:
raise Exception("No securities were selected")
diff --git a/Algorithm.Python/UniverseSelectedRegressionAlgorithm.py b/Algorithm.Python/UniverseSelectedRegressionAlgorithm.py
index bedd2869eb22..0e89277e6776 100644
--- a/Algorithm.Python/UniverseSelectedRegressionAlgorithm.py
+++ b/Algorithm.Python/UniverseSelectedRegressionAlgorithm.py
@@ -24,7 +24,7 @@ def Initialize(self):
self.UniverseSettings.Resolution = Resolution.Daily
- self.universe = self.AddUniverse(self.SelectionFunction)
+ self._universe = self.AddUniverse(self.SelectionFunction)
self.selectionCount = 0
def SelectionFunction(self, fundamentals):
@@ -37,13 +37,13 @@ def SelectionFunction(self, fundamentals):
return [ x.Symbol for x in sortedByDollarVolume[:self.selectionCount] ]
def OnData(self, data):
- if Symbol.Create("TSLA", SecurityType.Equity, Market.USA) in self.universe.Selected:
+ if Symbol.Create("TSLA", SecurityType.Equity, Market.USA) in self._universe.Selected:
raise ValueError(f"TSLA shouldn't of been selected")
- self.Buy(next(iter(self.universe.Selected)), 1)
+ self.Buy(next(iter(self._universe.Selected)), 1)
def OnEndOfAlgorithm(self):
if self.selectionCount != 3:
raise ValueError(f"Unexpected selection count {self.selectionCount}")
- if self.universe.Selected.Count != 3 or self.universe.Selected.Count == self.universe.Members.Count:
- raise ValueError(f"Unexpected universe selected count {self.universe.Selected.Count}")
+ if self._universe.Selected.Count != 3 or self._universe.Selected.Count == self._universe.Members.Count:
+ raise ValueError(f"Unexpected universe selected count {self._universe.Selected.Count}")
diff --git a/Algorithm/Alphas/AlphaModelPythonWrapper.cs b/Algorithm/Alphas/AlphaModelPythonWrapper.cs
index e02814c6444f..e4bd60cf857a 100644
--- a/Algorithm/Alphas/AlphaModelPythonWrapper.cs
+++ b/Algorithm/Alphas/AlphaModelPythonWrapper.cs
@@ -1,4 +1,4 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -27,7 +27,7 @@ namespace QuantConnect.Algorithm.Framework.Alphas
///
public class AlphaModelPythonWrapper : AlphaModel
{
- private readonly dynamic _model;
+ private readonly BasePythonWrapper _model;
///
/// Defines a name for a framework model
@@ -39,13 +39,13 @@ public override string Name
using (Py.GIL())
{
// if the model defines a Name property then use that
- if (_model.HasAttr("Name"))
+ if (_model.HasAttr(nameof(Name)))
{
- return (_model.Name as PyObject).GetAndDispose();
+ return _model.GetProperty(nameof(Name));
}
// if the model does not define a name property, use the python type name
- return (_model.__class__.__name__ as PyObject).GetAndDispose();
+ return _model.GetProperty(" __class__" ).GetAttr("__name__").GetAndDispose();
}
}
}
@@ -56,17 +56,14 @@ public override string Name
/// >Model that generates alpha
public AlphaModelPythonWrapper(PyObject model)
{
- using (Py.GIL())
+ _model = new BasePythonWrapper(model, false);
+ foreach (var attributeName in new[] { "Update", "OnSecuritiesChanged" })
{
- foreach (var attributeName in new[] { "Update", "OnSecuritiesChanged" })
+ if (!_model.HasAttr(attributeName))
{
- if (!model.HasAttr(attributeName))
- {
- throw new NotImplementedException($"IAlphaModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}");
- }
+ throw new NotImplementedException($"IAlphaModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}");
}
}
- _model = model;
}
///
@@ -80,7 +77,7 @@ public override IEnumerable Update(QCAlgorithm algorithm, Slice data)
{
using (Py.GIL())
{
- var insights = _model.Update(algorithm, new PythonSlice(data)) as PyObject;
+ var insights = _model.InvokeMethod(nameof(Update), algorithm, new PythonSlice(data));
var iterator = insights.GetIterator();
foreach (PyObject insight in iterator)
{
@@ -98,10 +95,7 @@ public override IEnumerable Update(QCAlgorithm algorithm, Slice data)
/// The security additions and removals from the algorithm
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
- using (Py.GIL())
- {
- _model.OnSecuritiesChanged(algorithm, changes);
- }
+ _model.InvokeMethod(nameof(OnSecuritiesChanged), algorithm, changes).Dispose();
}
}
-}
\ No newline at end of file
+}
diff --git a/Algorithm/Execution/ExecutionModelPythonWrapper.cs b/Algorithm/Execution/ExecutionModelPythonWrapper.cs
index ce4ece1ae082..a3c526517c59 100644
--- a/Algorithm/Execution/ExecutionModelPythonWrapper.cs
+++ b/Algorithm/Execution/ExecutionModelPythonWrapper.cs
@@ -1,4 +1,4 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -16,6 +16,7 @@
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Data.UniverseSelection;
+using QuantConnect.Python;
using System;
namespace QuantConnect.Algorithm.Framework.Execution
@@ -25,7 +26,7 @@ namespace QuantConnect.Algorithm.Framework.Execution
///
public class ExecutionModelPythonWrapper : ExecutionModel
{
- private readonly dynamic _model;
+ private readonly BasePythonWrapper _model;
///
/// Constructor for initialising the class with wrapped object
@@ -33,17 +34,14 @@ public class ExecutionModelPythonWrapper : ExecutionModel
/// Model defining how to execute trades to reach a portfolio target
public ExecutionModelPythonWrapper(PyObject model)
{
- using (Py.GIL())
+ _model = new BasePythonWrapper(model, false);
+ foreach (var attributeName in new[] { "Execute", "OnSecuritiesChanged" })
{
- foreach (var attributeName in new[] { "Execute", "OnSecuritiesChanged" })
+ if (!_model.HasAttr(attributeName))
{
- if (!model.HasAttr(attributeName))
- {
- throw new NotImplementedException($"IExecutionModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}");
- }
+ throw new NotImplementedException($"IExecutionModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}");
}
}
- _model = model;
}
///
@@ -54,10 +52,7 @@ public ExecutionModelPythonWrapper(PyObject model)
/// The portfolio targets to be ordered
public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
{
- using (Py.GIL())
- {
- _model.Execute(algorithm, targets);
- }
+ _model.InvokeMethod(nameof(Execute), algorithm, targets).Dispose();
}
///
@@ -67,10 +62,7 @@ public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
/// The security additions and removals from the algorithm
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
- using (Py.GIL())
- {
- _model.OnSecuritiesChanged(algorithm, changes);
- }
+ _model.InvokeMethod(nameof(OnSecuritiesChanged), algorithm, changes).Dispose();
}
}
-}
\ No newline at end of file
+}
diff --git a/Algorithm/Portfolio/PortfolioConstructionModelPythonWrapper.cs b/Algorithm/Portfolio/PortfolioConstructionModelPythonWrapper.cs
index 57256334bce9..f8c50d1c40d2 100644
--- a/Algorithm/Portfolio/PortfolioConstructionModelPythonWrapper.cs
+++ b/Algorithm/Portfolio/PortfolioConstructionModelPythonWrapper.cs
@@ -1,4 +1,4 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -16,6 +16,7 @@
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data.UniverseSelection;
+using QuantConnect.Python;
using System;
using System.Collections.Generic;
@@ -26,7 +27,7 @@ namespace QuantConnect.Algorithm.Framework.Portfolio
///
public class PortfolioConstructionModelPythonWrapper : PortfolioConstructionModel
{
- private readonly dynamic _model;
+ private readonly BasePythonWrapper _model;
private readonly bool _implementsDetermineTargetPercent;
///
@@ -36,17 +37,11 @@ public override bool RebalanceOnSecurityChanges
{
get
{
- using (Py.GIL())
- {
- return _model.RebalanceOnSecurityChanges;
- }
+ return _model.GetProperty(nameof(RebalanceOnSecurityChanges));
}
set
{
- using (Py.GIL())
- {
- _model.RebalanceOnSecurityChanges = value;
- }
+ _model.SetProperty(nameof(RebalanceOnSecurityChanges), value);
}
}
@@ -57,17 +52,11 @@ public override bool RebalanceOnInsightChanges
{
get
{
- using (Py.GIL())
- {
- return _model.RebalanceOnInsightChanges;
- }
+ return _model.GetProperty(nameof(RebalanceOnInsightChanges));
}
set
{
- using (Py.GIL())
- {
- _model.RebalanceOnInsightChanges = value;
- }
+ _model.SetProperty(nameof(RebalanceOnInsightChanges), value);
}
}
@@ -77,18 +66,18 @@ public override bool RebalanceOnInsightChanges
/// Model defining how to build a portfolio from alphas
public PortfolioConstructionModelPythonWrapper(PyObject model)
{
+ _model = new BasePythonWrapper(model, false);
using (Py.GIL())
{
foreach (var attributeName in new[] { "CreateTargets", "OnSecuritiesChanged" })
{
- if (!model.HasAttr(attributeName))
+ if (!_model.HasAttr(attributeName))
{
throw new NotImplementedException($"IPortfolioConstructionModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}");
}
}
- _model = model;
- _model.SetPythonWrapper(this);
+ _model.InvokeMethod(nameof(SetPythonWrapper), this).Dispose();
_implementsDetermineTargetPercent = model.GetPythonMethod("DetermineTargetPercent") != null;
}
@@ -102,13 +91,7 @@ public PortfolioConstructionModelPythonWrapper(PyObject model)
/// An enumerable of portfolio targets to be sent to the execution model
public override IEnumerable CreateTargets(QCAlgorithm algorithm, Insight[] insights)
{
- using (Py.GIL())
- {
- foreach (var target in _model.CreateTargets(algorithm, insights))
- {
- yield return target;
- }
- }
+ return _model.InvokeMethod>(nameof(CreateTargets), algorithm, insights);
}
///
@@ -118,10 +101,7 @@ public override IEnumerable CreateTargets(QCAlgorithm algorith
/// The security additions and removals from the algorithm
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
- using (Py.GIL())
- {
- _model.OnSecuritiesChanged(algorithm, changes);
- }
+ _model.InvokeMethod(nameof(OnSecuritiesChanged), algorithm, changes).Dispose();
}
///
@@ -132,10 +112,7 @@ public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges
/// True if the portfolio should create a target for the insight
protected override bool ShouldCreateTargetForInsight(Insight insight)
{
- using (Py.GIL())
- {
- return _model.ShouldCreateTargetForInsight(insight);
- }
+ return _model.InvokeMethod(nameof(ShouldCreateTargetForInsight), insight);
}
///
@@ -148,10 +125,7 @@ protected override bool ShouldCreateTargetForInsight(Insight insight)
/// True if should rebalance
protected override bool IsRebalanceDue(Insight[] insights, DateTime algorithmUtc)
{
- using (Py.GIL())
- {
- return _model.IsRebalanceDue(insights, algorithmUtc);
- }
+ return _model.InvokeMethod(nameof(IsRebalanceDue), insights, algorithmUtc);
}
///
@@ -160,10 +134,7 @@ protected override bool IsRebalanceDue(Insight[] insights, DateTime algorithmUtc
/// An enumerable of the target insights
protected override List GetTargetInsights()
{
- using (Py.GIL())
- {
- return _model.GetTargetInsights();
- }
+ return _model.InvokeMethod>(nameof(GetTargetInsights));
}
///
@@ -178,11 +149,11 @@ protected override Dictionary DetermineTargetPercent(List>(nameof(DetermineTargetPercent), activeInsights);
}
Dictionary dic;
- var result = _model.DetermineTargetPercent(activeInsights);
+ var result = _model.InvokeMethod(nameof(DetermineTargetPercent), activeInsights) as dynamic;
if ((result as PyObject).TryConvert(out dic))
{
// this is required if the python implementation is actually returning a C# dic, not common,
@@ -201,4 +172,4 @@ protected override Dictionary DetermineTargetPercent(ListLICENSE
-
+
diff --git a/Algorithm/Risk/RiskManagementModelPythonWrapper.cs b/Algorithm/Risk/RiskManagementModelPythonWrapper.cs
index 3b5ca1ebc20c..1656877feb7d 100644
--- a/Algorithm/Risk/RiskManagementModelPythonWrapper.cs
+++ b/Algorithm/Risk/RiskManagementModelPythonWrapper.cs
@@ -1,4 +1,4 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -26,7 +26,7 @@ namespace QuantConnect.Algorithm.Framework.Risk
///
public class RiskManagementModelPythonWrapper : RiskManagementModel
{
- private readonly dynamic _model;
+ private readonly BasePythonWrapper _model;
///
/// Constructor for initialising the class with wrapped object
@@ -34,7 +34,7 @@ public class RiskManagementModelPythonWrapper : RiskManagementModel
/// Model defining how risk is managed
public RiskManagementModelPythonWrapper(PyObject model)
{
- _model = model.ValidateImplementationOf();
+ _model = new BasePythonWrapper(model);
}
///
@@ -46,7 +46,7 @@ public override IEnumerable ManageRisk(QCAlgorithm algorithm,
{
using (Py.GIL())
{
- var riskTargetOverrides = _model.ManageRisk(algorithm, targets) as PyObject;
+ var riskTargetOverrides = _model.InvokeMethod("ManageRisk", algorithm, targets);
var iterator = riskTargetOverrides.GetIterator();
foreach (PyObject target in iterator)
{
@@ -64,10 +64,7 @@ public override IEnumerable ManageRisk(QCAlgorithm algorithm,
/// The security additions and removals from the algorithm
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
- using (Py.GIL())
- {
- _model.OnSecuritiesChanged(algorithm, changes);
- }
+ _model.InvokeMethod(nameof(OnSecuritiesChanged), algorithm, changes).Dispose();
}
}
}
diff --git a/Algorithm/Selection/UniverseSelectionModelPythonWrapper.cs b/Algorithm/Selection/UniverseSelectionModelPythonWrapper.cs
index 2c11ea7b2226..b06ef5d00018 100644
--- a/Algorithm/Selection/UniverseSelectionModelPythonWrapper.cs
+++ b/Algorithm/Selection/UniverseSelectionModelPythonWrapper.cs
@@ -1,4 +1,4 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using QuantConnect.Interfaces;
+using QuantConnect.Python;
namespace QuantConnect.Algorithm.Framework.Selection
{
@@ -26,7 +27,7 @@ namespace QuantConnect.Algorithm.Framework.Selection
///
public class UniverseSelectionModelPythonWrapper : UniverseSelectionModel
{
- private readonly dynamic _model;
+ private readonly BasePythonWrapper _model;
private readonly bool _modelHasGetNextRefreshTime;
///
@@ -39,10 +40,7 @@ public override DateTime GetNextRefreshTimeUtc()
return DateTime.MaxValue;
}
- using (Py.GIL())
- {
- return (_model.GetNextRefreshTimeUtc() as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(GetNextRefreshTimeUtc));
}
///
@@ -51,19 +49,19 @@ public override DateTime GetNextRefreshTimeUtc()
/// Model defining universes for the algorithm
public UniverseSelectionModelPythonWrapper(PyObject model)
{
+ _model = new BasePythonWrapper(model, false);
using (Py.GIL())
{
- _modelHasGetNextRefreshTime = model.HasAttr(nameof(IUniverseSelectionModel.GetNextRefreshTimeUtc));
+ _modelHasGetNextRefreshTime = _model.HasAttr(nameof(IUniverseSelectionModel.GetNextRefreshTimeUtc));
foreach (var attributeName in new[] { "CreateUniverses" })
{
- if (!model.HasAttr(attributeName))
+ if (!_model.HasAttr(attributeName))
{
throw new NotImplementedException($"IPortfolioSelectionModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}");
}
}
}
- _model = model;
}
///
@@ -75,7 +73,7 @@ public override IEnumerable CreateUniverses(QCAlgorithm algorithm)
{
using (Py.GIL())
{
- var universes = _model.CreateUniverses(algorithm) as PyObject;
+ var universes = _model.InvokeMethod(nameof(CreateUniverses), algorithm);
var iterator = universes.GetIterator();
foreach (PyObject universe in iterator)
{
@@ -86,4 +84,4 @@ public override IEnumerable CreateUniverses(QCAlgorithm algorithm)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Algorithm/UniverseDefinitions.cs b/Algorithm/UniverseDefinitions.cs
index e7dab53fdf81..a1cbddff39da 100644
--- a/Algorithm/UniverseDefinitions.cs
+++ b/Algorithm/UniverseDefinitions.cs
@@ -17,7 +17,6 @@
using System;
using System.Linq;
using Python.Runtime;
-using QuantConnect.Data;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
diff --git a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs
index 029c0a06f47e..20e3aa00e348 100644
--- a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs
+++ b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs
@@ -43,14 +43,29 @@ namespace QuantConnect.AlgorithmFactory.Python.Wrappers
///
/// Creates and wraps the algorithm written in python.
///
- public class AlgorithmPythonWrapper : IAlgorithm
+ public class AlgorithmPythonWrapper : BasePythonWrapper, IAlgorithm
{
- private readonly dynamic _algorithm;
+ private readonly PyObject _algorithm;
private readonly dynamic _onData;
- private readonly dynamic _onOrderEvent;
private readonly dynamic _onMarginCall;
private readonly IAlgorithm _baseAlgorithm;
+ // QCAlgorithm methods that might be implemented in the python algorithm:
+ // We keep them to avoid the BasePythonWrapper caching and eventual lookup overhead since these methods are called quite frequently
+ private dynamic _onBrokerageDisconnect;
+ private dynamic _onBrokerageMessage;
+ private dynamic _onBrokerageReconnect;
+ private dynamic _onSplits;
+ private dynamic _onDividends;
+ private dynamic _onDelistings;
+ private dynamic _onSymbolChangedEvents;
+ private dynamic _onEndOfDay;
+ private dynamic _onMarginCallWarning;
+ private dynamic _onOrderEvent;
+ private dynamic _onAssignmentOrderEvent;
+ private dynamic _onSecuritiesChanged;
+ private dynamic _onFrameworkSecuritiesChanged;
+
///
/// True if the underlying python algorithm implements "OnEndOfDay"
///
@@ -67,6 +82,7 @@ public class AlgorithmPythonWrapper : IAlgorithm
///
/// Name of the module that can be found in the PYTHONPATH
public AlgorithmPythonWrapper(string moduleName)
+ : base(false)
{
try
{
@@ -92,23 +108,22 @@ public AlgorithmPythonWrapper(string moduleName)
Logging.Log.Trace("AlgorithmPythonWrapper(): Creating IAlgorithm instance.");
_algorithm = attr.Invoke();
+ SetPythonInstance(_algorithm);
+ var dynAlgorithm = _algorithm as dynamic;
// Set pandas
- _algorithm.SetPandasConverter();
+ dynAlgorithm.SetPandasConverter();
// IAlgorithm reference for LEAN internal C# calls (without going from C# to Python and back)
- _baseAlgorithm = _algorithm.AsManagedObject(type);
+ _baseAlgorithm = dynAlgorithm.AsManagedObject(type);
// determines whether OnData method was defined or inherits from QCAlgorithm
// If it is not, OnData from the base class will not be called
- var pyAlgorithm = _algorithm as PyObject;
- _onData = pyAlgorithm.GetPythonMethod("OnData");
-
- _onMarginCall = pyAlgorithm.GetPythonMethod("OnMarginCall");
+ _onData = _algorithm.GetPythonMethod("OnData");
- _onOrderEvent = pyAlgorithm.GetAttr("OnOrderEvent");
+ _onMarginCall = _algorithm.GetPythonMethod("OnMarginCall");
- PyObject endOfDayMethod = pyAlgorithm.GetPythonMethod("OnEndOfDay");
+ PyObject endOfDayMethod = _algorithm.GetPythonMethod("OnEndOfDay");
if (endOfDayMethod != null)
{
// Since we have a EOD method implemented
@@ -128,6 +143,21 @@ public AlgorithmPythonWrapper(string moduleName)
// python will only use the last implemented, meaning only one will
// be used and seen.
}
+
+ // Initialize the python methods
+ _onBrokerageDisconnect = _algorithm.GetMethod("OnBrokerageDisconnect");
+ _onBrokerageMessage = _algorithm.GetMethod("OnBrokerageMessage");
+ _onBrokerageReconnect = _algorithm.GetMethod("OnBrokerageReconnect");
+ _onSplits = _algorithm.GetMethod("OnSplits");
+ _onDividends = _algorithm.GetMethod("OnDividends");
+ _onDelistings = _algorithm.GetMethod("OnDelistings");
+ _onSymbolChangedEvents = _algorithm.GetMethod("OnSymbolChangedEvents");
+ _onEndOfDay = _algorithm.GetMethod("OnEndOfDay");
+ _onMarginCallWarning = _algorithm.GetMethod("OnMarginCallWarning");
+ _onOrderEvent = _algorithm.GetMethod("OnOrderEvent");
+ _onAssignmentOrderEvent = _algorithm.GetMethod("OnAssignmentOrderEvent");
+ _onSecuritiesChanged = _algorithm.GetMethod("OnSecuritiesChanged");
+ _onFrameworkSecuritiesChanged = _algorithm.GetMethod("OnFrameworkSecuritiesChanged");
}
attr.Dispose();
}
@@ -675,10 +705,7 @@ public void OnEndOfTimeStep()
///
public void Initialize()
{
- using (Py.GIL())
- {
- _algorithm.Initialize();
- }
+ InvokeMethod(nameof(Initialize));
}
///
@@ -700,10 +727,7 @@ public void Initialize()
///
public void OnBrokerageDisconnect()
{
- using (Py.GIL())
- {
- _algorithm.OnBrokerageDisconnect();
- }
+ _onBrokerageDisconnect();
}
///
@@ -711,10 +735,7 @@ public void OnBrokerageDisconnect()
///
public void OnBrokerageMessage(BrokerageMessageEvent messageEvent)
{
- using (Py.GIL())
- {
- _algorithm.OnBrokerageMessage(messageEvent);
- }
+ _onBrokerageMessage(messageEvent);
}
///
@@ -722,10 +743,7 @@ public void OnBrokerageMessage(BrokerageMessageEvent messageEvent)
///
public void OnBrokerageReconnect()
{
- using (Py.GIL())
- {
- _algorithm.OnBrokerageReconnect();
- }
+ _onBrokerageReconnect();
}
///
@@ -758,10 +776,7 @@ public void OnFrameworkData(Slice slice)
/// The current time slice splits
public void OnSplits(Splits splits)
{
- using (Py.GIL())
- {
- _algorithm.OnSplits(splits);
- }
+ _onSplits(splits);
}
///
@@ -770,10 +785,7 @@ public void OnSplits(Splits splits)
/// The current time slice dividends
public void OnDividends(Dividends dividends)
{
- using (Py.GIL())
- {
- _algorithm.OnDividends(dividends);
- }
+ _onDividends(dividends);
}
///
@@ -782,10 +794,7 @@ public void OnDividends(Dividends dividends)
/// The current time slice delistings
public void OnDelistings(Delistings delistings)
{
- using (Py.GIL())
- {
- _algorithm.OnDelistings(delistings);
- }
+ _onDelistings(delistings);
}
///
@@ -794,10 +803,7 @@ public void OnDelistings(Delistings delistings)
/// The current time slice symbol changed events
public void OnSymbolChangedEvents(SymbolChangedEvents symbolsChanged)
{
- using (Py.GIL())
- {
- _algorithm.OnSymbolChangedEvents(symbolsChanged);
- }
+ _onSymbolChangedEvents(symbolsChanged);
}
///
@@ -805,10 +811,7 @@ public void OnSymbolChangedEvents(SymbolChangedEvents symbolsChanged)
///
public void OnEndOfAlgorithm()
{
- using (Py.GIL())
- {
- _algorithm.OnEndOfAlgorithm();
- }
+ InvokeMethod(nameof(OnEndOfAlgorithm));
}
///
@@ -822,10 +825,7 @@ public void OnEndOfDay()
{
try
{
- using (Py.GIL())
- {
- _algorithm.OnEndOfDay();
- }
+ _onEndOfDay();
}
// If OnEndOfDay is not defined in the script, but OnEndOfDay(Symbol) is, a python exception occurs
// Only throws if there is an error in its implementation body
@@ -850,10 +850,7 @@ public void OnEndOfDay(Symbol symbol)
{
try
{
- using (Py.GIL())
- {
- _algorithm.OnEndOfDay(symbol);
- }
+ _onEndOfDay(symbol);
}
// If OnEndOfDay(Symbol) is not defined in the script, but OnEndOfDay is, a python exception occurs
// Only throws if there is an error in its implementation body
@@ -874,20 +871,20 @@ public void OnMarginCall(List requests)
{
using (Py.GIL())
{
- var result = _algorithm.OnMarginCall(requests);
+ var method = GetMethod(nameof(OnMarginCall));
+ var result = method.Invoke(requests);
if (_onMarginCall != null)
{
- var pyRequests = result as PyObject;
// If the method does not return or returns a non-iterable PyObject, throw an exception
- if (pyRequests == null || !pyRequests.IsIterable())
+ if (result == null || !result.IsIterable())
{
throw new Exception("OnMarginCall must return a non-empty list of SubmitOrderRequest");
}
requests.Clear();
- using var iterator = pyRequests.GetIterator();
+ using var iterator = result.GetIterator();
foreach (PyObject pyRequest in iterator)
{
SubmitOrderRequest request;
@@ -911,10 +908,7 @@ public void OnMarginCall(List requests)
///
public void OnMarginCallWarning()
{
- using (Py.GIL())
- {
- _algorithm.OnMarginCallWarning();
- }
+ _onMarginCallWarning();
}
///
@@ -924,10 +918,7 @@ public void OnMarginCallWarning()
/// Event information
public void OnOrderEvent(OrderEvent newEvent)
{
- using (Py.GIL())
- {
- _onOrderEvent(newEvent);
- }
+ _onOrderEvent(newEvent);
}
///
@@ -948,10 +939,7 @@ public OrderTicket SubmitOrderRequest(SubmitOrderRequest request)
/// This method can be called asynchronously and so should only be used by seasoned C# experts. Ensure you use proper locks on thread-unsafe objects
public void OnAssignmentOrderEvent(OrderEvent assignmentEvent)
{
- using (Py.GIL())
- {
- _algorithm.OnAssignmentOrderEvent(assignmentEvent);
- }
+ _onAssignmentOrderEvent(assignmentEvent);
}
///
@@ -960,10 +948,7 @@ public void OnAssignmentOrderEvent(OrderEvent assignmentEvent)
/// Security additions/removals for this time step
public void OnSecuritiesChanged(SecurityChanges changes)
{
- using (Py.GIL())
- {
- _algorithm.OnSecuritiesChanged(changes);
- }
+ _onSecuritiesChanged(changes);
}
///
@@ -972,10 +957,7 @@ public void OnSecuritiesChanged(SecurityChanges changes)
/// Security additions/removals for this time step
public void OnFrameworkSecuritiesChanged(SecurityChanges changes)
{
- using (Py.GIL())
- {
- _algorithm.OnFrameworkSecuritiesChanged(changes);
- }
+ _onFrameworkSecuritiesChanged(changes);
}
///
@@ -992,10 +974,7 @@ public void PostInitialize()
///
public void OnWarmupFinished()
{
- using (Py.GIL())
- {
- _algorithm.OnWarmupFinished();
- }
+ InvokeMethod(nameof(OnWarmupFinished));
}
///
diff --git a/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj b/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj
index ed7f9b869cae..51692f7e9159 100644
--- a/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj
+++ b/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj
@@ -28,7 +28,7 @@
LICENSE
-
+
diff --git a/Common/Algorithm/Framework/Alphas/InsightScoreFunctionPythonWrapper.cs b/Common/Algorithm/Framework/Alphas/InsightScoreFunctionPythonWrapper.cs
index f8db1b99e79d..234256003f1e 100644
--- a/Common/Algorithm/Framework/Alphas/InsightScoreFunctionPythonWrapper.cs
+++ b/Common/Algorithm/Framework/Alphas/InsightScoreFunctionPythonWrapper.cs
@@ -16,23 +16,22 @@
using System;
using Python.Runtime;
using QuantConnect.Algorithm.Framework.Alphas.Analysis;
+using QuantConnect.Python;
namespace QuantConnect.Algorithm.Framework.Alphas
{
///
/// A python implementation insight evaluator wrapper
///
- public class InsightScoreFunctionPythonWrapper : IInsightScoreFunction
+ public class InsightScoreFunctionPythonWrapper : BasePythonWrapper, IInsightScoreFunction
{
- private readonly dynamic _model;
-
///
/// Creates a new python wrapper instance
///
/// The python instance to wrap
public InsightScoreFunctionPythonWrapper(PyObject insightEvaluator)
+ : base(insightEvaluator)
{
- _model = insightEvaluator;
}
///
@@ -40,10 +39,7 @@ public InsightScoreFunctionPythonWrapper(PyObject insightEvaluator)
///
public void Score(InsightManager insightManager, DateTime utcTime)
{
- using (Py.GIL())
- {
- var insights = _model.Score(insightManager, utcTime);
- }
+ InvokeMethod(nameof(Score), insightManager, utcTime);
}
}
}
diff --git a/Common/Data/Market/Greeks.cs b/Common/Data/Market/Greeks.cs
index fa427baa71d7..6b886d072107 100644
--- a/Common/Data/Market/Greeks.cs
+++ b/Common/Data/Market/Greeks.cs
@@ -1,11 +1,11 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -148,6 +148,20 @@ private set
}
}
+ ///
+ /// Gets the lambda.
+ ///
+ /// Lambda is the percentage change in option value per percentage change in the
+ /// underlying's price, a measure of leverage. Sometimes referred to as gearing.
+ /// (∂V/∂S ✕ S/V)
+ ///
+ ///
+ ///
+ /// Alias for required for compatibility with Python when
+ /// PEP8 API is used (lambda is a reserved keyword in Python).
+ ///
+ public decimal Lambda_ => Lambda;
+
///
/// Gets the theta per day.
///
@@ -160,7 +174,7 @@ private set
///
/// Initializes a new default instance of the class
///
- public Greeks()
+ public Greeks()
: this(0m, 0m, 0m, 0m, 0m, 0m)
{
}
@@ -201,4 +215,4 @@ public Greeks(Func> deltaGamma, Func vega, Func
_lambda = new Lazy(lambda);
}
}
-}
\ No newline at end of file
+}
diff --git a/Common/Data/Shortable/ShortableProviderPythonWrapper.cs b/Common/Data/Shortable/ShortableProviderPythonWrapper.cs
index 5fcae4311cc0..5b81375f6d6d 100644
--- a/Common/Data/Shortable/ShortableProviderPythonWrapper.cs
+++ b/Common/Data/Shortable/ShortableProviderPythonWrapper.cs
@@ -24,17 +24,15 @@ namespace QuantConnect.Data.Shortable
///
/// Python wrapper for custom shortable providers
///
- public class ShortableProviderPythonWrapper : IShortableProvider
+ public class ShortableProviderPythonWrapper : BasePythonWrapper, IShortableProvider
{
- private readonly dynamic _shortableProvider;
-
///
/// Creates a new instance
///
/// The python custom shortable provider
public ShortableProviderPythonWrapper(PyObject shortableProvider)
+ : base(shortableProvider)
{
- _shortableProvider = shortableProvider.ValidateImplementationOf();
}
///
@@ -45,10 +43,7 @@ public ShortableProviderPythonWrapper(PyObject shortableProvider)
/// zero indicating that it is does have borrowing costs
public decimal FeeRate(Symbol symbol, DateTime localTime)
{
- using (Py.GIL())
- {
- return (_shortableProvider.FeeRate(symbol, localTime) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(FeeRate), symbol, localTime);
}
///
@@ -60,10 +55,7 @@ public decimal FeeRate(Symbol symbol, DateTime localTime)
/// zero indicating that it is does have borrowing costs
public decimal RebateRate(Symbol symbol, DateTime localTime)
{
- using (Py.GIL())
- {
- return (_shortableProvider.RebateRate(symbol, localTime) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(RebateRate), symbol, localTime);
}
///
@@ -74,10 +66,7 @@ public decimal RebateRate(Symbol symbol, DateTime localTime)
/// The quantity shortable for the given Symbol as a positive number. Null if the Symbol is shortable without restrictions.
public long? ShortableQuantity(Symbol symbol, DateTime localTime)
{
- using (Py.GIL())
- {
- return (_shortableProvider.ShortableQuantity(symbol, localTime) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(ShortableQuantity), symbol, localTime);
}
}
}
diff --git a/Common/Data/UniverseSelection/UniversePythonWrapper.cs b/Common/Data/UniverseSelection/UniversePythonWrapper.cs
index 7a7470fe4938..9878b9aefaab 100644
--- a/Common/Data/UniverseSelection/UniversePythonWrapper.cs
+++ b/Common/Data/UniverseSelection/UniversePythonWrapper.cs
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using Python.Runtime;
using QuantConnect.Interfaces;
+using QuantConnect.Python;
using QuantConnect.Securities;
namespace QuantConnect.Data.UniverseSelection
@@ -26,7 +27,7 @@ namespace QuantConnect.Data.UniverseSelection
///
public class UniversePythonWrapper : Universe
{
- private readonly dynamic _universe;
+ private readonly BasePythonWrapper _model;
///
/// Gets the settings used for subscriptions added for this universe
@@ -35,17 +36,11 @@ public override UniverseSettings UniverseSettings
{
get
{
- using (Py.GIL())
- {
- return _universe.UniverseSettings;
- }
+ return _model.GetProperty(nameof(UniverseSettings));
}
set
{
- using (Py.GIL())
- {
- _universe.UniverseSettings = value;
- }
+ _model.SetProperty(nameof(UniverseSettings), value);
}
}
@@ -56,17 +51,11 @@ public override bool DisposeRequested
{
get
{
- using (Py.GIL())
- {
- return _universe.DisposeRequested;
- }
+ return _model.GetProperty(nameof(DisposeRequested));
}
protected set
{
- using (Py.GIL())
- {
- _universe.DisposeRequested = value;
- }
+ _model.SetProperty(nameof(DisposeRequested), value);
}
}
@@ -77,10 +66,7 @@ public override SubscriptionDataConfig Configuration
{
get
{
- using (Py.GIL())
- {
- return _universe.Configuration;
- }
+ return _model.GetProperty(nameof(Configuration));
}
}
@@ -91,10 +77,7 @@ public override ConcurrentDictionary Securities
{
get
{
- using (Py.GIL())
- {
- return _universe.Securities;
- }
+ return _model.GetProperty>(nameof(Securities));
}
}
@@ -103,7 +86,7 @@ public override ConcurrentDictionary Securities
///
public UniversePythonWrapper(PyObject universe) : base(null)
{
- _universe = universe;
+ _model = new BasePythonWrapper(universe, false);
}
///
@@ -116,7 +99,7 @@ public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataColl
{
using (Py.GIL())
{
- var symbols = _universe.SelectSymbols(utcTime, data) as PyObject;
+ var symbols = _model.InvokeMethod(nameof(SelectSymbols), utcTime, data);
var iterator = symbols.GetIterator();
foreach (PyObject symbol in iterator)
{
@@ -140,7 +123,8 @@ public override IEnumerable GetSubscriptionRequests(Securit
{
using (Py.GIL())
{
- var subscriptionRequests = _universe.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc, subscriptionService) as PyObject;
+ var subscriptionRequests = _model.InvokeMethod(nameof(GetSubscriptionRequests), security, currentTimeUtc,
+ maximumEndTimeUtc, subscriptionService);
var iterator = subscriptionRequests.GetIterator();
foreach (PyObject request in iterator)
{
diff --git a/Common/Extensions.cs b/Common/Extensions.cs
index cbed3f506fc6..c098be865c60 100644
--- a/Common/Extensions.cs
+++ b/Common/Extensions.cs
@@ -607,14 +607,67 @@ public static dynamic GetPythonMethod(this PyObject instance, string name)
{
using (Py.GIL())
{
- var method = instance.GetAttr(name);
+ PyObject method;
+
+ // Let's try first with snake-case style in case the user is using it
+ var snakeCasedNamed = name.ToSnakeCase();
+ if (snakeCasedNamed != name)
+ {
+ method = instance.GetPythonMethodWithChecks(snakeCasedNamed);
+ if (method != null)
+ {
+ return method;
+ }
+ }
+
+ method = instance.GetAttr(name);
var pythonType = method.GetPythonType();
- var isPythonDefined = pythonType.Repr().Equals("");
+ var isPythonDefined = pythonType.Repr().Equals("", StringComparison.Ordinal);
+
+ if (isPythonDefined)
+ {
+ return method;
+ }
- return isPythonDefined ? method : null;
+
+
+ return null;
}
}
+ ///
+ /// Gets a python method by name
+ ///
+ /// The object instance to search the method in
+ /// The name of the method
+ /// The python method or null if not defined or CSharp implemented
+ public static dynamic GetPythonMethodWithChecks(this PyObject instance, string name)
+ {
+ using (Py.GIL())
+ {
+ if (!instance.HasAttr(name))
+ {
+ return null;
+ }
+
+ return instance.GetPythonMethod(name);
+ }
+ }
+
+ ///
+ /// Gets a method from a instance by name.
+ /// First, it tries to get the snake-case version of the method name, in case the user is using that style.
+ /// Else, it tries to get the method with the original name, regardless of whether the class has a Python overload or not.
+ ///
+ /// The object instance to search the method in
+ /// The name of the method
+ /// The method matching the name
+ public static dynamic GetMethod(this PyObject instance, string name)
+ {
+ using var _ = Py.GIL();
+ return instance.GetPythonMethodWithChecks(name.ToSnakeCase()) ?? instance.GetAttr(name);
+ }
+
///
/// Get a python methods arg count
///
diff --git a/Common/Orders/OptionExercise/OptionExerciseModelPythonWrapper.cs b/Common/Orders/OptionExercise/OptionExerciseModelPythonWrapper.cs
index eaa33a2a84eb..95d6e9bb8f8f 100644
--- a/Common/Orders/OptionExercise/OptionExerciseModelPythonWrapper.cs
+++ b/Common/Orders/OptionExercise/OptionExerciseModelPythonWrapper.cs
@@ -24,17 +24,15 @@ namespace QuantConnect.Orders.OptionExercise
///
/// Python wrapper for custom option exercise models
///
- public class OptionExerciseModelPythonWrapper: IOptionExerciseModel
+ public class OptionExerciseModelPythonWrapper: BasePythonWrapper, IOptionExerciseModel
{
- private readonly dynamic _model;
-
///
/// Creates a new instance
///
/// The python model to wrapp
public OptionExerciseModelPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model.ValidateImplementationOf();
}
///
@@ -46,7 +44,7 @@ public IEnumerable OptionExercise(Option option, OptionExerciseOrder
{
using (Py.GIL())
{
- using var orderEventGenerator = _model.OptionExercise(option, order) as PyObject;
+ using var orderEventGenerator = InvokeMethod(nameof(OptionExercise), option, order);
using var iterator = orderEventGenerator.GetIterator();
foreach (PyObject item in iterator)
{
diff --git a/Common/Python/BasePythonWrapper.cs b/Common/Python/BasePythonWrapper.cs
new file mode 100644
index 000000000000..2cbc97687d16
--- /dev/null
+++ b/Common/Python/BasePythonWrapper.cs
@@ -0,0 +1,230 @@
+/*
+ * 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 Python.Runtime;
+
+namespace QuantConnect.Python
+{
+ ///
+ /// Base class for Python wrapper classes
+ ///
+ public class BasePythonWrapper
+ {
+ private PyObject _instance;
+ private object _underlyingClrObject;
+ private Type _underlyingClrObjectType;
+ private Dictionary _pythonMethods;
+ private Dictionary _pythonPropertyNames;
+
+ private readonly bool _validateInterface;
+
+ ///
+ /// Gets the underlying python instance
+ ///
+ protected PyObject Instance => _instance;
+
+ ///
+ /// Creates a new instance of the class
+ ///
+ /// Whether to perform validations for interface implementation
+ public BasePythonWrapper(bool validateInterface = true)
+ {
+ _pythonMethods = new();
+ _pythonPropertyNames = new();
+ _validateInterface = validateInterface;
+ }
+
+ ///
+ /// Creates a new instance of the class with the specified instance
+ ///
+ /// The underlying python instance
+ /// Whether to perform validations for interface implementation
+ public BasePythonWrapper(PyObject instance, bool validateInterface = true)
+ : this(validateInterface)
+ {
+ SetPythonInstance(instance);
+ }
+
+ ///
+ /// Sets the python instance
+ ///
+ /// The underlying python instance
+ public void SetPythonInstance(PyObject instance)
+ {
+ if (_instance != null)
+ {
+ _pythonMethods.Clear();
+ _pythonPropertyNames.Clear();
+ }
+
+ _instance = _validateInterface ? instance.ValidateImplementationOf() : instance;
+ _instance.TryConvert(out _underlyingClrObject);
+ if (_underlyingClrObject != null)
+ {
+ _underlyingClrObjectType = _underlyingClrObject.GetType();
+ }
+ }
+
+ ///
+ /// Gets the Python instance property with the specified name
+ ///
+ /// The name of the property
+ public T GetProperty(string propertyName)
+ {
+ using var _ = Py.GIL();
+ return GetProperty(propertyName).GetAndDispose();
+ }
+
+ ///
+ /// Gets the Python instance property with the specified name
+ ///
+ /// The name of the property
+ public PyObject GetProperty(string propertyName)
+ {
+ using var _ = Py.GIL();
+ return _instance.GetAttr(GetPropertyName(propertyName));
+ }
+
+ ///
+ /// Sets the Python instance property with the specified name
+ ///
+ /// The name of the property
+ /// The property value
+ public void SetProperty(string propertyName, object value)
+ {
+ using var _ = Py.GIL();
+ _instance.SetAttr(GetPropertyName(propertyName), value.ToPython());
+ }
+
+ ///
+ /// Gets the Python instance event with the specified name
+ ///
+ /// The name of the event
+ public dynamic GetEvent(string name)
+ {
+ using var _ = Py.GIL();
+ return _instance.GetAttr(GetPropertyName(name, true));
+ }
+
+ ///
+ /// Determines whether the Python instance has the specified attribute
+ ///
+ /// The attribute name
+ /// Whether the Python instance has the specified attribute
+ public bool HasAttr(string name)
+ {
+ using var _ = Py.GIL();
+ return _instance.HasAttr(name) || _instance.HasAttr(name.ToSnakeCase());
+ }
+
+ ///
+ /// Gets the Python instances method with the specified name and caches it
+ ///
+ /// The name of the method
+ /// The matched method
+ public PyObject GetMethod(string methodName)
+ {
+ if (!_pythonMethods.TryGetValue(methodName, out var method))
+ {
+ method = _instance.GetMethod(methodName);
+ _pythonMethods = AddToDictionary(_pythonMethods, methodName, method);
+ }
+
+ return method;
+ }
+
+ ///
+ /// Invokes the specified method with the specified arguments
+ ///
+ /// The name of the method
+ /// The arguments to call the method with
+ /// The returned valued converted to the given type
+ public T InvokeMethod(string methodName, params object[] args)
+ {
+ using var _ = Py.GIL();
+ var method = GetMethod(methodName);
+ return method.Invoke(args);
+ }
+
+ ///
+ /// Invokes the specified method with the specified arguments
+ ///
+ /// The name of the method
+ /// The arguments to call the method with
+ public PyObject InvokeMethod(string methodName, params object[] args)
+ {
+ using var _ = Py.GIL();
+ var method = GetMethod(methodName);
+ return method.Invoke(args);
+ }
+
+ private string GetPropertyName(string propertyName, bool isEvent = false)
+ {
+ if (!_pythonPropertyNames.TryGetValue(propertyName, out var pythonPropertyName))
+ {
+ var snakeCasedPropertyName = propertyName.ToSnakeCase();
+
+ // If the object is actually a C# object (e.g. a child class of a C# class),
+ // we check which property was defined in the Python class (if any), either the snake-cased or the original name.
+ if (!isEvent && _underlyingClrObject != null)
+ {
+ var property = _underlyingClrObjectType.GetProperty(propertyName);
+ if (property != null)
+ {
+ var clrPropertyValue = property.GetValue(_underlyingClrObject);
+ var pyObjectSnakeCasePropertyValue = _instance.GetAttr(snakeCasedPropertyName);
+
+ if (!pyObjectSnakeCasePropertyValue.TryConvert(out object pyObjectSnakeCasePropertyClrValue, true) ||
+ !ReferenceEquals(clrPropertyValue, pyObjectSnakeCasePropertyClrValue))
+ {
+ pythonPropertyName = snakeCasedPropertyName;
+ }
+ else
+ {
+ pythonPropertyName = propertyName;
+ }
+ }
+ }
+
+ if (pythonPropertyName == null)
+ {
+ pythonPropertyName = snakeCasedPropertyName;
+ if (!_instance.HasAttr(pythonPropertyName))
+ {
+ pythonPropertyName = propertyName;
+ }
+ }
+
+ _pythonPropertyNames = AddToDictionary(_pythonPropertyNames, propertyName, pythonPropertyName);
+ }
+
+ return pythonPropertyName;
+ }
+
+ ///
+ /// Adds a key-value pair to the dictionary by copying the original one first and returning a new dictionary
+ /// containing the new key-value pair along with the original ones.
+ /// We do this in order to avoid the overhead of using locks or concurrent dictionaries and still be thread-safe.
+ ///
+ private static Dictionary AddToDictionary(Dictionary dictionary, string key, T value)
+ {
+ var tmp = new Dictionary(dictionary);
+ tmp[key] = value;
+ return tmp;
+ }
+ }
+}
diff --git a/Common/Python/BenchmarkPythonWrapper.cs b/Common/Python/BenchmarkPythonWrapper.cs
index 7443a682d76d..1a9850d105bf 100644
--- a/Common/Python/BenchmarkPythonWrapper.cs
+++ b/Common/Python/BenchmarkPythonWrapper.cs
@@ -22,17 +22,15 @@ namespace QuantConnect.Python
///
/// Provides an implementation of that wraps a object
///
- public class BenchmarkPythonWrapper : IBenchmark
+ public class BenchmarkPythonWrapper : BasePythonWrapper, IBenchmark
{
- private readonly dynamic _model;
-
///
/// Constructor for initialising the class with wrapped object
///
/// Python benchmark model
public BenchmarkPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model;
}
///
@@ -42,10 +40,7 @@ public BenchmarkPythonWrapper(PyObject model)
/// The value of the benchmark at the specified time
public decimal Evaluate(DateTime time)
{
- using (Py.GIL())
- {
- return (_model.Evaluate(time) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(Evaluate), time);
}
}
}
diff --git a/Common/Python/BrokerageMessageHandlerPythonWrapper.cs b/Common/Python/BrokerageMessageHandlerPythonWrapper.cs
index 68084b36a383..31f04730abb2 100644
--- a/Common/Python/BrokerageMessageHandlerPythonWrapper.cs
+++ b/Common/Python/BrokerageMessageHandlerPythonWrapper.cs
@@ -21,17 +21,15 @@ namespace QuantConnect.Python
///
/// Provides a wrapper for implementations written in python
///
- public class BrokerageMessageHandlerPythonWrapper : IBrokerageMessageHandler
+ public class BrokerageMessageHandlerPythonWrapper : BasePythonWrapper, IBrokerageMessageHandler
{
- private readonly dynamic _model;
-
///
/// Initializes a new instance of the class
///
/// The python implementation of
public BrokerageMessageHandlerPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model.ValidateImplementationOf();
}
///
@@ -40,10 +38,7 @@ public BrokerageMessageHandlerPythonWrapper(PyObject model)
/// The message to be handled
public void HandleMessage(BrokerageMessageEvent message)
{
- using (Py.GIL())
- {
- _model.HandleMessage(message);
- }
+ InvokeMethod(nameof(HandleMessage), message);
}
///
@@ -53,10 +48,7 @@ public void HandleMessage(BrokerageMessageEvent message)
/// Whether the order should be added to the transaction handler
public bool HandleOrder(NewBrokerageOrderNotificationEventArgs eventArgs)
{
- using (Py.GIL())
- {
- return _model.HandleOrder(eventArgs);
- }
+ return InvokeMethod(nameof(HandleOrder), eventArgs);
}
}
}
diff --git a/Common/Python/BrokerageModelPythonWrapper.cs b/Common/Python/BrokerageModelPythonWrapper.cs
index ef7c867aebaa..6f823036c976 100644
--- a/Common/Python/BrokerageModelPythonWrapper.cs
+++ b/Common/Python/BrokerageModelPythonWrapper.cs
@@ -32,17 +32,15 @@ namespace QuantConnect.Python
///
/// Provides an implementation of that wraps a object
///
- public class BrokerageModelPythonWrapper : IBrokerageModel
+ public class BrokerageModelPythonWrapper : BasePythonWrapper, IBrokerageModel
{
- private readonly dynamic _model;
-
///
/// Constructor for initialising the class with wrapped object
///
/// Models brokerage transactions, fees, and order
public BrokerageModelPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model;
}
///
@@ -52,10 +50,7 @@ public AccountType AccountType
{
get
{
- using (Py.GIL())
- {
- return (_model.AccountType as PyObject).GetAndDispose();
- }
+ return GetProperty(nameof(AccountType));
}
}
@@ -67,10 +62,7 @@ public decimal RequiredFreeBuyingPowerPercent
{
get
{
- using (Py.GIL())
- {
- return (_model.RequiredFreeBuyingPowerPercent as PyObject).GetAndDispose();
- }
+ return GetProperty(nameof(RequiredFreeBuyingPowerPercent));
}
}
@@ -83,7 +75,7 @@ public IReadOnlyDictionary DefaultMarkets
{
using (Py.GIL())
{
- var markets = _model.DefaultMarkets;
+ var markets = GetProperty(nameof(DefaultMarkets)) as dynamic;
if ((markets as PyObject).TryConvert(out IReadOnlyDictionary csharpDic))
{
return csharpDic;
@@ -110,10 +102,7 @@ public IReadOnlyDictionary DefaultMarkets
/// The split event data
public void ApplySplit(List tickets, Split split)
{
- using (Py.GIL())
- {
- _model.ApplySplit(tickets, split);
- }
+ InvokeMethod(nameof(ApplySplit), tickets, split);
}
///
@@ -128,10 +117,7 @@ public void ApplySplit(List tickets, Split split)
/// True if the brokerage would be able to perform the execution, false otherwise
public bool CanExecuteOrder(Security security, Order order)
{
- using (Py.GIL())
- {
- return (_model.CanExecuteOrder(security, order) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(CanExecuteOrder), security, order);
}
///
@@ -149,12 +135,13 @@ public bool CanSubmitOrder(Security security, Order order, out BrokerageMessageE
{
using (Py.GIL())
{
- using var result = _model.CanSubmitOrder(security, order, out message) as PyObject;
+ message = null;
+ using var result = InvokeMethod(nameof(CanSubmitOrder), security, order, message);
// Since pythonnet does not support out parameters, the methods return
// a tuple where the out parameter comes after the other returned values
if (!PyTuple.IsTupleType(result))
{
- throw new ArgumentException($@"{_model.__class__.__name__}.CanSubmitOrder(): Must return a tuple value where the first value is a bool and the second a BrokerageMessageEvent");
+ throw new ArgumentException($@"{(Instance as dynamic).__class__.__name__}.CanSubmitOrder(): Must return a tuple value where the first value is a bool and the second a BrokerageMessageEvent");
}
message = result[1].As();
@@ -174,12 +161,13 @@ public bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest re
{
using (Py.GIL())
{
- using var result = _model.CanUpdateOrder(security,order, request, out message) as PyObject;
+ message = null;
+ using var result = InvokeMethod(nameof(CanUpdateOrder), security, order, request, message);
// Since pythonnet does not support out parameters, the methods return
// a tuple where the out parameter comes after the other returned values
if (!PyTuple.IsTupleType(result))
{
- throw new ArgumentException($@"{_model.__class__.__name__}.CanUpdateOrder(): Must return a tuple value where the first value is a bool and the second a BrokerageMessageEvent");
+ throw new ArgumentException($@"{(Instance as dynamic).__class__.__name__}.CanUpdateOrder(): Must return a tuple value where the first value is a bool and the second a BrokerageMessageEvent");
}
message = result[1].As();
@@ -196,7 +184,7 @@ public IBenchmark GetBenchmark(SecurityManager securities)
{
using (Py.GIL())
{
- var benchmark = _model.GetBenchmark(securities) as PyObject;
+ var benchmark = InvokeMethod(nameof(GetBenchmark), securities);
if (benchmark.TryConvert(out var csharpBenchmark))
{
return csharpBenchmark;
@@ -214,7 +202,7 @@ public IFeeModel GetFeeModel(Security security)
{
using (Py.GIL())
{
- var feeModel = _model.GetFeeModel(security) as PyObject;
+ var feeModel = InvokeMethod(nameof(GetFeeModel), security);
if (feeModel.TryConvert(out var csharpFeeModel))
{
return csharpFeeModel;
@@ -232,7 +220,7 @@ public IFillModel GetFillModel(Security security)
{
using (Py.GIL())
{
- var fillModel = _model.GetFillModel(security) as PyObject;
+ var fillModel = InvokeMethod(nameof(GetFillModel), security);
if (fillModel.TryConvert(out var csharpFillModel))
{
return csharpFillModel;
@@ -248,10 +236,7 @@ public IFillModel GetFillModel(Security security)
/// The leverage for the specified security
public decimal GetLeverage(Security security)
{
- using (Py.GIL())
- {
- return (_model.GetLeverage(security) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(GetLeverage), security);
}
///
@@ -263,7 +248,7 @@ public ISettlementModel GetSettlementModel(Security security)
{
using (Py.GIL())
{
- var settlementModel = _model.GetSettlementModel(security) as PyObject;
+ var settlementModel = InvokeMethod(nameof(GetSettlementModel), security);
if (settlementModel.TryConvert(out var csharpSettlementModel))
{
return csharpSettlementModel;
@@ -281,10 +266,7 @@ public ISettlementModel GetSettlementModel(Security security)
[Obsolete("Flagged deprecated and will remove December 1st 2018")]
public ISettlementModel GetSettlementModel(Security security, AccountType accountType)
{
- using (Py.GIL())
- {
- return (_model.GetSettlementModel(security, accountType) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(GetSettlementModel), security, accountType);
}
///
@@ -296,7 +278,7 @@ public ISlippageModel GetSlippageModel(Security security)
{
using (Py.GIL())
{
- var slippageModel = _model.GetSlippageModel(security) as PyObject;
+ var slippageModel = InvokeMethod(nameof(GetSlippageModel), security);
if (slippageModel.TryConvert(out var csharpSlippageModel))
{
return csharpSlippageModel;
@@ -314,10 +296,7 @@ public ISlippageModel GetSlippageModel(Security security)
///
public bool Shortable(IAlgorithm algorithm, Symbol symbol, decimal quantity)
{
- using (Py.GIL())
- {
- return (_model.Shortable(algorithm, symbol, quantity) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(Shortable), algorithm, symbol, quantity);
}
///
@@ -330,7 +309,7 @@ public IBuyingPowerModel GetBuyingPowerModel(Security security)
{
using (Py.GIL())
{
- var buyingPowerModel = _model.GetBuyingPowerModel(security) as PyObject;
+ var buyingPowerModel = InvokeMethod(nameof(GetBuyingPowerModel), security);
if (buyingPowerModel.TryConvert(out var csharpBuyingPowerModel))
{
return csharpBuyingPowerModel;
@@ -348,10 +327,7 @@ public IBuyingPowerModel GetBuyingPowerModel(Security security)
[Obsolete("Flagged deprecated and will remove December 1st 2018")]
public IBuyingPowerModel GetBuyingPowerModel(Security security, AccountType accountType)
{
- using (Py.GIL())
- {
- return (_model.GetBuyingPowerModel(security, accountType) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(GetBuyingPowerModel), security, accountType);
}
///
@@ -362,7 +338,7 @@ public IShortableProvider GetShortableProvider(Security security)
{
using (Py.GIL())
{
- var shortableProvider = _model.GetShortableProvider(security) as PyObject;
+ var shortableProvider = InvokeMethod(nameof(GetShortableProvider), security);
if (shortableProvider.TryConvert(out var csharpShortableProvider))
{
return csharpShortableProvider;
@@ -379,7 +355,7 @@ public IBrokerageModel GetModel()
{
using (Py.GIL())
{
- return (_model as PyObject).AsManagedObject(typeof(IBrokerageModel)) as IBrokerageModel;
+ return Instance.AsManagedObject(typeof(IBrokerageModel)) as IBrokerageModel;
}
}
@@ -392,7 +368,7 @@ public IMarginInterestRateModel GetMarginInterestRateModel(Security security)
{
using (Py.GIL())
{
- var marginInterestRateModel = _model.GetMarginInterestRateModel(security) as PyObject;
+ var marginInterestRateModel = InvokeMethod(nameof(GetMarginInterestRateModel), security);
if (marginInterestRateModel.TryConvert(out var csharpBuyingPowerModel))
{
return csharpBuyingPowerModel;
diff --git a/Common/Python/BuyingPowerModelPythonWrapper.cs b/Common/Python/BuyingPowerModelPythonWrapper.cs
index 3c838f51d2e9..bfd25861ffd6 100644
--- a/Common/Python/BuyingPowerModelPythonWrapper.cs
+++ b/Common/Python/BuyingPowerModelPythonWrapper.cs
@@ -21,17 +21,15 @@ namespace QuantConnect.Python
///
/// Wraps a object that represents a security's model of buying power
///
- public class BuyingPowerModelPythonWrapper : IBuyingPowerModel
+ public class BuyingPowerModelPythonWrapper : BasePythonWrapper, IBuyingPowerModel
{
- private readonly dynamic _model;
-
///
/// Constructor for initializing the class with wrapped object
///
/// Represents a security's model of buying power
public BuyingPowerModelPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model.ValidateImplementationOf();
}
///
@@ -41,10 +39,7 @@ public BuyingPowerModelPythonWrapper(PyObject model)
/// The buying power available for the trade
public BuyingPower GetBuyingPower(BuyingPowerParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.GetBuyingPower(parameters) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(GetBuyingPower), parameters);
}
///
@@ -54,10 +49,7 @@ public BuyingPower GetBuyingPower(BuyingPowerParameters parameters)
/// The current leverage in the security
public decimal GetLeverage(Security security)
{
- using (Py.GIL())
- {
- return (_model.GetLeverage(security) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(GetLeverage), security);
}
///
@@ -68,11 +60,7 @@ public decimal GetLeverage(Security security)
/// Returns the maximum allowed market order quantity and if zero, also the reason
public GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.GetMaximumOrderQuantityForTargetBuyingPower(parameters)
- as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(GetMaximumOrderQuantityForTargetBuyingPower), parameters);
}
///
@@ -84,11 +72,7 @@ public GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower
public GetMaximumOrderQuantityResult GetMaximumOrderQuantityForDeltaBuyingPower(
GetMaximumOrderQuantityForDeltaBuyingPowerParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.GetMaximumOrderQuantityForDeltaBuyingPower(parameters)
- as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(GetMaximumOrderQuantityForDeltaBuyingPower), parameters);
}
///
@@ -98,11 +82,7 @@ public GetMaximumOrderQuantityResult GetMaximumOrderQuantityForDeltaBuyingPower(
/// The reserved buying power in account currency
public ReservedBuyingPowerForPosition GetReservedBuyingPowerForPosition(ReservedBuyingPowerForPositionParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.GetReservedBuyingPowerForPosition(parameters)
- as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(GetReservedBuyingPowerForPosition), parameters);
}
///
@@ -112,11 +92,7 @@ public ReservedBuyingPowerForPosition GetReservedBuyingPowerForPosition(Reserved
/// Returns buying power information for an order
public HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.HasSufficientBuyingPowerForOrder(parameters)
- as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(HasSufficientBuyingPowerForOrder), parameters);
}
///
@@ -129,10 +105,7 @@ public HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(H
/// The new leverage
public void SetLeverage(Security security, decimal leverage)
{
- using (Py.GIL())
- {
- _model.SetLeverage(security, leverage);
- }
+ InvokeMethod(nameof(SetLeverage), security, leverage);
}
///
@@ -142,11 +115,7 @@ public void SetLeverage(Security security, decimal leverage)
/// The maintenance margin required for the provided holdings quantity/cost/value
public MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.GetMaintenanceMargin(parameters) as PyObject)
- .GetAndDispose();
- }
+ return InvokeMethod(nameof(GetMaintenanceMargin), parameters);
}
///
@@ -156,11 +125,7 @@ public MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parame
/// The initial margin required for the provided security and quantity
public InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.GetInitialMarginRequirement(parameters) as PyObject)
- .GetAndDispose();
- }
+ return InvokeMethod(nameof(GetInitialMarginRequirement), parameters);
}
///
@@ -170,11 +135,7 @@ public InitialMargin GetInitialMarginRequirement(InitialMarginParameters paramet
/// The total margin in terms of the currency quoted in the order
public InitialMargin GetInitialMarginRequiredForOrder(InitialMarginRequiredForOrderParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.GetInitialMarginRequiredForOrder(parameters) as PyObject)
- .GetAndDispose();
- }
+ return InvokeMethod(nameof(GetInitialMarginRequiredForOrder), parameters);
}
}
}
diff --git a/Common/Python/DataConsolidatorPythonWrapper.cs b/Common/Python/DataConsolidatorPythonWrapper.cs
index bd8e69822dcc..ab2e5e09725f 100644
--- a/Common/Python/DataConsolidatorPythonWrapper.cs
+++ b/Common/Python/DataConsolidatorPythonWrapper.cs
@@ -23,17 +23,16 @@ namespace QuantConnect.Python
///
/// Provides an Data Consolidator that wraps a object that represents a custom Python consolidator
///
- public class DataConsolidatorPythonWrapper : IDataConsolidator
+ /// TODO: Inherit from BasePythonWrapper instead of BasePythonWrapper. But first fix ValidateImplementationOf to exclude properties getters and setters (IsSpecialName)
+ public class DataConsolidatorPythonWrapper : BasePythonWrapper, IDataConsolidator
{
- private readonly dynamic _consolidator;
-
///
/// Gets the most recently consolidated piece of data. This will be null if this consolidator
/// has not produced any data yet.
///
public IBaseData Consolidated
{
- get { using (Py.GIL()) {return _consolidator.Consolidated;} }
+ get { return GetProperty(nameof(Consolidated)); }
}
///
@@ -41,7 +40,7 @@ public IBaseData Consolidated
///
public IBaseData WorkingData
{
- get { using (Py.GIL()) {return _consolidator.WorkingData;} }
+ get { return GetProperty(nameof(WorkingData)); }
}
///
@@ -49,7 +48,7 @@ public IBaseData WorkingData
///
public Type InputType
{
- get { using (Py.GIL()) {return _consolidator.InputType;} }
+ get { return GetProperty(nameof(InputType)); }
}
///
@@ -57,7 +56,7 @@ public Type InputType
///
public Type OutputType
{
- get { using (Py.GIL()) {return _consolidator.OutputType;} }
+ get { return GetProperty(nameof(OutputType)); }
}
///
@@ -65,8 +64,16 @@ public Type OutputType
///
public event DataConsolidatedHandler DataConsolidated
{
- add { using (Py.GIL()) {_consolidator.DataConsolidated += value;} }
- remove { using (Py.GIL()) {_consolidator.DataConsolidated -= value;} }
+ add
+ {
+ var eventHandler = GetEvent(nameof(DataConsolidated));
+ eventHandler += value;
+ }
+ remove
+ {
+ var eventHandler = GetEvent(nameof(DataConsolidated));
+ eventHandler -= value;
+ }
}
///
@@ -74,19 +81,16 @@ public event DataConsolidatedHandler DataConsolidated
///
/// Represents a custom python consolidator
public DataConsolidatorPythonWrapper(PyObject consolidator)
+ : base(consolidator, false)
{
- using (Py.GIL())
+ foreach (var attributeName in new[] { "InputType", "OutputType", "WorkingData", "Consolidated" })
{
- foreach (var attributeName in new[] { "InputType", "OutputType", "WorkingData", "Consolidated" })
+ if (!HasAttr(attributeName))
{
- if (!consolidator.HasAttr(attributeName))
- {
- throw new NotImplementedException(
- Messages.PythonCommon.AttributeNotImplemented($"IDataConsolidator.{attributeName}", consolidator.GetPythonType()));
- }
+ throw new NotImplementedException(
+ Messages.PythonCommon.AttributeNotImplemented($"IDataConsolidator.{attributeName}", consolidator.GetPythonType()));
}
}
- _consolidator = consolidator;
}
///
@@ -95,10 +99,7 @@ public DataConsolidatorPythonWrapper(PyObject consolidator)
/// The current time in the local time zone (same as )
public void Scan(DateTime currentLocalTime)
{
- using (Py.GIL())
- {
- _consolidator.Scan(currentLocalTime);
- }
+ InvokeMethod(nameof(Scan), currentLocalTime);
}
///
@@ -107,10 +108,7 @@ public void Scan(DateTime currentLocalTime)
/// The new data for the consolidator
public void Update(IBaseData data)
{
- using (Py.GIL())
- {
- _consolidator.Update(data);
- }
+ InvokeMethod(nameof(Update), data);
}
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
diff --git a/Common/Python/DividendYieldModelPythonWrapper.cs b/Common/Python/DividendYieldModelPythonWrapper.cs
index 86b2021f72dc..d51bb1f25021 100644
--- a/Common/Python/DividendYieldModelPythonWrapper.cs
+++ b/Common/Python/DividendYieldModelPythonWrapper.cs
@@ -22,17 +22,15 @@ namespace QuantConnect.Python
///
/// Wraps a object that represents a dividend yield model
///
- public class DividendYieldModelPythonWrapper : IDividendYieldModel
+ public class DividendYieldModelPythonWrapper : BasePythonWrapper, IDividendYieldModel
{
- private readonly dynamic _model;
-
///
/// Constructor for initializing the class with wrapped object
///
/// Represents a security's model of dividend yield
public DividendYieldModelPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model.ValidateImplementationOf();
}
///
@@ -42,8 +40,7 @@ public DividendYieldModelPythonWrapper(PyObject model)
/// Dividend yield on the given date of the given symbol
public decimal GetDividendYield(DateTime date)
{
- using var _ = Py.GIL();
- return (_model.GetDividendYield(date) as PyObject).GetAndDispose();
+ return InvokeMethod(nameof(GetDividendYield), date);
}
///
diff --git a/Common/Python/FeeModelPythonWrapper.cs b/Common/Python/FeeModelPythonWrapper.cs
index 962a9101ed28..0ea61649c61e 100644
--- a/Common/Python/FeeModelPythonWrapper.cs
+++ b/Common/Python/FeeModelPythonWrapper.cs
@@ -1,4 +1,4 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -24,7 +24,7 @@ namespace QuantConnect.Python
///
public class FeeModelPythonWrapper : FeeModel
{
- private readonly dynamic _model;
+ private readonly BasePythonWrapper _model;
private bool _extendedVersion = true;
///
@@ -33,7 +33,7 @@ public class FeeModelPythonWrapper : FeeModel
/// Represents a model that simulates order fees
public FeeModelPythonWrapper(PyObject model)
{
- _model = model;
+ _model = new BasePythonWrapper(model, false);
}
///
@@ -50,17 +50,16 @@ public override OrderFee GetOrderFee(OrderFeeParameters parameters)
{
try
{
- return (_model.GetOrderFee(parameters) as PyObject).GetAndDispose();
+ return _model.InvokeMethod(nameof(GetOrderFee), parameters);
}
catch (PythonException)
{
_extendedVersion = false;
}
}
- var fee = (_model.GetOrderFee(parameters.Security, parameters.Order)
- as PyObject).GetAndDispose();
+ var fee = _model.InvokeMethod(nameof(GetOrderFee), parameters.Security, parameters.Order);
return new OrderFee(new CashAmount(fee, "USD"));
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Common/Python/FillModelPythonWrapper.cs b/Common/Python/FillModelPythonWrapper.cs
index ce39b236ae34..0c4ba6c5b08c 100644
--- a/Common/Python/FillModelPythonWrapper.cs
+++ b/Common/Python/FillModelPythonWrapper.cs
@@ -26,7 +26,7 @@ namespace QuantConnect.Python
///
public class FillModelPythonWrapper : FillModel
{
- private readonly dynamic _model;
+ private readonly BasePythonWrapper _model;
///
/// Constructor for initialising the class with wrapped object
@@ -34,10 +34,10 @@ public class FillModelPythonWrapper : FillModel
/// Represents a model that simulates order fill events
public FillModelPythonWrapper(PyObject model)
{
- _model = model;
+ _model = new BasePythonWrapper(model, false);
using (Py.GIL())
{
- _model.SetPythonWrapper(this);
+ (model as dynamic).SetPythonWrapper(this);
}
}
@@ -49,10 +49,7 @@ public FillModelPythonWrapper(PyObject model)
public override Fill Fill(FillModelParameters parameters)
{
Parameters = parameters;
- using (Py.GIL())
- {
- return (_model.Fill(parameters) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(Fill), parameters);
}
///
@@ -63,10 +60,7 @@ public override Fill Fill(FillModelParameters parameters)
/// Order fill information detailing the average price and quantity filled.
public override OrderEvent LimitFill(Security asset, LimitOrder order)
{
- using (Py.GIL())
- {
- return (_model.LimitFill(asset, order) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(LimitFill), asset, order);
}
///
@@ -77,10 +71,7 @@ public override OrderEvent LimitFill(Security asset, LimitOrder order)
/// Order fill information detailing the average price and quantity filled.
public override OrderEvent LimitIfTouchedFill(Security asset, LimitIfTouchedOrder order)
{
- using (Py.GIL())
- {
- return (_model.LimitIfTouchedFill(asset, order) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(LimitIfTouchedFill), asset, order);
}
///
@@ -91,10 +82,7 @@ public override OrderEvent LimitIfTouchedFill(Security asset, LimitIfTouchedOrde
/// Order fill information detailing the average price and quantity filled.
public override OrderEvent MarketFill(Security asset, MarketOrder order)
{
- using (Py.GIL())
- {
- return (_model.MarketFill(asset, order) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(MarketFill), asset, order);
}
///
@@ -105,10 +93,7 @@ public override OrderEvent MarketFill(Security asset, MarketOrder order)
/// Order fill information detailing the average price and quantity filled.
public override OrderEvent MarketOnCloseFill(Security asset, MarketOnCloseOrder order)
{
- using (Py.GIL())
- {
- return (_model.MarketOnCloseFill(asset, order) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(MarketOnCloseFill), asset, order);
}
///
@@ -119,10 +104,7 @@ public override OrderEvent MarketOnCloseFill(Security asset, MarketOnCloseOrder
/// Order fill information detailing the average price and quantity filled.
public override OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder order)
{
- using (Py.GIL())
- {
- return (_model.MarketOnOpenFill(asset, order) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(MarketOnOpenFill), asset, order);
}
///
@@ -133,10 +115,7 @@ public override OrderEvent MarketOnOpenFill(Security asset, MarketOnOpenOrder or
/// Order fill information detailing the average price and quantity filled.
public override OrderEvent StopLimitFill(Security asset, StopLimitOrder order)
{
- using (Py.GIL())
- {
- return (_model.StopLimitFill(asset, order) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(StopLimitFill), asset, order);
}
///
@@ -147,10 +126,7 @@ public override OrderEvent StopLimitFill(Security asset, StopLimitOrder order)
/// Order fill information detailing the average price and quantity filled.
public override OrderEvent StopMarketFill(Security asset, StopMarketOrder order)
{
- using (Py.GIL())
- {
- return (_model.StopMarketFill(asset, order) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(StopMarketFill), asset, order);
}
///
@@ -161,10 +137,7 @@ public override OrderEvent StopMarketFill(Security asset, StopMarketOrder order)
/// Order fill information detailing the average price and quantity filled.
public override OrderEvent TrailingStopFill(Security asset, TrailingStopOrder order)
{
- using (Py.GIL())
- {
- return (_model.TrailingStopFill(asset, order) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(TrailingStopFill), asset, order);
}
///
@@ -175,10 +148,7 @@ public override OrderEvent TrailingStopFill(Security asset, TrailingStopOrder or
/// Order fill information detailing the average price and quantity filled for each leg. If any of the fills fails, none of the orders will be filled and the returned list will be empty
public override List ComboMarketFill(Order order, FillModelParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.ComboMarketFill(order, parameters) as PyObject).GetAndDispose>();
- }
+ return _model.InvokeMethod>(nameof(ComboMarketFill), order, parameters);
}
///
@@ -189,10 +159,7 @@ public override List ComboMarketFill(Order order, FillModelParameter
/// Order fill information detailing the average price and quantity filled for each leg. If any of the fills fails, none of the orders will be filled and the returned list will be empty
public override List ComboLimitFill(Order order, FillModelParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.ComboLimitFill(order, parameters) as PyObject).GetAndDispose>();
- }
+ return _model.InvokeMethod>(nameof(ComboLimitFill), order, parameters);
}
///
@@ -203,10 +170,7 @@ public override List ComboLimitFill(Order order, FillModelParameters
/// Order fill information detailing the average price and quantity filled for each leg. If any of the fills fails, none of the orders will be filled and the returned list will be empty
public override List ComboLegLimitFill(Order order, FillModelParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.ComboLegLimitFill(order, parameters) as PyObject).GetAndDispose>();
- }
+ return _model.InvokeMethod>(nameof(ComboLegLimitFill), order, parameters);
}
///
@@ -216,10 +180,7 @@ public override List ComboLegLimitFill(Order order, FillModelParamet
/// The order direction, decides whether to pick bid or ask
protected override Prices GetPrices(Security asset, OrderDirection direction)
{
- using (Py.GIL())
- {
- return (_model.GetPrices(asset, direction) as PyObject).GetAndDispose();
- }
+ return _model.InvokeMethod(nameof(GetPrices), asset, direction);
}
///
diff --git a/Common/Python/MarginCallModelPythonWrapper.cs b/Common/Python/MarginCallModelPythonWrapper.cs
index 70e82fa7ca42..48a1e492bd00 100644
--- a/Common/Python/MarginCallModelPythonWrapper.cs
+++ b/Common/Python/MarginCallModelPythonWrapper.cs
@@ -24,28 +24,15 @@ namespace QuantConnect.Python
///
/// Provides a margin call model that wraps a object that represents the model responsible for picking which orders should be executed during a margin call
///
- public class MarginCallModelPythonWrapper : IMarginCallModel
+ public class MarginCallModelPythonWrapper : BasePythonWrapper, IMarginCallModel
{
- private readonly dynamic _model;
-
///
/// Constructor for initialising the class with wrapped object
///
/// Represents the model responsible for picking which orders should be executed during a margin call
public MarginCallModelPythonWrapper(PyObject model)
+ : base(model)
{
- using (Py.GIL())
- {
- foreach (var attributeName in new[] { "ExecuteMarginCall", "GetMarginCallOrders" })
- {
- if (!model.HasAttr(attributeName))
- {
- throw new NotImplementedException(
- Messages.PythonCommon.AttributeNotImplemented($"IMarginCallModel.{attributeName}", model.GetPythonType()));
- }
- }
- }
- _model = model;
}
///
@@ -58,7 +45,7 @@ public List ExecuteMarginCall(IEnumerable gener
{
using (Py.GIL())
{
- var marginCalls = _model.ExecuteMarginCall(generatedMarginCallOrders) as PyObject;
+ var marginCalls = InvokeMethod(nameof(ExecuteMarginCall), generatedMarginCallOrders);
// Since ExecuteMarginCall may return a python list
// Need to convert to C# list
@@ -89,13 +76,13 @@ public List GetMarginCallOrders(out bool issueMarginCallWarn
{
using (Py.GIL())
{
- var value = _model.GetMarginCallOrders(out issueMarginCallWarning);
+ var value = InvokeMethod(nameof(GetMarginCallOrders), false);
// Since pythonnet does not support out parameters, the methods return
// a tuple where the out parameter comes after the other returned values
if (!PyTuple.IsTupleType(value))
{
- throw new ArgumentException($@"{_model.__class__.__name__}.GetMarginCallOrders(): {
+ throw new ArgumentException($@"{(Instance as dynamic).__class__.__name__}.GetMarginCallOrders(): {
Messages.MarginCallModelPythonWrapper.GetMarginCallOrdersMustReturnTuple}");
}
diff --git a/Common/Python/MarginInterestRateModelPythonWrapper.cs b/Common/Python/MarginInterestRateModelPythonWrapper.cs
index 25ec02b8380f..274ddc075ebf 100644
--- a/Common/Python/MarginInterestRateModelPythonWrapper.cs
+++ b/Common/Python/MarginInterestRateModelPythonWrapper.cs
@@ -21,17 +21,15 @@ namespace QuantConnect.Python
///
/// Wraps a object that represents a security's margin interest rate model
///
- public class MarginInterestRateModelPythonWrapper : IMarginInterestRateModel
+ public class MarginInterestRateModelPythonWrapper : BasePythonWrapper, IMarginInterestRateModel
{
- private readonly dynamic _model;
-
///
/// Constructor for initializing the class with wrapped object
///
/// Represents a security's model of buying power
public MarginInterestRateModelPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model.ValidateImplementationOf();
}
///
@@ -40,10 +38,7 @@ public MarginInterestRateModelPythonWrapper(PyObject model)
/// The parameters to use
public void ApplyMarginInterestRate(MarginInterestRateParameters parameters)
{
- using (Py.GIL())
- {
- _model.ApplyMarginInterestRate(parameters);
- }
+ InvokeMethod(nameof(ApplyMarginInterestRate), parameters);
}
}
}
diff --git a/Common/Python/OptionAssignmentModelPythonWrapper.cs b/Common/Python/OptionAssignmentModelPythonWrapper.cs
index a8ee904e96f3..b8940c85bb53 100644
--- a/Common/Python/OptionAssignmentModelPythonWrapper.cs
+++ b/Common/Python/OptionAssignmentModelPythonWrapper.cs
@@ -21,17 +21,15 @@ namespace QuantConnect.Python
///
/// Python wrapper for custom option assignment models
///
- public class OptionAssignmentModelPythonWrapper : IOptionAssignmentModel
+ public class OptionAssignmentModelPythonWrapper : BasePythonWrapper, IOptionAssignmentModel
{
- private readonly dynamic _model;
-
///
/// Creates a new instance
///
/// The python model to wrapp
public OptionAssignmentModelPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model.ValidateImplementationOf();
}
///
@@ -41,10 +39,7 @@ public OptionAssignmentModelPythonWrapper(PyObject model)
/// The option assignment result
public OptionAssignmentResult GetAssignment(OptionAssignmentParameters parameters)
{
- using (Py.GIL())
- {
- return (_model.GetAssignment(parameters) as PyObject).GetAndDispose();
- }
+ return InvokeMethod(nameof(GetAssignment), parameters);
}
}
}
diff --git a/Common/Python/PythonWrapper.cs b/Common/Python/PythonWrapper.cs
index a921d82aaf5f..6f454b40bc4f 100644
--- a/Common/Python/PythonWrapper.cs
+++ b/Common/Python/PythonWrapper.cs
@@ -46,7 +46,7 @@ public static PyObject ValidateImplementationOf(this PyObject model)
{
foreach (var member in members)
{
- if (!model.HasAttr(member.Name))
+ if (!model.HasAttr(member.Name) && !model.HasAttr(member.Name.ToSnakeCase()))
{
missingMembers.Add(member.Name);
}
@@ -61,5 +61,64 @@ public static PyObject ValidateImplementationOf(this PyObject model)
return model;
}
+
+ ///
+ /// Invokes the specified method on the provided instance with the specified arguments
+ ///
+ /// The instance
+ /// The name of the method to invoke
+ /// The arguments to call the method with
+ /// The return value of the called method converted into the type
+ public static T InvokeMethod(this PyObject model, string methodName, params object[] args)
+ {
+ using var _ = Py.GIL();
+ return InvokeMethodImpl(model, methodName, args).GetAndDispose();
+ }
+
+ ///
+ /// Invokes the specified method on the provided instance with the specified arguments
+ ///
+ /// The instance
+ /// The name of the method to invoke
+ /// The arguments to call the method with
+ public static void InvokeMethod(this PyObject model, string methodName, params object[] args)
+ {
+ InvokeMethodImpl(model, methodName, args);
+ }
+
+ ///
+ /// Invokes the given method with the specified arguments
+ ///
+ /// The method to invoke
+ /// The arguments to call the method with
+ /// The return value of the called method converted into the type
+ public static T Invoke(this PyObject method, params object[] args)
+ {
+ using var _ = Py.GIL();
+ return InvokeMethodImpl(method, args).GetAndDispose();
+ }
+
+ ///
+ /// Invokes the given method with the specified arguments
+ ///
+ /// The method to invoke
+ /// The arguments to call the method with
+ public static PyObject Invoke(this PyObject method, params object[] args)
+ {
+ return InvokeMethodImpl(method, args);
+ }
+
+ private static PyObject InvokeMethodImpl(PyObject model, string methodName, params object[] args)
+ {
+ using var _ = Py.GIL();
+ PyObject method = model.GetMethod(methodName);
+ return InvokeMethodImpl(method, args);
+ }
+
+ private static PyObject InvokeMethodImpl(PyObject method, params object[] args)
+ {
+ using var _ = Py.GIL();
+ return method.Invoke(args.Select(arg => arg.ToPython()).ToArray());
+ }
}
}
diff --git a/Common/Python/RiskFreeInterestRateModelPythonWrapper.cs b/Common/Python/RiskFreeInterestRateModelPythonWrapper.cs
index d8c31a7c8944..1eb58a97fa3a 100644
--- a/Common/Python/RiskFreeInterestRateModelPythonWrapper.cs
+++ b/Common/Python/RiskFreeInterestRateModelPythonWrapper.cs
@@ -22,17 +22,15 @@ namespace QuantConnect.Python
///
/// Wraps a object that represents a risk-free interest rate model
///
- public class RiskFreeInterestRateModelPythonWrapper : IRiskFreeInterestRateModel
+ public class RiskFreeInterestRateModelPythonWrapper : BasePythonWrapper, IRiskFreeInterestRateModel
{
- private readonly dynamic _model;
-
///
/// Constructor for initializing the class with wrapped object
///
/// Represents a security's model of buying power
public RiskFreeInterestRateModelPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model.ValidateImplementationOf();
}
///
@@ -42,8 +40,7 @@ public RiskFreeInterestRateModelPythonWrapper(PyObject model)
/// Interest rate on the given date
public decimal GetInterestRate(DateTime date)
{
- using var _ = Py.GIL();
- return (_model.GetInterestRate(date) as PyObject).GetAndDispose();
+ return InvokeMethod(nameof(GetInterestRate), date);
}
///
diff --git a/Common/Python/SecurityInitializerPythonWrapper.cs b/Common/Python/SecurityInitializerPythonWrapper.cs
index 798ab9548db0..52c5260d49f7 100644
--- a/Common/Python/SecurityInitializerPythonWrapper.cs
+++ b/Common/Python/SecurityInitializerPythonWrapper.cs
@@ -1,4 +1,4 @@
-/*
+/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -21,17 +21,15 @@ namespace QuantConnect.Python
///
/// Wraps a object that represents a type capable of initializing a new security
///
- public class SecurityInitializerPythonWrapper : ISecurityInitializer
+ public class SecurityInitializerPythonWrapper : BasePythonWrapper, ISecurityInitializer
{
- private readonly dynamic _model;
-
///
/// Constructor for initialising the class with wrapped object
///
/// Represents a type capable of initializing a new security
public SecurityInitializerPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model;
}
///
@@ -40,10 +38,7 @@ public SecurityInitializerPythonWrapper(PyObject model)
/// The security to be initialized
public void Initialize(Security security)
{
- using (Py.GIL())
- {
- _model.Initialize(security);
- }
+ InvokeMethod(nameof(Initialize), security);
}
}
-}
\ No newline at end of file
+}
diff --git a/Common/Python/SettlementModelPythonWrapper.cs b/Common/Python/SettlementModelPythonWrapper.cs
index aac75b3a9c1c..3b621a61af33 100644
--- a/Common/Python/SettlementModelPythonWrapper.cs
+++ b/Common/Python/SettlementModelPythonWrapper.cs
@@ -22,16 +22,14 @@ namespace QuantConnect.Python
///
/// Provides an implementation of that wraps a object
///
- public class SettlementModelPythonWrapper : ISettlementModel
+ public class SettlementModelPythonWrapper : BasePythonWrapper, ISettlementModel
{
- private readonly dynamic _model;
-
/// Constructor for initialising the class with wrapped object
///
/// Settlement Python Model
public SettlementModelPythonWrapper(PyObject model)
+ : base(model)
{
- _model = model.ValidateImplementationOf();
}
///
@@ -40,10 +38,7 @@ public SettlementModelPythonWrapper(PyObject model)
/// The funds application parameters
public void ApplyFunds(ApplyFundsSettlementModelParameters applyFundsParameters)
{
- using (Py.GIL())
- {
- _model.ApplyFunds(applyFundsParameters);
- }
+ InvokeMethod(nameof(ApplyFunds), applyFundsParameters);
}
///
@@ -52,10 +47,7 @@ public void ApplyFunds(ApplyFundsSettlementModelParameters applyFundsParameters)
/// The settlement parameters
public void Scan(ScanSettlementModelParameters settlementParameters)
{
- using (Py.GIL())
- {
- _model.Scan(settlementParameters);
- }
+ InvokeMethod(nameof(Scan), settlementParameters);
}
///
@@ -65,13 +57,13 @@ public CashAmount GetUnsettledCash()
{
using (Py.GIL())
{
- var result = _model.GetUnsettledCash();
+ var result = InvokeMethod