Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enables custom buying power model setting in python #2425

Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
+322 −2
Diff settings

Always

Just for now

@@ -45,7 +45,7 @@ public override void Initialize()

// The default buying power model for the Crypto security type is now CashBuyingPowerModel.
// Since this test algorithm uses leverage we need to set a buying power model with margin.
security.BuyingPowerModel = new SecurityMarginModel(3.3m);
security.SetBuyingPowerModel(new SecurityMarginModel(3.3m));

var con = new TradeBarConsolidator(1);
SubscriptionManager.AddConsolidator("BTCUSD", con);
@@ -52,7 +52,7 @@ def Initialize(self):

### The default buying power model for the Crypto security type is now CashBuyingPowerModel.
### Since this test algorithm uses leverage we need to set a buying power model with margin.
security.BuyingPowerModel = SecurityMarginModel(3.3)
security.SetBuyingPowerModel(SecurityMarginModel(3.3))

con = TradeBarConsolidator(timedelta(1))
self.SubscriptionManager.AddConsolidator("BTCUSD", con)
@@ -0,0 +1,136 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using Python.Runtime;
using QuantConnect.Orders;
using QuantConnect.Securities;
using System;

namespace QuantConnect.Python
{
/// <summary>
/// Wraps a <see cref="PyObject"/> object that represents a security's model of buying power
/// </summary>
public class BuyingPowerModelPythonWrapper : IBuyingPowerModel
{
private readonly dynamic _model;

/// <summary>
/// Constructor for initialising the <see cref="BuyingPowerModelPythonWrapper"/> class with wrapped <see cref="PyObject"/> object
/// </summary>
/// <param name="model">Represents a security's model of buying power</param>
public BuyingPowerModelPythonWrapper(PyObject model)
{
using (Py.GIL())
{
foreach (var attributeName in new[] { "GetBuyingPower", "GetLeverage", "GetMaximumOrderQuantityForTargetValue", "GetReservedBuyingPowerForPosition", "HasSufficientBuyingPowerForOrder", "SetLeverage" })
{
if (!model.HasAttr(attributeName))
{
throw new NotImplementedException($"IBuyingPowerModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}");
}
}
}
_model = model;
}

/// <summary>
/// Gets the buying power available for a trade
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security to be traded</param>
/// <param name="direction">The direction of the trade</param>
/// <returns>The buying power available for the trade</returns>
public decimal GetBuyingPower(SecurityPortfolioManager portfolio, Security security, OrderDirection direction)
{
using (Py.GIL())
{
return _model.GetBuyingPower(portfolio, security, direction);
}
}

/// <summary>
/// Gets the current leverage of the security
/// </summary>
/// <param name="security">The security to get leverage for</param>
/// <returns>The current leverage in the security</returns>
public decimal GetLeverage(Security security)
{
using (Py.GIL())
{
return _model.GetLeverage(security);
}
}

/// <summary>
/// Get the maximum market order quantity to obtain a position with a given value in account currency
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security to be traded</param>
/// <param name="target">Target percentage holdings</param>
/// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
public GetMaximumOrderQuantityForTargetValueResult GetMaximumOrderQuantityForTargetValue(SecurityPortfolioManager portfolio, Security security, decimal target)
{
using (Py.GIL())
{
return _model.GetMaximumOrderQuantityForTargetValue(portfolio, security, target);
}
}

/// <summary>
/// Gets the amount of buying power reserved to maintain the specified position
/// </summary>
/// <param name="security">The security for the position</param>
/// <returns>The reserved buying power in account currency</returns>
public decimal GetReservedBuyingPowerForPosition(Security security)
{
using (Py.GIL())
{
return _model.GetReservedBuyingPowerForPosition(security);
}
}

/// <summary>
/// Check if there is sufficient buying power to execute this order.
/// </summary>
/// <param name="portfolio">The algorithm's portfolio</param>
/// <param name="security">The security to be traded</param>
/// <param name="order">The order to be checked</param>
/// <returns>Returns buying power information for an order</returns>
public HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(SecurityPortfolioManager portfolio, Security security, Order order)
{
using (Py.GIL())
{
return _model.HasSufficientBuyingPowerForOrder(portfolio, security, order);
}
}

/// <summary>
/// Sets the leverage for the applicable securities, i.e, equities
/// </summary>
/// <remarks>
/// This is added to maintain backwards compatibility with the old margin/leverage system
/// </remarks>
/// <param name="security">The security to set leverage for</param>
/// <param name="leverage">The new leverage</param>
public void SetLeverage(Security security, decimal leverage)
{
using (Py.GIL())
{
_model.SetLeverage(security, leverage);
}
}
}
}
Copy path View file
@@ -179,6 +179,7 @@
<Compile Include="Brokerages\GDAXBrokerageModel.cs" />
<Compile Include="Chart.cs" />
<Compile Include="ChartPoint.cs" />
<Compile Include="Python\BuyingPowerModelPythonWrapper.cs" />
<Compile Include="Series.cs" />
<Compile Include="Data\Custom\Intrinio\IntrinioConfig.cs" />
<Compile Include="Data\Custom\Intrinio\IntrinioEconomicData.cs" />
Copy path View file
@@ -711,6 +711,42 @@ public void SetVolatilityModel(PyObject volatilityModel)
VolatilityModel = new VolatilityModelPythonWrapper(volatilityModel);
}

/// <summary>
/// Sets the buying power model
/// </summary>
/// <param name="buyingPowerModel">Model that represents a security's model of buying power</param>
public void SetBuyingPowerModel(IBuyingPowerModel buyingPowerModel)
{
BuyingPowerModel = buyingPowerModel;
}

