Skip to content

Instantly share code, notes, and snippets.

@alun
Last active September 8, 2022 10:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alun/4424cb0e61a4955355ea82bb72286af3 to your computer and use it in GitHub Desktop.
Save alun/4424cb0e61a4955355ea82bb72286af3 to your computer and use it in GitHub Desktop.
Stoken ACA - Zorro

v2

  • added full portfolio rebalance (33% each slice):
    • when one of the slices (algos) switches the risk on/off mode
    • one a year
  • added plotting of the fractions of each slice in portfolio
  • some minor refactoring

v1

  • basic no leverage strategy implementation
  • no rebalancing
// ZorroTrader implementation of Stoken ACA tactical asset allocation strategy
//
// Read here for more implementation details
// https://allocatesmartly.com/stokens-active-combined-asset-strategy/
// needs dividend adjusted price history for the following ETFs
#define ASSETS "SPY-US","VNQ-US","GLD-US","IEF-US","TLT-US"
// three algos, one per risk asset
#define ALGOS "SPY","VNQ","GLD"
const var NumAlgos = 3;
// should we reinvest profits?
const bool Reinvest = false;
// set to true if one of slices allocation updated
bool isAllocationUpdated = false;
// initially selected [Asset] for allocation plot
string DefaultAsset;
var portfolioValue() {
var Profit = ifelse(Reinvest, ProfitTotal, 0);
return Capital + Profit;
}
// Calcualtes currently required number of lots for current asset
int lotsRequired() {
return portfolioValue() / NumAlgos / priceC();
}
// Prints debug portfolio info to CSV file (not a real CSV)
void printPortfolio() {
print(TO_CSV, "\n\nDate: %s",strdate(YMD,0));
string _algo, _asset;
while(_algo = of(ALGOS)) {
algo(_algo);
while(_asset = of(ASSETS)) {
asset(_asset);
for(current_trades) {
var value = TradeLots * priceC();
print(TO_CSV, "\n%s:%s %.2f", Asset, Algo, value);
}
}
}
print(TO_CSV, "\nTotal: %.2f", portfolioValue());
}
// Plots allocation of current capital per algo
void plotAllocation() {
var total = portfolioValue();
int maybeNew = NEW;
string _algo, _asset;
while(_algo = of(ALGOS)) {
algo(_algo);
var algoTotal = 0;
while(_asset = of(ASSETS)) {
asset(_asset);
for(current_trades) {
algoTotal += TradeLots * priceC();
}
}
int col;
switch (Algo) {
case "SPY": col = RED; break;
case "VNQ": col = GREEN; break;
case "GLD": col = BLUE; break;
}
// plot on the asset chart which was selected in the [Asset]
// combo box when test started
asset(DefaultAsset);
// print(TO_CSV, "\n %s: %2.f %2.f", "a", algoTotal, total);
plot(Algo, algoTotal / total, maybeNew|SPLINE, col);
if (maybeNew == NEW) {
maybeNew = 0;
}
}
}
// Enters risk asset in the begginging
void initEnter(string riskAsset) {
asset(riskAsset);
enterLong(lotsRequired());
}
// Rebalance
void rebalance() {
string _algo, _asset;
while(_algo = of(ALGOS)) {
algo(_algo);
int algoLotsTotal = 0;
string algoAsset = 0;
while(_asset = of(ASSETS)) {
// this should always be risk-on or risk-off asset for the given algo
// but might have multiple trades due to previous rebalance
asset(_asset);
for(current_trades) {
if (algoAsset == 0) {
algoAsset = TradeAsset;
} else if (algoAsset != TradeAsset) {
printf("Error: unexpected trade asset %s, expecting %s", TradeAsset, algoAsset);
}
algoLotsTotal += TradeLots;
}
}
if (algoAsset == 0) {
printf("Error: unexpected trade asset %s, expecting %s", TradeAsset, algoAsset);
}
asset(algoAsset);
int lotsDelta = algoLotsTotal - lotsRequired();
if (lotsDelta > 0) {
// partial close
exitLong(Algo, 0, lotsDelta);
} else if (lotsDelta < 0) {
// grow position
enterLong(-lotsDelta);
}
}
}
// Runs Algo (1 algo per slice)
// each slice is defined by the risk/defensive assets pair
// and upper/lower channel period of the risk asset
void runAlgo(
string riskAsset,
string defensiveAsset,
int riskOnPeriod,
int riskOffPeriod
) {
asset(riskAsset);
vars LagCloses = series(priceC(1));
var UpperChannel = MaxVal(LagCloses, riskOnPeriod);
var LowerChannel = MinVal(LagCloses, riskOffPeriod);
// plot channels - useful for debugging
plot("Upper", UpperChannel, 0, GREEN);
plot("Lower", LowerChannel, 0, BLUE);
if (priceC() > UpperChannel && NumOpenLong == 0) {
// risk-on
asset(defensiveAsset);
exitLong();
asset(riskAsset);
enterLong(lotsRequired());
isAllocationUpdated = true;
}
else if (priceC() < LowerChannel) {
// risk-off
exitLong();
asset(defensiveAsset);
if (NumOpenLong == 0) {
enterLong(lotsRequired());
isAllocationUpdated = true;
}
}
}
function run() {
if (is(INITRUN)) {
set(LOGFILE|PLOTNOW);
StartDate = 20030101;
LookBack = 252;
Capital = 100000;
BarPeriod = 1440;
DefaultAsset = Asset;
string _asset;
while(_asset = of(ASSETS))
assetAdd(_asset);
}
static bool initBought = false;
// go risk-on assets
if (!is(LOOKBACK) && !initBought) {
while(algo(loop(ALGOS))) {
if (Algo == "SPY") initEnter("SPY-US");
else if (Algo == "VNQ") initEnter("VNQ-US");
else if (Algo == "GLD") initEnter("GLD-US");
}
initBought = true;
}
isAllocationUpdated = false;
while(algo(loop(ALGOS))) {
if (Algo == "SPY") runAlgo("SPY-US", "IEF-US", 126, 252);
else if (Algo == "VNQ") runAlgo("VNQ-US", "IEF-US", 126, 252);
else if (Algo == "GLD") runAlgo("GLD-US", "TLT-US", 252, 126);
}
if (!is(LOOKBACK)) {
static int lastYearRebalance = -1;
if (isAllocationUpdated || year() != lastYearRebalance) {
rebalance();
lastYearRebalance = year();
}
plotAllocation();
// only print portfolio on the first day of month
static int lastMonth = -1;
if (month() != lastMonth) {
lastMonth = month();
printPortfolio();
}
}
}
Test stoken_aca , Zorro 2.502
Simulated account AssetsFix
Bar period 24 hours (avg 2088 min)
Total processed 6400 bars
Test period 2006-02-28..2022-09-02 (4157 bars)
Lookback period 252 bars (73 weeks)
Montecarlo cycles 200
Simulation mode Realistic
Capital invested 100000$
Gross win/loss 164107$-14769$, +195695.8p, lr 154501$
Average profit 9046$/year, 754$/month, 34.79$/day
Max drawdown -4652$ 3.1% (MAE -20349$ 13.6%)
Total down time 1% (TAE 90%)
Max down time 25 weeks from Jun 2018
Max open margin 50862$
Max open risk 1016$
Trade volume 1117364$ (67681$/year)
Transaction costs -126$ spr, 109$ slp, 0$ rol
Capital required 21194$
Number of trades 131 (8/year)
Percent winning 81.7%
Max win/loss 11686$ / -3713$
Avg trade profit 1140$ 1493.9p (+2009.8p / -806.4p)
Avg trade slippage 0.83$ 1.1p (+1.6p / -1.3p)
Avg trade bars 333 (+158 / -85)
Max trade bars 2140 (620 weeks)
Time in market 1050%
Max open trades 8
Max loss streak 5 (uncorrelated 3)
Annual growth rate 5.69% (ROI 9.05%)
Profit factor 11.11 (PRR 8.34)
Reward/Risk ratio 32.1
Sharpe ratio 0.75 (Sortino 0.70)
R2 coefficient 0.900
Ulcer index 3.8%
Scholz tax 39388 EUR
Year Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Total
2006 0 3 3 -2 -1 3 1 -1 4 5 -2 +14
2007 4 -1 -1 2 0 -4 -2 1 4 3 -1 2 +7
2008 3 2 -1 -3 -1 2 -0 -1 0 -1 8 6 +13
2009 -5 -1 2 -3 -2 -1 2 4 2 -1 6 -0 +3
2010 -2 3 3 3 -2 -2 2 0 4 3 0 3 +15
2011 -0 3 -0 4 -0 -2 2 0 -6 3 -0 -1 +2
2012 3 0 1 0 -3 1 1 0 -0 -1 0 0 +5
2013 1 1 1 3 -2 -1 1 -2 1 2 -1 0 +4
2014 1 2 0 1 1 1 -0 2 -2 3 2 1 +13
2015 2 -1 0 -1 -0 -2 2 -2 0 2 -0 -0 +0
2016 -0 -0 2 0 -0 3 2 -1 -0 -2 -1 1 +2
2017 0 1 -0 0 0 1 0 1 -0 0 1 0 +6
2018 -0 -1 -0 -0 0 -0 0 1 -1 -2 1 -2 -4
2019 2 -0 2 -0 0 2 0 1 0 1 -0 1 +10
2020 1 -2 -2 1 0 0 2 0 -1 -1 0 2 +1
2021 -1 0 1 2 0 1 2 1 -2 2 -0 2 +9
2022 -2 -1 1 -2 -1 -1 1 -2 2 -5
Portfolio analysis OptF ProF Win/Loss Wgt%
GLD-US avg ---- 5.96 16/9 22.5
IEF-US avg ---- 40.02 21/3 8.5
SPY-US avg ---- 8.94 25/6 24.3
TLT-US avg ---- 6.63 16/3 10.1
VNQ-US avg ---- 122.57 29/3 34.6
GLD avg ---- 6.15 32/12 32.6
SPY avg ---- 9.49 34/9 27.8
VNQ avg ---- 140.16 41/3 39.6
GLD-US:GLD:L ---- 5.96 16/9 22.5
IEF-US:SPY:L ---- 17.10 9/3 3.5
IEF-US:VNQ:L ---- ++++ 12/0 5.0
SPY-US:SPY:L ---- 8.94 25/6 24.3
TLT-US:GLD:L ---- 6.63 16/3 10.1
VNQ-US:VNQ:L ---- 122.57 29/3 34.6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment