Gekko Forum
[BOUNTY] Adaptive ATR-ADX Trend - Printable Version

+- Gekko Forum (https://forum.gekko.wizb.it)
+-- Forum: Gekko (https://forum.gekko.wizb.it/forum-13.html)
+--- Forum: Feature Requests (https://forum.gekko.wizb.it/forum-15.html)
+--- Thread: [BOUNTY] Adaptive ATR-ADX Trend (/thread-1431.html)



[BOUNTY] Adaptive ATR-ADX Trend - briancrypto - 02-03-2018

I know nothing about coding but what I do know is that I have been manually using the following script from tradingview and killing it everyday. Now if gekko could be coded to run this script I could get away from sitting infornt of a computer. lol

The script I have been using is located here: https://www.tradingview.com/script/H48yeyRa-Adaptive-ATR-ADX-Trend-V2/

Here is the code:
Code:
//@version=2
// Constructs the trailing ATR stop above or below the price, and switches
// directions when the source price breaks the ATR stop. Uses the Average
// Directional Index (ADX) to switch between ATR multipliers. The higher
// multiplier is used when the ADX is rising, and the lower ATR multiplier
// is used with the ADX is falling. This ADX criteria further widens the gap
// between the source price and the trailing ATR stop when the price is trending,
// and lessens the gap between the ATR and the price when then price is not
// trending.
//
// The ATR-ADX stop is effectively a double adapative stop that trails the price,
// by both adapting to the true range of the price, and the average directional
// change. When the stop is below the price (long trade) the value never decreases
// until the price intersects the stop, and it reverses to being above the price
// (short trade). When the stop is above the price it will never increase until
// it is intersected by the price. As the true range and ADX change, the stop
// will move more quickly or more slowly.
//
// Version 2 adds four additional utilities.
//
// First, if the 'Above Threshold' box is checked,the falling (smaller) multiplier
// will be used regardless once the ADX rises above a certain threshold (default > 30).
// The ATR will effectively rise faster once the price enters 'very trendy' mode.
// Typically, ADX > 20/25 is used in classic ADX trading (which I have found
// unprofitable through backtesting). The idea behind this extra multiplier criteria
// is that once the price starts trending 'very' well, a top is most likely near,
// and when that top comes the price will quickly rebound. Experienced traders know
// exactly what I am describing. Play around with an ADX/DI indicator using this
// stop system in tandem and it will be found that many successful trades are entered
// when the market is not trending (i.e. < ADX < 25), and exited once/if the price
// enters 'very trendy' mode above 30 and the ATR changes (stopped out).
//
// Second, heiken-ashi bars can be introduced to the ATR stop system. This is the same
// thing as just switching the chart to Heiken Ashi mode, but without having to change
// the plotting type. I find this useful, so that things like pivot lines can be
// preserved to their correct calculations, while the benefit of heiken ashi bars can
// still be enjoyed.
//
// Third, Different source prices can be used. I have found that HLC3 is best because
// it keeps the price from being stopped out in very key areas, set as the default.
//
// Fourth, alert conditions are introduced so that the trader can be warned when the
// ATR-ADX changes. These can be used by right clicking the strategy and clicking
// "Add Alert...". Reference the bottom of the script for the names of the alert
// conditions.
//
// See also: http://www.fxtsp.com/1287-doubly-adaptive-profit-average-true-range-objectives/

study(title = "Adaptive ATR-ADX Trend V2", shorttitle = "Adaptive ATR V2", overlay = true)

//Mode
src = input(title = "Source", type = source, defval = hlc3)
atrLen = input(title = "ATR", type = integer, defval = 21, minval = 1, maxval = 100)
m1 = input(title = "ATR Multiplier - ADX Rising", type = float, defval = 3.5, minval = 1, step = 0.1, maxval = 100)
m2 = input(title = "ATR Multiplier - ADX Falling", type = float, defval = 1.75, minval = 1, step = 0.1, maxval = 100)

adxLen = input(title = "ADX", type = integer, defval = 14, minval = 1, maxval = 100)
adxThresh = input(title = "ADX Threshold", type = integer, defval = 30, minval = 1)
aboveThresh = input(true, title = "ADX Above Threshold uses ATR Falling Multiplier Even if Rising?")
useHeiken = input(false, title = "Use Heiken-Ashi Bars (Source will be ohlc4)")
   
// DI-Pos, DI-Neg, ADX

hR = change(high)
lR = -change(low)

dmPos = hR > lR ? max(hR, 0) : 0
dmNeg = lR > hR ? max(lR, 0) : 0

sTR = nz(sTR[1]) - nz(sTR[1]) / adxLen + tr
sDMPos = nz(sDMPos[1]) - nz(sDMPos[1]) / adxLen + dmPos
sDMNeg = nz(sDMNeg[1]) - nz(sDMNeg[1]) / adxLen + dmNeg

DIP = sDMPos / sTR * 100
DIN = sDMNeg / sTR * 100
DX = abs(DIP - DIN) / (DIP + DIN) * 100
adx = sma(DX, adxLen)

// Heiken-Ashi

xClose = ohlc4
xOpen = (nz(xOpen[1]) + nz(close[1])) / 2
xHigh = max(high, max(xOpen, xClose))
xLow = min(low, min(xOpen, xClose))

// Trailing ATR

v1 = abs(xHigh - xClose[1])
v2 = abs(xLow - xClose[1])
v3 = xHigh - xLow

trueRange = max(v1, max(v2, v3))
atr = useHeiken ? rma(trueRange, atrLen) : atr(atrLen)

m = rising(adx, 1) and (adx < adxThresh or not aboveThresh) ? m1 : falling(adx, 1) or (adx > adxThresh and aboveThresh) ? m2 : nz(m[1])
mUp = DIP >= DIN ? m : m2
mDn = DIN >= DIP ? m : m2

src_ = useHeiken ? xClose : src
c = useHeiken ? xClose : close
t = useHeiken ? (xHigh + xLow) / 2 : hl2

up = t - mUp * atr
dn = t + mDn * atr

TUp = max(src_[1], c[1]) > TUp[1] ? max(up, TUp[1]) : up
TDown = min(src_[1], c[1]) < TDown[1] ? min(dn, TDown[1]) : dn

trend = min(src_, min(c, close)) > TDown[1] ? 1 : max(src_, max(c, close)) < TUp[1]? -1 : nz(trend[1], 1)
stop = trend == 1 ? TUp : TDown
trendChange = change(trend)

// Plot

lineColor = not(trendChange) ? trend > 0 ? #00FF00DD : #FF0000DD : #00000000
shapeColor = trendChange ? trendChange > 0 ? #00FF00F8 : #FF0000F8 : #00000000

plot(stop, color = lineColor, style = line, linewidth = 1, title = "ATR Trend")
plotshape(trendChange ? stop : na, style = shape.circle, size = size.tiny, location = location.absolute, color = shapeColor, title = "Change")

alertcondition(trendChange > 0, title = "ATR-ADX Change Up", message = "ATR-ADX Change Up")
alertcondition(trendChange < 0, title = "ATR-ADX Change Down", message = "ATR-ADX Change Down")

// end
Bounty offered.. .003 BTC


RE: [BOUNTY] Adaptive ATR-ADX Trend - askmike - 02-03-2018

Great stuff, 2 small questions:

- There are some differences with how Gekko and Tradeview aggregate data, which means that even if the implementation is 100% correct there might still be minor differences in output. Is this acceptable?
- Tradeview does more than give out signals: it can also draw lines and points on the chart, this is not (yet) supported for Gekko. Would it be acceptable that the strategy only gives out signals (either buy or sell)?

If you agree with both, please send the bounty amount to this address 15s9j4DYTxnCV3JEbNBeMJYEzM2St3Gq8f where I'll keep it in escrow. Thanks!


RE: [BOUNTY] Adaptive ATR-ADX Trend - briancrypto - 02-03-2018

(02-03-2018, 05:54 PM)askmike Wrote: Great stuff, 2 small questions:

- There are some differences with how Gekko and Tradeview aggregate data, which means that even if the implementation is 100% correct there might still be minor differences in output. Is this acceptable? - YES
- Tradeview does more than give out signals: it can also draw lines and points on the chart, this is not (yet) supported for Gekko. Would it be acceptable that the strategy only gives out signals (either buy or sell)?  - YES

If you agree with both, please send the bounty amount to this address 15s9j4DYTxnCV3JEbNBeMJYEzM2St3Gq8f where I'll keep it in escrow. Thanks!

Askmike-
Thank you for replying... 
I think even with minor difference this strategy has the potential to be huge for everyone. I say we give it a shot. 

I just tried to send the bounty of .003 from bittrex but the amount is to small.. I am guessing because of the transaction fees. Any suggestions how to send this amount from bittrex. I can send via another coin from bittrex if preferred in order to avoid the high btc fees.


RE: [BOUNTY] Adaptive ATR-ADX Trend - askmike - 02-04-2018

You can also send me ADA, note that I won't be converting that to bitcoin. So that means you should update the bounty reward from 0.003 BTC to X ada (however much you want to send). You can send those here: DdzFFzCqrhsgQhxh4FPJTKphvzmKHLDJJ6sBFrE18TopT1pRjSNCpQqG8b4ezqmeWGFH6iwN17D84UaVEoLuhpv7h6CoV1fJsaXFit7Z

If you rather send something else instead let me know what and I'll see if I can get a wallet easily (you can also do ETH or LTC if you rather).

I'll confirm once I have received them (with the amount).


RE: [BOUNTY] Adaptive ATR-ADX Trend - Gryphon - 03-01-2018

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: 
[Image: attachment.php?aid=59]

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/develop/config/strategies/ATR_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/develop/strategies/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;



RE: [BOUNTY] Adaptive ATR-ADX Trend - hasitt - 06-08-2018

(03-01-2018, 10:50 AM)Gryphon Wrote: 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 thoug

Hi and thanks very much for your work on this, it's a pretty good strategy as is. I'm neither a javascript nor a pine expert but I've been going through the code and how the stop works and I may have found something of use. Are you still looking into this?

The stop movements that I see on a TradingView chart that I don't see in your code occurs when the +DI drops below 30 in a long. In this situation the stop is increased in value (decreased in a short) by an adaptive amount that I haven't figured out how to clearly calculate... yet. This obviously brings the stop closer to the current price as a trend is running out of steam.

The code to bring the stop closer is calculating a normalised adx that has a slightly different value to the regular ADX. I plotted the graph and in the areas I checked it was +2-5, for example, if regular ADX was 15, this other adx was 20 or 17. I went through the pine code where he normalised the data to achieve this adaptive stop and I changed some of the variable names to make them a bit more descriptive. These are lines 64-77 from the original script 

Code:
highChange = currentHigh - prevHigh;
lowChange = -(currentLow - prevLow);

if (highChange > lowChange) {
dmPos = max(highChange, 0);
}
else {
dmPos = 0;
}
if (lowChange > highChange) {
dmNeg = max(lowChange, 0);
}
else {
dmNeg = 0;
}


trueRange = (prevTR - prevTR/adxLen + currentTR;
DMPos = prevDMPos - prevDMPos/adxLen + currentDMPos;
DMNeg = prevDMNeg - prevDMNeg/adxLen + currentDMNeg;

DiPos = DMPos/trueRange * 100;
DiNeg = DMNeg/trueRange  * 100;
DX = abs(DiPos - DiNeg) / (DiPos + DiNeg) * 100;
adx = sma(DX, adxLen);

It looks like he is normalising the values by dividing the previous periods values of (TR/+DMI/-DMI) by ADX length, then dividing them again by the normalised true range in the final block. After that he calculates the DX using what looks like a relative change/difference formula. This gives a regularised and smoothed adx based on TR and DM.

This is not the complete part of the code that is missing but it is a start. I may have overlooked something in your code as I have been focusing more on the original to get to grips with that first. Please let me know if I have missed anything obvious so far. I'll continue looking at how this is used later on in the original script.