/// <summary>
/// Sets the buying power model
/// </summary>
/// <param name="pyObject">Model that represents a security's model of buying power</param>
public void SetBuyingPowerModel(PyObject pyObject)
{
SetBuyingPowerModel(new BuyingPowerModelPythonWrapper(pyObject));
}

/// <summary>
/// Sets the margin model
/// </summary>
/// <param name="marginModel">Model that represents a security's model of buying power</param>
public void SetMarginModel(IBuyingPowerModel marginModel)
{
MarginModel = marginModel;
}

/// <summary>
/// Sets the margin model
/// </summary>
/// <param name="pyObject">Model that represents a security's model of buying power</param>
public void SetMarginModel(PyObject pyObject)
{
SetMarginModel(new BuyingPowerModelPythonWrapper(pyObject));
}

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
@@ -0,0 +1,146 @@
/*
* 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 NUnit.Framework;
using Python.Runtime;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Python;
using QuantConnect.Securities;
using QuantConnect.Securities.Equity;
using QuantConnect.Tests.Common.Securities;
using System;

namespace QuantConnect.Tests.Python
{
[TestFixture]
public class SecurityCustomModelTests
{
[Test]
[TestCase(true)]
[TestCase(false)]
public void SetBuyingPowerModelSuccess(bool isChild)
{
var algorithm = new QCAlgorithm();
algorithm.SetDateTime(new DateTime(2018, 8, 20, 15, 0, 0));
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());

var spy = algorithm.AddEquity("SPY", Resolution.Daily);
spy.SetMarketPrice(new Tick(algorithm.Time, Symbols.SPY, 100m, 100m));

// Test two custom buying power models.
// The first inherits from C# SecurityMarginModel and the other is 100% python
var code = isChild
? CreateCustomBuyingPowerModelFromSecurityMarginModelCode()
: CreateCustomBuyingPowerModelCode();

spy.SetBuyingPowerModel(CreateCustomBuyingPowerModel(code));
Assert.IsAssignableFrom<BuyingPowerModelPythonWrapper>(spy.MarginModel);
Assert.AreEqual(1, spy.MarginModel.GetLeverage(spy));

spy.SetLeverage(2);
Assert.AreEqual(2, spy.MarginModel.GetLeverage(spy));

var quantity = algorithm.CalculateOrderQuantity(spy.Symbol, 1m);
Assert.AreEqual(isChild ? 100 : 200, quantity);
}

[Test]
public void SetBuyingPowerModelFails()
{
var spy = GetSecurity<Equity>(Symbols.SPY, Resolution.Daily);

// Renaming GetBuyingPower will cause a NotImplementedException exception
var code = CreateCustomBuyingPowerModelCode();
code = code.Replace("GetBuyingPower", "SetBuyingPower");
var pyObject = CreateCustomBuyingPowerModel(code);
Assert.Throws<NotImplementedException>(() => spy.SetBuyingPowerModel(pyObject));
}

private PyObject CreateCustomBuyingPowerModel(string code)
{
using (Py.GIL())
{
var module = PythonEngine.ModuleFromString("CustomBuyingPowerModel", code);
return module.GetAttr("CustomBuyingPowerModel").Invoke();
}
}

private string CreateCustomBuyingPowerModelCode() => @"
import os, sys
sys.path.append(os.getcwd())
from clr import AddReference
AddReference('QuantConnect.Common')
from QuantConnect import *
from QuantConnect.Securities import *
class CustomBuyingPowerModel:
def __init__(self):
self.margin = 1.0
def GetBuyingPower(self, portfolio, security, direction):
return portfolio.MarginRemaining
def GetLeverage(self, security):
return 1.0 / self.margin
def GetMaximumOrderQuantityForTargetValue(self, portfolio, security, order):
return GetMaximumOrderQuantityForTargetValueResult(200)
def GetReservedBuyingPowerForPosition(self, security):
return security.Holdings.AbsoluteHoldingsCost * self.margin
def HasSufficientBuyingPowerForOrder(self, portfolio, security, order):
return HasSufficientBuyingPowerForOrderResult(True)
def SetLeverage(self, security, leverage):
self.margin = 1.0 / float(leverage)";

private string CreateCustomBuyingPowerModelFromSecurityMarginModelCode() => @"
import os, sys
sys.path.append(os.getcwd())
from clr import AddReference
AddReference('QuantConnect.Common')
from QuantConnect import *
from QuantConnect.Securities import *
class CustomBuyingPowerModel(SecurityMarginModel):
def GetMaximumOrderQuantityForTargetValue(self, portfolio, security, order):
return GetMaximumOrderQuantityForTargetValueResult(100)";

private Security GetSecurity<T>(Symbol symbol, Resolution resolution)
{
var subscriptionDataConfig = new SubscriptionDataConfig(
typeof(T),
symbol,
resolution,
TimeZones.Utc,
TimeZones.Utc,
true,
true,
false);

return new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
subscriptionDataConfig,
new Cash(CashBook.AccountCurrency, 0, 1m),
SymbolProperties.GetDefault(CashBook.AccountCurrency));
}
}
}
@@ -385,6 +385,7 @@
<Compile Include="Messaging\StreamingMessageHandlerTests.cs" />
<Compile Include="Python\AlgorithmPythonWrapperTests.cs" />
<Compile Include="Python\MethodOverloadTests.cs" />
<Compile Include="Python\SecurityCustomModelTests.cs" />
<Compile Include="Python\PandasConverterTests.cs" />
<Compile Include="RegressionTests.cs" />
<Compile Include="Symbols.cs" />
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.