Skip to content

Commit 4569595

Browse files
authored
Create PAA_Model.py
Pragmatic asset allocation model
1 parent 310e92f commit 4569595

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

Strategies/PAA_Model.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# From article : Pragmatic Asset Allocation Model for Semi-Active Investors
2+
# Radovan Vojtko, Juliána Javorská
3+
4+
from AlgorithmImports import *
5+
6+
class MacroOptimizedAssetAllocation(QCAlgorithm):
7+
def Initialize(self):
8+
self.SetStartDate(2023, 1, 1)
9+
self.SetCash(10000)
10+
11+
# Equity ETFs: broad US, developed ex-US, and emerging markets
12+
self.equity_assets = {
13+
"SPY": self.AddEquity("SPY", Resolution.Daily).Symbol, # Broad US market
14+
"IEFA": self.AddEquity("IEFA", Resolution.Daily).Symbol, # Developed markets ex-US
15+
"VWO": self.AddEquity("VWO", Resolution.Daily).Symbol, # Emerging markets
16+
}
17+
18+
# Safe assets: inflation protection and intermediate bonds
19+
self.safe_assets = {
20+
"TIP": self.AddEquity("TIP", Resolution.Daily).Symbol, # Inflation-protected bonds
21+
"IEF": self.AddEquity("IEF", Resolution.Daily).Symbol, # Intermediate-term Treasuries
22+
"GLD": self.AddEquity("GLD", Resolution.Daily).Symbol, # Gold ETF
23+
}
24+
25+
self.momentum_window = 12 # 12-month momentum
26+
self.momentum_scores = {}
27+
28+
# Yield curve data remains for macro risk-off decisions
29+
self.yield_curve = self.AddData(Fred, "T10Y3M", Resolution.Daily).Symbol
30+
31+
# Rebalance quarterly
32+
self.Schedule.On(
33+
self.DateRules.MonthEnd(self.equity_assets["SPY"]),
34+
self.TimeRules.At(15, 45),
35+
self.Rebalance
36+
)
37+
38+
def Rebalance(self):
39+
if not self.DataReady():
40+
self.Debug("Data not ready for rebalancing.")
41+
return
42+
43+
momentum_results = {}
44+
for symbol in self.equity_assets.values():
45+
history = self.History(symbol, self.momentum_window * 22, Resolution.Daily)
46+
if history.empty or len(history) < self.momentum_window * 22:
47+
self.Debug(f"Insufficient history for {symbol}")
48+
continue
49+
50+
start_price = history["close"].iloc[0]
51+
end_price = history["close"].iloc[-1]
52+
momentum = end_price / start_price - 1
53+
sma_12m = history["close"].mean()
54+
current_price = self.Securities[symbol].Price
55+
56+
self.Debug(f"{symbol}: Momentum={momentum:.2%}, SMA={sma_12m:.2f}, CurrentPrice={current_price:.2f}")
57+
if current_price > sma_12m:
58+
momentum_results[symbol] = momentum
59+
60+
# Check yield curve inversion: risk-off mode
61+
if self.YieldCurveInverted():
62+
self.AllocateToHedgingPortfolio()
63+
return
64+
65+
# Rank assets by momentum
66+
sorted_assets = sorted(momentum_results, key=momentum_results.get, reverse=True)
67+
68+
# Liquidate positions not in the new allocation
69+
for symbol in self.Portfolio.Keys:
70+
if symbol not in sorted_assets[:2]:
71+
self.Liquidate(symbol)
72+
73+
# Allocate to top momentum assets with full allocation (adjust weights as needed)
74+
if len(sorted_assets) > 0:
75+
self.SetHoldings(sorted_assets[0], 0.5)
76+
if len(sorted_assets) > 1:
77+
self.SetHoldings(sorted_assets[1], 0.5)
78+
79+
self.Debug(f"Rebalanced to: {[str(sym) for sym in sorted_assets[:2]]}")
80+
81+
def YieldCurveInverted(self):
82+
if self.yield_curve in self.CurrentSlice and self.CurrentSlice[self.yield_curve]:
83+
inversion = self.CurrentSlice[self.yield_curve].Value < 0
84+
self.Debug(f"Yield curve inversion: {inversion}")
85+
return inversion
86+
self.Debug("Yield curve data not available.")
87+
return False
88+
89+
def AllocateToHedgingPortfolio(self):
90+
# Liquidate all equity positions
91+
for symbol in self.equity_assets.values():
92+
self.Liquidate(symbol)
93+
94+
# Allocate to safe assets – weights are adjustable based on risk preference
95+
self.SetHoldings(self.safe_assets["TIP"], 0.33)
96+
self.SetHoldings(self.safe_assets["IEF"], 0.33)
97+
self.SetHoldings(self.safe_assets["GLD"], 0.34)
98+
self.Debug("Allocated to hedging portfolio")
99+
100+
def DataReady(self):
101+
return all([self.Securities[s].HasData for s in self.equity_assets.values()])

0 commit comments

Comments
 (0)