incumque
Version:
Multi Exchange Crypto Currency Trading bot, Data Analysis Library and Strategy Back testing Engine
177 lines (160 loc) • 7.1 kB
JavaScript
const {Strategy} = require("./Strategy")
/**
* Class EmaTrend
<pre>
* This class is another simple example on how to write Strategies
*
* It uses a relative strength index and 2 exponential average to try and find trade entry positions
*
* Below are the conditions for a long and a short trade
*
* Long entry conditions
* 1. The fast moving average must be above the slow moving average
* 2. The 2 exponential averages should be closely squeezed to trigger a Signal
* 3. The RSI must be below the provided limit default is 50
*
* Short entry conditions
* 1. The fast moving average must be below the slow moving average
* 2. The 2 exponential averages should be closely squeezed to trigger a Signal
* 3. The RSI must be above the provided limit default is 50
</pre>
*/
class EmaTrend extends Strategy{
static RSI=Strategy.INDICATORS.RsiIndicator.className;
static EMA= Strategy.INDICATORS.EMAIndicator.className;
static ATR = Strategy.INDICATORS.AtrIndicator.className;
/**
* @typedef {Object} emaTrendExtras Engine configuration options
* @property {Number} periodFast Strategy property, the multiplier to use determine trend direction changes
* @property {Number} periodSlow Strategy property, the fast period to use for moving average
* @property {Number} periodSlow Strategy property, the slow period to use for moving average
* @property {Number} squeezeFactor Strategy property, the squeeze factor or the needed difference between the distance between fast ema and slow ema
* @property {Number} rsiLimit Strategy property, the rsi required limit for confirmation default is 50
*
*
*/
/**
* @typedef {Object} emaTrendConfig Strategy configuration options
* @property {number} sidePreference Strategy property, the trading preference long|short/biDirectional
* @property {emaTrendExtras} strategyExtras Strategy property, strategy specific arguments for custom implementations
*/
/**
*
* @param args {emaTrendConfig} - The Strategies Parameters
* @return {EmaTrend}
*/
static init(args){
return new EmaTrend( args);
}
/**
*
* @param args {emaTrendConfig} - The Strategies Parameters
*/
constructor(args) {
super(args);
this.setContext("EmaTrend")
this.RSI = null;
this.emaFast = null;
this.emaSlow = null;
this.periodFast = args.strategyExtras && args.strategyExtras.periodFast || 10;
this.periodSlow = args.strategyExtras && args.strategyExtras.periodSlow || 20;
this.sidePreference = args.sidePreference || 'long';
this.currentTrend = this.states.STATE_PENDING;
this.squeezeFactor = args.strategyExtras && args.strategyExtras.squeezeFactor || 1.9;
this.bounceSlow = true;
}
/**
*
* @param state Sets the State of the Strategy usually called from within the BiFox Engine
*/
setState(state){ this.state = state; }
/**
*
* @return {String} the string representation of the current state in the Strategy
*/
getState(){ return this.state}
/**
*
* @param klineCandles {Array<Array<Number>>} Sets up the Strategy with Indicator Data and Historical Candle data
*/
async setup(klineCandles){
this.setIndicator(klineCandles,{period:14},EmaTrend.RSI);
this.RSI = this.getIndicator();
this.setIndicator(klineCandles,{},EmaTrend.ATR);
this.atr = this.getIndicator();
this.setIndicator(klineCandles,{period:this.periodFast},EmaTrend.EMA);
this.emaFast = this.getIndicator()
this.setIndicator(klineCandles,{period:this.periodSlow},EmaTrend.EMA);
this.emaSlow = this.getIndicator();
return this;
}
/**
*
* @return {Array<any>} returns an Indicator Data Array
*/
getIndicator(){return super.getIndicator()}
/**
*
* @param {number} _index
* @param {boolean} isBackTest
* @param {ticker} ticker
* @return {Promise<{custom: {}, context: null, state, timestamp: number}>}
*/
async run(_index=0, isBackTest=false, ticker=null){
let currPrice = this.kline.o[isBackTest ? _index : this.kline.o.length-1];
let currSlowEma = this.emaSlow[isBackTest ? _index : this.emaSlow.length-1];
let currFastEma = this.emaFast[isBackTest ? _index : this.emaFast.length-1];
let currRsi = this.RSI[isBackTest ? _index : this.RSI.length - 1];
let currATR = this.atr[isBackTest ? _index : this.atr.length - 1];
this.getTrendDirection(currSlowEma,currFastEma)
if(this.state === this.states.STATE_ENTER_LONG || this.state === this.states.STATE_ENTER_SHORT){
this.state = this.states.STATE_AWAIT_ORDER_FILLED;
return this.getStrategyResult(this.state, {});
}if(this.state === this.states.STATE_PENDING){
if(this.currentTrend === this.states.STATE_TREND_UP){
if(currRsi >=50 && this.calculateDistance(currPrice,currSlowEma,currFastEma,currATR) <= this.squeezeFactor ){
this.state = (this.sidePreference === 'long' || this.sidePreference === 'biDirectional') ? this.states.STATE_ENTER_LONG : this.state;
}if(currRsi < 50 && this.calculateDistance(currPrice,currSlowEma,currFastEma,currATR) <= this.squeezeFactor ){
this.state = (this.sidePreference === 'short' || this.sidePreference === 'biDirectional') ? this.states.STATE_ENTER_SHORT : this.state;
}
}
if(this.currentTrend === this.states.STATE_TREND_UP){
}else{
this.getTrendDirection(currSlowEma,currFastEma)
}
return this.getStrategyResult(this.state,{
rsi:currRsi,
close:currPrice,
emaSlow:currSlowEma,
emaFast:currFastEma
});
}
return this.getStrategyResult(this.state,{
rsi:currRsi,
close:currPrice,
emaSlow:currSlowEma,
emaFast:currFastEma
});
}
getTrendDirection(slowEma, fastEma){
this.currentTrend = fastEma>slowEma ? this.states.STATE_TREND_UP : (fastEma<slowEma) ? this.states.STATE_TREND_DOWN : this.state.STATE_PENDING;
}
/**
*
* @param {number} currPrice - the current price
* @param {number} currSlowEma - the current slow moving exponential moving average
* @param {number} currFastEma - the current fast moving exponential moving average
* @param {number} currATR - the current Average true range
* @return {number} the distance between the ema's over the atr
*/
calculateDistance(currPrice,currSlowEma,currFastEma,currATR){
let targetEma = this.bounceSlow ? currSlowEma : currFastEma;
let distance = currPrice > targetEma ? (currPrice-targetEma) : targetEma - currPrice;
return distance / currATR;
}
}
/**
*
* @type {{EmaTrend: EmaTrend}}
*/
module.exports = {EmaTrend:EmaTrend}