
The strategy trades large Australian stocks, going long zero-share issuance stocks and short high-share issuance stocks, sorting annually into portfolios by net issuance and rebalancing equally weighted positions yearly.
ASSET CLASS: stocks | REGION: United States | FREQUENCY:
Yearly | MARKET: equities | KEYWORD: Share, Issuance, Effect
I. STRATEGY IN A NUTSHELL
Universe: Australian exchange-listed stocks, restricted to the largest 30% by market capitalization.
Portfolio Formation:
Each December, firms are sorted into eight portfolios based on net share issuance:
Net Issuance Measure = log(adjusted shares at June (t–1)) – log(adjusted shares at June (t–2)).
Negative issuance stocks: split into two groups: NegLow (most negative) and NegHigh.
Zero issuance stocks: grouped into the Zeros portfolio.
Positive issuance stocks: ranked into quintiles (PosLow → PosHigh).
Strategy Rule:
Equal-weighted positions, rebalanced annually.
Go long the Zeros portfolio.
Go short the PosHigh portfolio.
II. ECONOMIC RATIONALE
Dilution Effect:
New share issuance dilutes existing shareholders’ ownership, often putting downward pressure on stock prices.
Investor Behavioural Bias:
Markets do not always fully and immediately price in dilution effects. The delayed adjustment reflects investor underreaction and other behavioural biases.
Exploitable Anomaly:
By systematically avoiding high-issuance stocks (shorting PosHigh) and favoring no-issuance stocks (long Zeros), the strategy exploits this persistent mispricing.
III. SOURCE PAPER
Share Issuance Effects in the Cross-Section of Stock Returns [Click to Open PDF]
Lancaster, Bornholt, Reserve Bank of Australia, Griffith University
<Abstract>
Previous research describes the net share issuance anomaly in U.S. stocks as pervasive, both in size-based sorts and in cross-section regressions. As a further test of its pervasiveness, this paper undertakes an in-depth study of share issuance effects in the Australian equity market. The anomaly is observed in all size stocks except micro stocks. For example, equal weighted portfolios of non-issuing big stocks outperform portfolios of high issuing big stocks by an average of 0.84% per month over 1990–2009. This outperformance survives risk adjustment and appears to subsume the asset growth effect in Australian stock returns.


IV. BACKTEST PERFORMANCE
| Annualised Return | 10.56% |
| Volatility | 12.25% |
| Beta | -0.133 |
| Sharpe Ratio | 0.54 |
| Sortino Ratio | -0.045 |
| Maximum Drawdown | N/A |
| Win Rate | 66% |
V. FULL PYTHON CODE
from AlgorithmImports import *
from math import isnan
class ShareIssuanceEffect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.long:List[Symbol] = []
self.short:List[Symbol] = []
self.shares_number:Dict[Symbol, RollingWindow] = {}
self.leverage:int = 5
self.min_share_price:float = 5.
market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.fundamental_count:int = 500
self.fundamental_sorting_key = lambda x: x.DollarVolume
self.record_shares_flag = False
self.record_shares_flag_month:int = 6
self.selection_flag = False
self.selection_flag_month:int = 11
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.Schedule.On(self.DateRules.MonthEnd(market), self.TimeRules.BeforeMarketClose(market), self.Selection)
self.settings.daily_precise_end_time = False
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
security.SetLeverage(self.leverage)
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag and not self.record_shares_flag:
return Universe.Unchanged
selected:List[Fundamental] = [
x for x in fundamental if x.HasFundamentalData and x.Market == 'usa' and x.Price >= self.min_share_price and \
not isnan(x.FinancialStatements.BalanceSheet.OrdinarySharesNumber.ThreeMonths) and x.FinancialStatements.BalanceSheet.OrdinarySharesNumber.ThreeMonths > 0
]
if len(selected) > self.fundamental_count:
selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
if self.record_shares_flag:
for stock in selected:
symbol:Symbol = stock.Symbol
if symbol not in self.shares_number:
self.shares_number[symbol] = RollingWindow[float](2)
shares_number:float = stock.FinancialStatements.BalanceSheet.OrdinarySharesNumber.ThreeMonths
self.shares_number[symbol].Add(shares_number)
# NOTE: Get rid of old shares number records so we work with latest values.
del_symbols:List[Symbol] = []
for symbol in self.shares_number:
if symbol not in [x.Symbol for x in selected]:
del_symbols.append(symbol)
for symbol in del_symbols:
del self.shares_number[symbol]
self.record_shares_flag = False
elif self.selection_flag:
net_issuance:Dict[Symbol, float] = {}
for stock in selected:
symbol:Symbol = stock.Symbol
if symbol in self.shares_number and self.shares_number[symbol].IsReady:
shares_values:List[float] = list(self.shares_number[symbol])
net_issuance[symbol] = shares_values[0] / shares_values[-1] - 1
if len(net_issuance) != 0:
zero_net_issuance:List[float] = [x[0] for x in net_issuance.items() if x[1] == 0]
pos_net_issuance:List = [x for x in net_issuance.items() if x[1] > 0]
sorted_pos_by_net_issuance:List = sorted(pos_net_issuance, key = lambda x: x[1], reverse = True)
quantile:int = int(len(sorted_pos_by_net_issuance)/5)
pos_high:List[Symbol] = [x[0] for x in sorted_pos_by_net_issuance[:quantile]]
neg_net_issuance:List = [x for x in net_issuance.items() if x[1] < 0]
sorted_neg_by_net_issuance:List = sorted(neg_net_issuance, key = lambda x: x[1], reverse = False)
half:int = int(len(sorted_neg_by_net_issuance) / 2)
neg_high:List[Symbol] = [x[0] for x in sorted_neg_by_net_issuance[:half]]
#self.long = zero_net_issuance
self.long = neg_high
self.short = pos_high
return self.long + self.short
def OnData(self, data: Slice) -> None:
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution and rebalance
targets:List[PortfolioTarget] = []
for i, portfolio in enumerate([self.long, self.short]):
for symbol in portfolio:
if symbol in data and data[symbol]:
targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio)))
self.SetHoldings(targets, True)
self.long.clear()
self.short.clear()
def Selection(self) -> None:
if self.Time.month == self.record_shares_flag_month:
self.record_shares_flag = True
elif self.Time.month == self.selection_flag_month:
self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
VI. Backtest Performance