Problems reproducing Antonacci's dual momentum GEM strategy

I modified some code from another trader, to try and reproduce the allocations and results of Antonacci's Dual Momentum GEM strategies.

The rules are simple:

• Compare the performance of SPY 500, a global non-US-stock index and US treasury bills over the last 252 trading days, every month
• If one of the two equity indices shows the highest return of all three indices, take a 100% long position in that index
• If US treasury bills show the highest return, take a 100% long position in these

The results of my backtests are way off the official results, both in allocations and returns.

Can anyone help me spot the problems?

27
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month

# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
#set_symbol_lookup_date('2002-08-01')
context.stocks = symbols('SPY','ACWX','AGG')
context.look_back_window = 252
context.port_size = 1
context.month_count = 0

schedule_function(
rebalance,
date_rules.month_end(days_offset=0),
time_rules.market_open(minutes=5)
)
pass

def rebalance(context, data):
#get daily historical prices
h =   data.history(context.stocks, 'price', context.look_back_window, '1d')
#use dataframe resample to get monthly values
h = h.resample('M').last()
print h
#first, get the pct_change() from the first date (0) in the dataframe and today (-1). Then splice the last row because the first row is all NaN. Drop all stocks with NaN then turn the dataframe into series. Series make it easier to sort.
mom = h.iloc[[0,-1]].pct_change().tail(1).dropna(axis=1).squeeze()
#order by return percentage, and take only the top
mom = mom.sort_values().index[-1]
print mom
#liquidate positions that are not in the buy list
for s in context.portfolio.positions:
if s != mom:
order_target_percent(s, 0)

#get weight based on how many stocks are selected
weight = 0.95

#purchase stock with largest momentum
order_target_percent(mom, weight)

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
record(leverage = context.account.leverage)
pass

There was a runtime error.
5 responses

I have not looked at your code but can tell you from long experience you are wasting your time trying to match his figures. You have no idea what data or softwareβ he was using and you are chasing your own backside for no reward.

My advice is to rely on your own skills and your own data (or Quantopian's data). Thrice check your code does what this simple system is supposed to do. Once you are satisfied if there is still a large difference contact Antonacci. Ask for his data and code or at least details of it.

It's your money on the line. Make your own judgement - I always do. Then I have only myself to blame when the system f***s up. And they usually do.

ππππππππππ

" Then I have only myself to blame when the system f***s up."

See, that's what I hate - I like to blame others :-D

But jokes aside, I have already found a few emberassing mistakes. I haven't implemented the rules correctly, the absolute momentum part of dual momentum is completely missing.

Between the paper and the results there are many discrepancies.

Try this from 2007.
It is more close to my understanding of Global Equities Dual Momentum.

# Gary Antonacci's Global Equities Dual Momentum replication

# https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2042750
# http://www.optimalmomentum.com/gem_allocation.html

def initialize(context):
schedule_function(rebalance, date_rules.month_end(), time_rules.market_close(minutes=5))
set_commission(commission.PerShare(cost=0))

def rebalance(context, data):
assets = symbols('SPY', 'ACWX', 'SHV')
mom_period = 252

hist = data.history(assets, 'price', mom_period + 1, '1d')
mom = (hist.iloc[-1]/hist.iloc[0]) - 1.0
mom = mom.dropna()
mom = mom.sort_values().index[-1]
mom = mom if mom > 0 else symbols('SHV')
print mom

if get_open_orders(): return

for asset in assets:
if asset is mom:
order_target_percent(asset, 1.0)
else:
order_target(asset, 0)

record(leverage = context.account.leverage)