The large drops correspond to stock splits. Corporate action objects can be examined to get more detail.
ca = stock_obj.corporate_actions()ca[:10]
[StockSplit(Stock Split on 2000-06-21),
StockSplit(Stock Split on 2005-02-28),
RegularCashDividend(Quarterly on 2012-08-09),
RegularCashDividend(Quarterly on 2012-11-07),
RegularCashDividend(Quarterly on 2013-02-07),
RegularCashDividend(Quarterly on 2013-05-09),
RegularCashDividend(Quarterly on 2013-08-08),
RegularCashDividend(Quarterly on 2013-11-06),
RegularCashDividend(Quarterly on 2014-02-06),
RegularCashDividend(Quarterly on 2014-05-08)]
from sigtech.framework.internal.equities.corporate_actions.corporate_actions import StockSplitstock_splits = [c for c in ca ifisinstance(c, StockSplit)]stock_splits
[StockSplit(Stock Split on 2000-06-21),
StockSplit(Stock Split on 2005-02-28),
StockSplit(Stock Split on 2014-06-09),
StockSplit(Stock Split on 2020-08-31)]
stock_splits[-2]
It is possible to fetch price series data from SingleStock objects that handle reinvestment following corporate events with the adjusted_price_history method.
stock_obj.adjusted_price_history?
The arguments to adjusted_price_history determine whether regular and special dividends should be reinvested, respectively. Events like stock splits are automatically handled by this method: relevant adjustment factors are computed and then incorporated in building price series.
In this case, there are stock splits and regular cash dividends, but no special cash dividends. This is reflected in the values of the adjusted_price_history series. Regardless of argument choice, the stock splits no longer impact the adjusted price series.
adjusted_data.tail()
The following example using TSLA shows what happens when no dividends have been received by the holders of the security.
Utilise the SIGMaster to find the primary US listing of TSLA:
As no dividends have been paid by TSLA, altering the input arguments to adjusted_price_history does not impact the resulting time series. As expected, the adjusted price series does not show a division by the split factor (5) following the August 2020 stock split.
adjusted_data.tail()
The ratio of the final adjusted/unadjusted prices is exactly the stock split ratio of 5:
The ReinvestmentStrategy building block creates objects that are tradable and accurately mimics the experience of an Equity Portfolio Manager.
Whilst SingleStock historical price series data is not adjusted for corporate actions, such as stock splits, and does not assume reinvestment of dividends, these adjusted price series are available from ReinvestmentStrategy objects. ReinvestmentStrategy objects can also account for transaction costs, while the adjusted_price_history method does not.
To build a simple basket trading a SingleStock object (to illustrate the problem with trading these directly):
As with any strategy object, calling build triggers the backtest specified by the object's parameters to be run. It is then possible to view strategy performance through calling history.
rs_holidays = [ d for d in sig.obj.get(underlyer_holidays).holidays if (d.year >= start_date.year) & (d.year <= end_date.year)][d for d in rs_holidays if d in rs.history()]
[]
The following code block is a comparison between the RS and the SingleStock object - adjusted and unadjusted.
Since the RS has been defined to begin in 2019, whereas the history of the stock object starts in 2000, it was necessary to drop nans when combining histories.
The time series were rescaled to all begin at 1: the value of any Strategy object will begin at the level of its initial_cash parameter, which defaults to 1000.
The first day of history of the RS was ignored as orders need to be placed and executed before the strategy attains its exposure to the underlying. Since this is not completed until 03/01/2019, the first return of the RS is essentially that due to cash. Learn more about customising this behaviour.
Relationship to Strategy class
ReinvestmentStrategy inherits from the Strategy class and inherits its fields, methods, and more.
Continuing from the above outline, the position in 1000045.SINGLE_STOCK.TRADABLE UNREALISED CASH at [2019/02/14, 09:00:00] is dividend proceeds.
ca = rs.underlyer_object.corporate_actions()first_dividend_effective_dtm = dtm.datetime(2019, 2, 8, 9, tzinfo=local_timezone)dividend = [c for c in ca \if c.effective_date == first_dividend_effective_dtm.date()][0]dividend
With this change, the order to reinvest cash from the dividend on 14th February has disappeared. Instead, the unrealised cash from the dividend effective date becomes part of realised 'USD CASH' on the dividend payment date.
The jumps in ratio between the two versions of the RS occur on dividend effective dates.
Fetch regular cash dividends:
from sigtech.framework.internal.equities.corporate_actions.corporate_actions import RegularCashDividenddividends = [ c for c in ca ifisinstance(c, RegularCashDividend)if c.payment_date >= start_date]
ratio_changes =sorted(df.RATIO.pct_change().sort_values(). \ head(len(dividends)).index.tolist())payment_dates = [c.effective_date for c in dividends \if c.effective_date < end_date]
Tracking
Revisiting the first Reinvestment Strategy created, the portfolio table shows that the initial position in stock is actually 90.114% [2019/01/03 16:00:00]:
The reason this exposure does not exactly match the target exposure of 100% is that the sizing, or determination of the number of shares to trade, is done using the prior day's close. This closely resembles the experience of a trader with access to EOD prices.
In most cases, the difference between target weight and realised weight would not be this large. In this particular case, AAPL experienced a large negative move on the day of execution (-9.96%).
The enable_tracking parameter will cause an RS to trade toward full exposure on a schedule provided by the user (tracking_frequency, default SOM).
An optional tracking_threshold can also be set (default 1bp) that controls whether these tracking trades should be executed. For example, the threshold is 1%, the current exposure is 99.5%, so no action takes place.
The final_trade_out parameter causes a Reinvestment Strategy to sell out of the underlying before it is delisted. The RS will then hold cash.
This can be very useful when another strategy, such as SignalStrategy, is trading a universe of RS. The onus will no longer be on the user to ensure that no trades on the Reinvestment Strategy, corresponding to the delisted security, are initiated.
The version of the RS with final_trade_out set to False has nan prices from the delisting date onward, rendering it impossible to trade, which includes the closing of positions in it. The version with final_trade_out set to True does not have this problem.
Shorting costs can be specified with the borrow_cost_ts parameter. The following example assumes there is no borrowing cost:
rs_short = sig.ReinvestmentStrategy( start_date=start_date, end_date=end_date, currency=stock_obj.currency, underlyer=ticker, ticker=f'{ticker}_RS_SHORT',# specific to shorting direction='short',# cash given opposite sign as we are short initial_cash=-1000, borrow_cost_ts=0,# should not specify dividend reinvestment when short reinvest_cash_dividends=False,)rs_short.build()
The following example uses a fixed annual cost of 1%:
Note: a time series could also be supplied.
rs_short_borrow = sig.ReinvestmentStrategy( start_date=start_date, end_date=end_date, currency=stock_obj.currency, underlyer=ticker, ticker=f'{ticker}_RS_SHORT_BORROW',# specific to shorting direction='short',# cash given opposite sign as we are short initial_cash=-1000, borrow_cost_ts=1,# should not specify dividend reinvestment when short reinvest_cash_dividends=False,)rs_short_borrow.build()df = pd.concat({'Borrow': rs_short_borrow.history(),'No Borrow': rs_short.history(),}, axis=1)df.plot()