
The strategy invests in ETFs and futures from 66 countries, going long on the top 33% with the highest book-to-market ratios, short on the lowest, and rebalancing monthly.
ASSET CLASS: ETFs, futures | REGION: Global | FREQUENCY:
Monthly | MARKET: equities | KEYWORD: Value, Effect, Countries
I. STRATEGY IN A NUTSHELL
The strategy trades crude oil derivatives using the GFU index to predict next-month returns. Based on the regression forecast, the investor goes long or short and rebalances monthly.
II. ECONOMIC RATIONALE
GFU captures alternative economic signals that help forecast oil prices. Research shows it provides robust, statistically and economically significant predictive power, enhancing commodity market timing strategies.
III. SOURCE PAPER
A Performance Evaluation Model for Global Macro Funds [Click to Open PDF]
Zaremba, Montpellier Business School, Poznan University of Economics and Business
<Abstract>
The paper concentrates on value and size effects in country portfolios. It contributes to academic literature threefold. First, I provide fresh evidence that the value and size effects may be useful in explaining the cross-sectional variation in country returns. The computations are based on a broad sample of 66 countries in years 2000-2013. Second, I document that the country-level value and size effects are indifferent to currency conversions. Finally, I introduce an alternative macro-level Fama-French model, which, contrary to its prototype, employs country-based factors. I show that applying this modification makes the model more successful in evaluation of funds with global investment mandate than the standard CAPM and FF models.
IV. BACKTEST PERFORMANCE
| Annualised Return | 6.8% |
| Volatility | 9.21% |
| Beta | -0.012 |
| Sharpe Ratio | 0.3 |
| Sortino Ratio | N/A |
| Maximum Drawdown | N/A |
| Win Rate | 49% |
V. FULL PYTHON CODE
from AlgorithmImports import *
#endregion
class ValueEffectwithinCountries(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
self.symbols = {
'Argentina' : 'ARGT',
'Australia' : 'EWA',
'Austria' : 'EWO',
'Belgium' : 'EWK',
'Brazil' : 'EWZ',
'Canada' : 'EWC',
'Chile' : 'ECH',
'China' : 'FXI',
'Egypt' : 'EGPT',
'France' : 'EWQ',
'Germany' : 'EWG',
'Hong Kong' : 'EWH',
'India' : 'INDA',
'Indonesia' : 'EIDO',
'Ireland' : 'EIRO',
'Israel' : 'EIS',
'Italy' : 'EWI',
'Japan' : 'EWJ',
'Malaysia' : 'EWM',
'Mexico' : 'EWW',
'Netherlands' : 'EWN',
'New Zealand' : 'ENZL',
'Norway' : 'NORW',
'Philippines' : 'EPHE',
'Poland' : 'EPOL',
'Russia' : 'ERUS',
'Saudi Arabia' : 'KSA',
'Singapore' : 'EWS',
'South Africa' : 'EZA',
'South Korea' : 'EWY',
'Spain' : 'EWS',
'Sweden' : 'EWD',
'Switzerland' : 'EWL',
'Taiwan' : 'EWT',
'Thailand' : 'THD',
'Turkey' : 'TUR',
'United Kingdom' : 'EWU',
'United States' : 'SPY'
}
for symbol in self.symbols:
data = self.AddEquity(self.symbols[symbol], Resolution.Daily)
data.SetLeverage(5)
self.country_pb_data:Symbol = self.AddData(CountryPB, 'CountryData').Symbol
self.quantile:int = 3
self.recent_month:int = -1
self.max_missing_days:int = 365
def OnData(self, data:Slice) -> None:
# rebalance once a month
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
if self.Securities[self.country_pb_data].GetLastData() and (self.Time.date() - self.Securities[self.country_pb_data].GetLastData().Time.date()).days > self.max_missing_days:
self.Liquidate()
return
bm_data:dict[str, float] = {}
country_pb_data = self.Securities[self.country_pb_data].GetLastData()
if country_pb_data:
for symbol in self.symbols:
pb:float = country_pb_data[symbol]
bm_data[symbol] = 1 / pb
long:List[str] = []
short:List[str] = []
if len(bm_data) >= self.quantile:
sorted_by_bm:List = sorted(bm_data.items(), key = lambda x: x[1], reverse = True)
quantile:int = int(len(bm_data) / self.quantile)
long = [x[0] for x in sorted_by_bm[:quantile]]
short = [x[0] for x in sorted_by_bm[-quantile:]]
# liquidate
invested:List[str] = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
# trade execution
long_count:int = len(long)
short_count:int = len(short)
for symbol in long:
traded_symbol:str = self.symbols[symbol]
if traded_symbol in data and data[traded_symbol]:
self.SetHoldings(traded_symbol, 1 / long_count)
for symbol in short:
traded_symbol:str = self.symbols[symbol]
if traded_symbol in data and data[traded_symbol]:
self.SetHoldings(traded_symbol, -1 / short_count)
# Country PB data
# NOTE: IMPORTANT: Data order must be ascending (date-wise)
from dateutil.relativedelta import relativedelta
class CountryPB(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/economic/country_pb.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = CountryPB()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%Y") + relativedelta(years=1)
self.symbols = ['Argentina','Australia','Austria','Belgium','Brazil','Canada','Chile','China','Egypt','France','Germany','Hong Kong','India','Indonesia','Ireland','Israel','Italy','Japan','Malaysia','Mexico','Netherlands','New Zealand','Norway','Philippines','Poland','Russia','Saudi Arabia','Singapore','South Africa','South Korea','Spain','Sweden','Switzerland','Taiwan','Thailand','Turkey','United Kingdom','United States']
index = 1
for symbol in self.symbols:
data[symbol] = float(split[index])
index += 1
data.Value = float(split[1])
return data
VI. Backtest Performance