Hi everyone,
I've been encountering some issues with error messages similar to the following: Insufficient buying power to complete order (Value:7270.56), Reason: Id: 1233, Initial Margin: 7274.64, Free Margin: 4408.0538.
I thought that it might be caused by slippage since the order will only be excuted the next day. In addition, it targets microcap equities so there is a high chance that the default margin would be insufficient to account for the ask/bid spread. To test if these were indeed the issues, I set the following:
self.Settings.FreePortfolioValuePercentage = 0.6
I believe this means that the cash buffer is set to 60% (correct me if I'm wrong). However, the error still persisted.
Digging around a bit, I found out that apparently Algorithm.Settings.FreePortfolioValuePercentage sets the buffer cash value to a static value, so the cash buffer percentage does not follow the growth in portfolio value. The details of the issue can be found in the link here:
https://github.com/QuantConnect/Lean/issues/4104I tried to reassign the cash buffer value on every rebalancing cycle (3 months) in the OnSecuritiesChanged function using two different methods, neither of which worked. I would really appreciate it if anyone could help me. Thank you!
from QuantConnect import *
from QuantConnect.Parameters import *
from QuantConnect.Benchmarks import *
from QuantConnect.Brokerages import *
from QuantConnect.Util import *
from QuantConnect.Interfaces import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Selection import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Algorithm.Framework.Portfolio import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Risk import *
from QuantConnect.Indicators import *
from QuantConnect.Data import *
from QuantConnect.Data.Consolidators import *
from QuantConnect.Data.Custom import *
from QuantConnect.Data.Fundamental import *
from QuantConnect.Data.Market import *
from QuantConnect.Data.UniverseSelection import *
from QuantConnect.Notifications import *
from QuantConnect.Orders import *
from QuantConnect.Orders.Fees import *
from QuantConnect.Orders.Fills import *
from QuantConnect.Orders.Slippage import *
from QuantConnect.Scheduling import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Equity import *
from QuantConnect.Securities.Forex import *
from QuantConnect.Securities.Interfaces import *
from datetime import date, datetime, timedelta
from QuantConnect.Python import *
from QuantConnect.Storage import *
QCAlgorithmFramework = QCAlgorithm
QCAlgorithmFrameworkBridge = QCAlgorithm
import math
import numpy as np
import pandas as pd
import scipy as sp
class MicroGrowth(QCAlgorithm):
def Initialize(self):
#self.SetStartDate(2020, 2, 12) # Set Start Date
self.SetStartDate(2011, 2, 28)
self.SetEndDate(2021, 3, 1)
self.SetCash(100000) # Set Strategy Cash
#self.Settings.FreePortfolioValuePercentage = 0.6
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.lastmonth = -1
self.lastday = -1
self.monthinterval = 1
self.Symbols = None
self.tobeliquidated = None
self.numsecurities = 25
#self.SetWarmUp(timedelta(365))
def CoarseSelectionFunction(self,coarse):
if self.IsWarmingUp: return
if self.lastmonth == -1 or self.Time.month != self.lastmonth:
self.lastmonth = self.Time.month
self.lastday = self.Time.day
return [x.Symbol for x in coarse if x.HasFundamentalData]
else:
return Universe.Unchanged
def FineSelectionFunction(self,fine):
#momo_dict = {}
security_momo_list = []
MKTCAP_dict = {}
#exclude delisted and TOPS (due to split value issue)
excluded_delisted = [i for i in fine if isinstance(i.SecurityReference.DelistingDate.date(),datetime) == False and i.Symbol.Value != "TOPS"]
#filter by mkt_cap
for i in fine:
if isinstance(i.MarketCap,(float,int)) and i.MarketCap != 0:
MKTCAP_dict[i]=i.MarketCap
microcap = [i for i in excluded_delisted if isinstance(MKTCAP_dict.get(i),(int,float)) and MKTCAP_dict.get(i)>25e6 and MKTCAP_dict.get(i)<250e6]
#filter by Price-to-Sales Ratio < 1 (defined to be null if result <= 0)
micro_PSR = [i for i in microcap if isinstance(i.ValuationRatios.PSRatio,(float,int)) and i.ValuationRatios.PSRatio < 1 and i.ValuationRatios.PSRatio > 0]
#sorting by momentum
for i in micro_PSR:
hist = self.History(i.Symbol, 180 * self.monthinterval + 1, Resolution.Daily)
if 'close' not in list(hist.columns):
self.Debug(f'{i.Symbol.Value} DOES NOT HAVE "close". List of headers: {list(hist.columns)}')
continue
close_list = hist['close'].tolist()
#self.Error(f'{i.Symbol.Value} INDICES: {[col for col in hist.columns]}')
if len(close_list) == 180 *self.monthinterval + 1:
#self.Debug(f'LENGTH IS: {len(close_list)}')
curr_price = close_list[-1]
price_6M = close_list[0]
price_2M = close_list[60*self.monthinterval]
price_1M = close_list[30*self.monthinterval]
#if i.Symbol.Value == "TOPS":
# self.Log(f'CURRENT PRICE: {curr_price}, BASELINE PRICE: {baseline_price}')
momo_1M = curr_price/price_1M
momo_2M = curr_price/price_2M/2
momo_6M = curr_price/price_6M/6
if momo_1M > momo_2M and momo_2M > momo_6M:
security_momo_list.append([i.Symbol,momo_1M])
security_momo_list_sorted = sorted(security_momo_list,key = lambda i : i[1],reverse = True)
output = [f[0] for f in security_momo_list_sorted[:self.numsecurities]]
#self.Debug(f'{[f.Value for f in output]}')
#output = [f[0] for f in security_momo_list]
self.Symbols = output
return output
def OnSecuritiesChanged(self, changes):
self.tobeliquidated = [security.Symbol for security in changes.RemovedSecurities]
for sym in self.tobeliquidated:
self.Liquidate(sym)
#self.Settings.FreePortfolioValuePercentage = 0.6
self.Settings.FreePortfolioValue = self.Settings.FreePortfolioValuePercentage * self.Portfolio.TotalHoldingsValue
for sym in self.Symbols:
self.SetHoldings(str(sym),1/self.numsecurities)
return
Shile Wen
Hi Han,
An easier method would be to use the Portfolio fields to compute a percentage modifier for our SetHoldings calculation:
pct = self.Portfolio.Cash / self.Portfolio.TotalPortfolioValuefor sym in self.Symbols: self.SetHoldings(str(sym),1/self.numsecurities * pct)
Please see the attached backtest for reference.
Best,
Shile Wen
Han Ruobin
Hi Shile,
Thanks for your reply! Correct me if I am wrong, but it seems that using that method I would be setting the value of each of my holdings to be equal to a fraction of my available cash. However, the amount of available cash will usually only be a small fraction of my total portfolio value.
I believe this method would work if I were to liquidate all my assets first and then proceed to purchase whatever securities I want. If I did that, the amount of portfolio cash would be the majority of my portfolio value. However, I am not doing that since usually a good number of securities I want to own in any rebalancing cycle is already owned, and I just need to change their position sizes.
Furthermore, it seems that at certain times my portfolio cash reaches negative for some reason, and by performing setholdings on a negative fraction some of my holdings will be in a short position instead, which should never be the case since this is a purely long strategy.
Lastly, I would still like to check what the issue is with the self.Settings.FreePortfolioValuePercentage method, or if I'm using it wrongly. Thank you.
Shile Wen
Hi Han Ruobin,
Feel free to change the fields to other Portfolio fields to achieve what you would like, which can be found here.
The Cash is sometimes negative if there is a drastic price change when submitting a market order, and we can check to only trade if Cash is positive with a self.Portfolio.Cash > 0 check.
The self.Settings.FreePortfolioValuePercentage is supposed to be set only once in Initialize, as it is just a setting to be configured.
Best,
Shile Wen
Han Ruobin
Hi Shile,
Thank you for your reply. I am still perturbed by why setting more than 50% cash buffer would still be insufficient to ensure buying power? I would like to know the underlying reason for this issue so that I can possibly avoid it in the future. Thank you!
Ruobin
Shile Wen
Hi Han,
The current logic doesn't account for current holdings. Please use the debugger and set break points in OnSecuritiesChanged and look for the portfolio variables to understand how margin is being consumed.
Best,
Shile Wen
Han Ruobin
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!