I realise there is no bounty here, but the strategy looked interesting so I gave it a shot. I wasn't sure if i should post it here or in Strat Dev.
The strategy in the link looks excellent - unfortunately I've not been able to get behaviour anywhere close!
Logically I think my code is correct, but a fresh pair of eyes on it would be very welcome... I'm running a genetic algorithm on the parameters to see if I can get some parameters that do more than marginally beat the market. On the plus side, it does seem to give decent sharpe ratios. I think the next step is writing out price and stop to a csv and plotting it to get a better idea of what is going on.
EDIT: Done some plotting, it's behaving as desired, although with a slightly big spike in the stop at each trade. Looks like it's a case of tuning the parameters properly:
I've made some changes to the behaviour of the described strategy which have improved it in my testing:
Original strategy used the max high and min low of the last X candles as a base value for the stop. I have changed this to use the average highs and lows, and also reset this to the high or low of the current candle each time a trade is made. This makes it slightly more resilient to the more wild fluctuations, and means it ignores highs or lows that happened before the current trade, which improved performance considerably.
I have also separated the high and low ATR multipliers for Bull and Bear markets to give more flexibility in tuning.
Using Heiken Ashi candles also seemed to help.
NPM Requirements: lodash (npm i --save lodash)
ATR_ADX.toml - values are as recommended in OP's link, don't work very well for me though!
https://github.com/RJPGriffin/gekko/blob...R_ADX.toml
ATR_ADX.js
https://github.com/RJPGriffin/gekko/blob...ATR_ADX.js
The strategy in the link looks excellent - unfortunately I've not been able to get behaviour anywhere close!
Logically I think my code is correct, but a fresh pair of eyes on it would be very welcome... I'm running a genetic algorithm on the parameters to see if I can get some parameters that do more than marginally beat the market. On the plus side, it does seem to give decent sharpe ratios. I think the next step is writing out price and stop to a csv and plotting it to get a better idea of what is going on.
EDIT: Done some plotting, it's behaving as desired, although with a slightly big spike in the stop at each trade. Looks like it's a case of tuning the parameters properly:
I've made some changes to the behaviour of the described strategy which have improved it in my testing:
Original strategy used the max high and min low of the last X candles as a base value for the stop. I have changed this to use the average highs and lows, and also reset this to the high or low of the current candle each time a trade is made. This makes it slightly more resilient to the more wild fluctuations, and means it ignores highs or lows that happened before the current trade, which improved performance considerably.
I have also separated the high and low ATR multipliers for Bull and Bear markets to give more flexibility in tuning.
Using Heiken Ashi candles also seemed to help.
NPM Requirements: lodash (npm i --save lodash)
ATR_ADX.toml - values are as recommended in OP's link, don't work very well for me though!
https://github.com/RJPGriffin/gekko/blob...R_ADX.toml
Code:
# ATR period
ATR = 22
#ADX period
ADX = 22
# ATR Threshold
ATR_threshold = 30
#ATR Multiplers
BEAR_Multiplier_low = 1.75
BEAR_Multiplier_high = 3
BULL_Multiplier_low = 1.75
BULL_Multiplier_high = 3
ATR_ADX.js
https://github.com/RJPGriffin/gekko/blob...ATR_ADX.js
Code:
/*
ATR ADX Adaptive Strategy for gekko
Built to immitate this script:https://www.tradingview.com/script/H48yeyRa-Adaptive-ATR-ADX-Trend-V2/
-
(CC-BY-SA 4.0) Rowan Griffin
https://creativecommons.org/licenses/by-sa/4.0/
*/
// req's
var log = require('../core/log.js');
var config = require('../core/util.js').getConfig();
var _ = require('lodash');
// strategy
var strat = {
/* INIT */
init: function() {
// core
this.name = 'Adaptive ATR ADX';
this.requiredHistory = config.tradingAdvisor.historySize;
// debug? set to false to disable all logging/messages/stats (improves performance in backtests)
this.debug = false;
// performance
config.backtest.batchSize = 1000; // increase performance
config.silent = true;
config.debug = false;
// Heiken Ashi candle
this.HACandle;
this.previousCandle = { //Initialise previousCandle with 0
"open": 0,
"close": 0,
"high": 0,
"low": 0
};
// ATR
this.addTulipIndicator('ATR', 'atr', {
optInTimePeriod: this.settings.ATR
});
// ADX
this.addTulipIndicator('ADX', 'adx', {
optInTimePeriod: this.settings.ADX
})
//Directional Indicator - Needed to decide initial long or short position on strat start
this.addTulipIndicator('DI', 'di', {
optInTimePeriod: this.settings.ADX //matches the ADX period
})
//High and low
this.highs = new Array(this.settings.ATR);
this.lows = new Array(this.settings.ATR);
this.arrayCounter = 0; // to track array position
this.periodMax = 0;
this.periodMin = 0;
//Variables
this.trend = 'none';
this.newTrend = false;
this.stop;
//ATR Threshold
this.ATR_threshold = this.settings.ATR_threshold;
// debug stuff
this.startTime = new Date();
/* MESSAGES */
// message the user about required history
log.info("====================================");
log.info('Running', this.name);
log.info('====================================');
log.info("Make sure your warmup period matches the longer of ATR or ADX and that Gekko downloads data if needed");
// warn users
if (this.requiredHistory < this.settings.ATR || this.requiredHistory < this.settings.ATR) {
log.warn("*** WARNING *** Your Warmup period is lower than ATR|ADX. If Gekko does not download data automatically when running LIVE the strategy will not behave as expected");
}
}, // init()
heikenAshi: function(candle) {
return {
close: (candle.open + candle.close + candle.high + candle.low) / 4,
open: (this.previousCandle.open + this.previousCandle.close) / 2,
high: _.max([candle.high, candle.open, candle.close]),
low: _.min([candle.low, candle.open, candle.close])
};
},
updateMinMax: function(candle) {
//Gets the minimum and maximum trade price from the last ATR Period
// Add latest high and low to arrays at counter index
this.lows[this.arrayCounter] = candle.low;
this.highs[this.arrayCounter] = candle.high;
//increment index
this.arrayCounter = this.arrayCounter + 1;
if (this.arrayCounter === this.settings.ATR) { //reset index if at array length
this.arrayCounter = 0;
}
//Set newest period max and min
this.periodMax = Math.max.apply(Math, this.highs);
this.periodMin = Math.min.apply(Math, this.lows);
},
updateMinMaxAverage: function(candle) {
//same as above except it outputs the average low/high instead of the min/max
// Add latest high and low to arrays at counter index
this.lows[this.arrayCounter] = candle.low;
this.highs[this.arrayCounter] = candle.high;
//increment index
this.arrayCounter = this.arrayCounter + 1;
if (this.arrayCounter === this.settings.ATR) { //reset index if at array length
this.arrayCounter = 0;
}
let averageMin = 0,
averageMax = 0;
for (let i in this.lows) {
averageMin += this.lows[i];
averageMax += this.highs[i];
}
//Set newest period max and min
this.periodMax = averageMin / this.lows.length;
this.periodMin = averageMax / this.highs.length;
},
resetMinMax: function() {
// Resets the minimum and maximum arrays.
// Thought is that doing this each trade limits the effect of past data that is no longer that relevant
for (var i in this.lows) {
this.lows[i] = this.HACandle.low;
this.highs[i] = this.HACandle.high;
}
},
getBullMultiplier: function(adx) {
// Returns the correct Bull multiplier depending on ADX value
if (adx < this.ATR_threshold) {
return this.settings.BULL_Multiplier_high;
} else {
return this.settings.BULL_Multiplier_low;
}
},
getBearMultiplier: function(adx) {
// Returns the correct Bear multiplier depending on ADX value
if (adx < this.ATR_threshold) {
return this.settings.BEAR_Multiplier_high;
} else {
return this.settings.BEAR_Multiplier_low;
}
},
//called on each new candle, before check.
update: function(candle) {
// this.updateMinMax(candle);
this.HACandle = this.heikenAshi(candle);
this.updateMinMaxAverage(this.HACandle);
this.previousCandle = candle; //for HA calculation
},
/* CHECK */
check: function() {
// get all indicators
let ind = this.tulipIndicators,
atr = ind.ATR.result.result,
adx = ind.ADX.result.result,
di = ind.DI.result,
price = this.HACandle.close;
//Reset the min max if the trend is new
if (this.newTrend) {
this.resetMinMax(this.HACandle);
}
//Check for long
if (this.trend === 'bull') {
//Calculate new stop target
//Bull trend so stop needs to be below the trendline.
let newStop = this.periodMax - (atr * this.getBullMultiplier(adx))
// Stop can only increase, therefore only use the new stop if it is higher than current stop
// If trend is new, bull stop will never be higher than bear stop, therefore use it anyway.
if (newStop > this.stop || this.newTrend) {
this.stop = newStop;
this.newTrend = false;
}
//If candle close price has passed the latest stop, change advice to short
if (price <= this.stop) {
this.short();
}
} else if (this.trend === 'bear') {
//Calculate new stop target
let newStop = this.periodMin + (atr * this.getBearMultiplier(adx))
if (newStop < this.stop || this.newTrend) {
this.stop = newStop;
this.newTrend = false;
}
//check if price has hit target
if (price >= this.stop) {
this.long();
}
}
// This will only run on the very first candle. Buys if current trend is bull, sells if bear.
else if (this.trend === 'none') {
if (di.plus_di > di.minus_di) { //BULL
this.long();
} else {
this.short();
}
}
if (this.debug) {
log.info('Current Trend: ' + this.trend);
log.info('Period Min: ' + this.periodMin);
log.info('Period Max: ' + this.periodMax);
log.info('Current Stop: ' + this.stop);
log.info('Current Price: ' + price);
log.info('\n\n');
}
}, // check()
/* LONG */
long: function() {
if (this.trend !== 'bull') // new trend? (only act on new trends)
{
this.trend = 'bull';
this.newTrend = true;
this.advice('long');
}
},
/* SHORT */
short: function() {
// new trend? (else do things)
if (this.trend !== 'bear') {
this.trend = 'bear';
this.newTrend = true;
this.advice('short');
}
},
/* END backtest */
end: function() {
let seconds = ((new Date() - this.startTime) / 1000),
minutes = seconds / 60,
str;
minutes < 1 ? str = seconds.toFixed(2) + ' seconds' : str = minutes.toFixed(2) + ' minutes';
log.info('====================================');
log.info('Finished in ' + str);
log.info('====================================');
}
};
module.exports = strat;