Bitvested / ta.js

Financial Technical Analysis in JavaScript
MIT License
141 stars 33 forks source link

rsi divergence #18

Closed btm2021 closed 1 year ago

btm2021 commented 1 year ago

hi devs. first thanks your amazing lib. divergence is important of TA. could you implement to ta.js example here https://www.tradingview.com/script/AkWCX6pt-RSI-Divergence-Indicator-w-Alerts/ this is rsi divergence.

thanks for amazing lib.

Vultwo commented 1 year ago

Hi, thank you. Unfortunately I'm not very good at converting Pine Script code to JavaScript. I will keep the issue open if anyone wants to decide to help.

btm2021 commented 1 year ago

tks for reply. here is sample about detect diverger is python :https://github.com/SpiralDevelopment/RSI-divergence-detector in js : https://github.com/bkawk/divergence

in sample js

To detect the divergence of the relative strength index (RSI) and price in JavaScript, you can use the following steps:

Calculate the RSI values for a given time period, using the following formula: RSI = 100 - (100 / (1 + (average of up days / average of down days)))

Calculate the price change for the same time period, by subtracting the first closing price from the last closing price.

If the price has increased and the RSI has decreased, or if the price has decreased and the RSI has increased, then there is a divergence between the RSI and price.

Here's some example code that demonstrates these steps:

Copy code function detectDivergence(prices, timePeriod) { // Calculate the RSI values let upDays = 0; let downDays = 0; for (let i = 1; i < timePeriod; i++) { let priceChange = prices[i] - prices[i - 1]; if (priceChange > 0) { upDays += priceChange; } else { downDays -= priceChange; } } let avgUpDays = upDays / timePeriod; let avgDownDays = downDays / timePeriod; let rs = avgUpDays / avgDownDays; let rsi = 100 - (100 / (1 + rs));

// Calculate the price change let priceChange = prices[timePeriod - 1] - prices[0];

// Check for divergence if ((priceChange > 0 && rsi < 0) || (priceChange < 0 && rsi > 0)) { console.log("There is a divergence between the RSI and price"); } else { console.log("There is no divergence between the RSI and price"); } }

let prices = [100, 105, 110, 100, 95, 105]; detectDivergence(prices, 3); // There is no divergence between the RSI and price

here is javascript convert of pinescript function rsiDivergenceIndicator(data, len, src, lbR, lbL, rangeUpper, rangeLower, plotBull, plotHiddenBull, plotBear, plotHiddenBear) { const osc = rsi(src, len); const obLevel = hline(70); const osLevel = hline(30); const plFound = pivotlow(osc, lbL, lbR); const phFound = pivothigh(osc, lbL, lbR); const _inRange = (cond) => { const bars = barssince(cond === true); return rangeLower <= bars && bars <= rangeUpper; }

// Regular Bullish const oscHL = osc[lbR] > valuewhen(plFound, osc[lbR], 1) && _inRange(plFound[1]); const priceLL = low[lbR] < valuewhen(plFound, low[lbR], 1); const bullCond = plotBull && priceLL && oscHL && plFound;

// Hidden Bullish const oscLL = osc[lbR] < valuewhen(plFound, osc[lbR], 1) && _inRange(plFound[1]); const priceHL = low[lbR] > valuewhen(plFound, low[lbR], 1); const hiddenBullCond = plotHiddenBull && priceHL && oscLL && plFound;

// Regular Bearish const oscLH = osc[lbR] < valuewhen(phFound, osc[lbR], 1) && _inRange(phFound[1]); const priceHH = high[lbR] > valuewhen(phFound, high[lbR], 1); const bearCond = plotBear && priceHH && oscLH && phFound;

// Hidden Bearish const oscHH = osc[lbR] > valuewhen(phFound, osc[lbR], 1) && _inRange(phFound[1]); const priceLH = high[lbR] < valuewhen(phFound, high[lbR], 1); const hiddenBearCond = plotHiddenBear && priceLH && oscHH && phFound;

please consider add to your lib. tks so much.

Vultwo commented 1 year ago

Thanks for the code, I'll try to add this as soon as possible. I hope to have a updated version of the library online by next week.

btm2021 commented 1 year ago

if lib have divengencer it is big move. tks so much.

Vultwo commented 1 year ago

Hi, I have uploaded a new version. Could you check if it actually does what you need it to do. I couldn't figure out what these values meant:

// Regular Bullish const oscHL = osc[lbR] > valuewhen(plFound, osc[lbR], 1) && _inRange(plFound[1]); const priceLL = low[lbR] < valuewhen(plFound, low[lbR], 1); const bullCond = plotBull && priceLL && oscHL && plFound;

// Hidden Bullish const oscLL = osc[lbR] < valuewhen(plFound, osc[lbR], 1) && _inRange(plFound[1]); const priceHL = low[lbR] > valuewhen(plFound, low[lbR], 1); const hiddenBullCond = plotHiddenBull && priceHL && oscLL && plFound;

// Regular Bearish const oscLH = osc[lbR] < valuewhen(phFound, osc[lbR], 1) && _inRange(phFound[1]); const priceHH = high[lbR] > valuewhen(phFound, high[lbR], 1); const bearCond = plotBear && priceHH && oscLH && phFound;

// Hidden Bearish const oscHH = osc[lbR] > valuewhen(phFound, osc[lbR], 1) && _inRange(phFound[1]); const priceLH = high[lbR] < valuewhen(phFound, high[lbR], 1); const hiddenBearCond = plotHiddenBear && priceLH && oscHH && phFound;

Vultwo commented 1 year ago

RSI Divergence (from docs)

var data = [74,83,66,78,69,70,84,73,74,73,83];
var rsi_length = 5;
var rsi_function = ta.wrsi; // default (the tradingview rsi indicator)
await ta.rsi_divergence(data, rsi_length, rsi_function);
// output (array)
// [0, 0, 1, 0, 1, 0] (better to quantify if needed)

Code:

async function rsi_divergence(data, length, rs) {
  if(!rs) rs = module.exports.wrsi;
  var rd = await rs(data, length), out = [];
  data = await module.exports.mom(data.slice(length-1, data.length), 2);
  for(var i = 0; i < data.length; i++) {
    if((data[i] > 0 && rd[i] < 0) || (data[i] < 0 && rd[i] > 0)) {
      out.push(1);
    } else {
      out.push(0);
    }
  }
  return out;
}
btm2021 commented 1 year ago
Screen Shot 2023-01-15 at 7 28 16 PM Screen Shot 2023-01-15 at 7 28 39 PM

work like a charm. tks so much. good job men.

phattranky commented 1 year ago

Hi @btm2021 ,

Could you share your code how do you map the 0 1 in the result to the price position as the above screenshot. Thanks

phattranky commented 1 year ago

Let's say I have the result is

[0,1,1,0,1,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,1,0,0,1,1,0,0,1,0,1,0,0,0,1,1,1,1,1,0,0,0,1,0,0,1,0,1,0,1,0,0,0,1,1,1,0,0,1,1,0,0,1,0,0,0,0,0,1,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,1,0]

How can I know which point is the start point and endpoint to draw the line ?

Vultwo commented 1 year ago

Let's say I have the result is

[0,1,1,0,1,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,1,0,0,1,1,0,0,1,0,1,0,0,0,1,1,1,1,1,0,0,0,1,0,0,1,0,1,0,1,0,0,0,1,1,1,0,0,1,1,0,0,1,0,0,0,0,0,1,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,1,0]

How can I know which point is the start point and endpoint to draw the line ?

@phattranky I'm not really sure how he maps the values in the array, my best guess is he just loops over it using a for loop. The index zero of the output is the first value (oldest data point). So you should splice the first values from the original data before putting them into the array.

phattranky commented 1 year ago

Thanks @Vultwo

As I know, There are 6 kinds of Divergence. I mean with your function How can we detect it?

diverCapture

As your example

var data = [74,83,66,78,69,70,84,73,74,73,83];
var rsi_length = 5;
var rsi_function = ta.wrsi; // default (the tradingview rsi indicator)
await ta.rsi_divergence(data, rsi_length, rsi_function);
// output (array)
// [0, 0, 1, 0, 1, 0] (better to quantify if needed)

How can We know the divergence is bear or bull and which Candles (closed price) are connected become a line ?

@btm2021 in case you can help.

Thanks a lot

phattranky commented 1 year ago

Here is the code of TradingView, But I don't how to convert it to Javascript

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © JayTradingCharts

//@version=4
//@version=4  
//---- thank to mohanee for the base code. we modified this to not give entries but instead alert on the just the divs 
study(title="RSI Divergence Indicator w/Alerts", format=format.price, resolution="")
len = input(title="RSI Period", minval=1, defval=14)
src = input(title="RSI Source", defval=close)
lbR = input(title="Pivot Lookback Right", defval=5)
lbL = input(title="Pivot Lookback Left", defval=5)
rangeUpper = input(title="Max of Lookback Range", defval=60)
rangeLower = input(title="Min of Lookback Range", defval=5)
plotBull = input(title="Plot Bullish", defval=true)
plotHiddenBull = input(title="Plot Hidden Bullish", defval=false)
plotBear = input(title="Plot Bearish", defval=true)
plotHiddenBear = input(title="Plot Hidden Bearish", defval=false)
bearColor = color.red
bullColor = color.green
hiddenBullColor = color.new(color.green, 80)
hiddenBearColor = color.new(color.red, 80)
textColor = color.white
noneColor = color.new(color.white, 100)
osc = rsi(src, len)

plot(osc, title="RSI", linewidth=2, color=#8D1699)
hline(50, title="Middle Line", linestyle=hline.style_dotted)
obLevel = hline(70, title="Overbought", linestyle=hline.style_dotted)
osLevel = hline(30, title="Oversold", linestyle=hline.style_dotted)
fill(obLevel, osLevel, title="Background", color=#9915FF, transp=90)

plFound = na(pivotlow(osc, lbL, lbR)) ? false : true
phFound = na(pivothigh(osc, lbL, lbR)) ? false : true
_inRange(cond) =>
    bars = barssince(cond == true)
    rangeLower <= bars and bars <= rangeUpper

//------------------------------------------------------------------------------
// Regular Bullish
// Osc: Higher Low

oscHL = osc[lbR] > valuewhen(plFound, osc[lbR], 1) and _inRange(plFound[1])

// Price: Lower Low

priceLL = low[lbR] < valuewhen(plFound, low[lbR], 1)
bullCond = plotBull and priceLL and oscHL and plFound

plot(
     plFound ? osc[lbR] : na,
     offset=-lbR,
     title="Regular Bullish",
     linewidth=2,
     color=(bullCond ? bullColor : noneColor),
     transp=0
     )

plotshape(
     bullCond ? osc[lbR] : na,
     offset=-lbR,
     title="Regular Bullish Label",
     text=" Bull ",
     style=shape.labelup,
     location=location.absolute,
     color=bullColor,
     textcolor=textColor,
     transp=0
     )

//------------------------------------------------------------------------------
// Hidden Bullish
// Osc: Lower Low

oscLL = osc[lbR] < valuewhen(plFound, osc[lbR], 1) and _inRange(plFound[1])

// Price: Higher Low

priceHL = low[lbR] > valuewhen(plFound, low[lbR], 1)
hiddenBullCond = plotHiddenBull and priceHL and oscLL and plFound

plot(
     plFound ? osc[lbR] : na,
     offset=-lbR,
     title="Hidden Bullish",
     linewidth=2,
     color=(hiddenBullCond ? hiddenBullColor : noneColor),
     transp=0
     )

plotshape(
     hiddenBullCond ? osc[lbR] : na,
     offset=-lbR,
     title="Hidden Bullish Label",
     text=" H Bull ",
     style=shape.labelup,
     location=location.absolute,
     color=bullColor,
     textcolor=textColor,
     transp=0
     )

//------------------------------------------------------------------------------
// Regular Bearish
// Osc: Lower High

oscLH = osc[lbR] < valuewhen(phFound, osc[lbR], 1) and _inRange(phFound[1])

// Price: Higher High

priceHH = high[lbR] > valuewhen(phFound, high[lbR], 1)

bearCond = plotBear and priceHH and oscLH and phFound

plot(
     phFound ? osc[lbR] : na,
     offset=-lbR,
     title="Regular Bearish",
     linewidth=2,
     color=(bearCond ? bearColor : noneColor),
     transp=0
     )

plotshape(
     bearCond ? osc[lbR] : na,
     offset=-lbR,
     title="Regular Bearish Label",
     text=" Bear ",
     style=shape.labeldown,
     location=location.absolute,
     color=bearColor,
     textcolor=textColor,
     transp=0
     )

//------------------------------------------------------------------------------
// Hidden Bearish
// Osc: Higher High

oscHH = osc[lbR] > valuewhen(phFound, osc[lbR], 1) and _inRange(phFound[1])

// Price: Lower High

priceLH = high[lbR] < valuewhen(phFound, high[lbR], 1)

hiddenBearCond = plotHiddenBear and priceLH and oscHH and phFound

plot(
     phFound ? osc[lbR] : na,
     offset=-lbR,
     title="Hidden Bearish",
     linewidth=2,
     color=(hiddenBearCond ? hiddenBearColor : noneColor),
     transp=0
     )

plotshape(
     hiddenBearCond ? osc[lbR] : na,
     offset=-lbR,
     title="Hidden Bearish Label",
     text=" H Bear ",
     style=shape.labeldown,
     location=location.absolute,
     color=bearColor,
     textcolor=textColor,
     transp=0
     )

alertcondition(bullCond, title="Bull", message="Regular Bull Div {{ticker}} XXmin")
alertcondition(bearCond, title="Bear", message="Regular Bear Div {{ticker}} XXmin")
alertcondition(hiddenBullCond, title="H Bull", message="Hidden Bull Div {{ticker}} XXmin")
alertcondition(hiddenBearCond, title="H Bear", message="Hidden Bear Div {{ticker}} XXmin")
Vultwo commented 1 year ago

@phattranky The code seems to make use of pivot points to determine wether the current state is in regular or hidden. This library also contains a function for calculating these pivot points (fractals or zigzag). You could calculate the current state by determining wether the pivot points of the rsi and price are also diverging. I will create a function for this, but I don't know when it's ready.

As I know, There are 6 kinds of Divergence. I mean with your function How can we detect it?

diverCapture

phattranky commented 1 year ago

@Vultwo Thanks bro. Hope We can have it soon. It must a great function.

btm2021 commented 1 year ago

hello all, sorry because i in my holiday. @phattranky i just get result and compare with timestamp of ohvcl. in my picture is tradingview library. you can get here https://www.tradingview.com/HTML5-stock-forex-bitcoin-charting-library/?feature=technical-analysis-charts. get result of scan and format to right timestamp and use tradingview lib mark this scan. it not thing else.

btm2021 commented 1 year ago

important when check div is use right point. example. u can use pivot of ta.js and detect important zone of price and important zone of rsi and use function div to check. hidden div i think not useful because it not right, hidden div just simple lag of price or bigboy make market move. if you use hidden div it very noise. just check div regular. and make your choice.

btm2021 commented 1 year ago

and important. div is technical like rsi or bb. you should combine more signal to confirm your signal.

btm2021 commented 1 year ago

here my function got result and compare with ohcvl function arrangeResult(result, length) { for (i = 0; i < length - 1; i++) { result.unshift(0) } return result; } example ema.
let result = await ta.ema(data, period); result = arrangeResult(result, period); because nodejs not pandas like python. use this func and put together.

btm2021 commented 1 year ago

@Vultwo, i have write some function check swing high and swing low. and some func. utils. can you add to your lib ?

phattranky commented 1 year ago

Thanks @btm2021 ,

Instead of drawing on the chart, can we detect it on the code side? My idea is to create a bot that scans all the candle values in a period of time and notify to the user when they detect any divergence.

The result expected may be like

BTC has the regular bear closed on 15:00 - 1H
btm2021 commented 1 year ago

@phattranky hi, sorry i just write frond-end ui, grap data and show to chart, your idead seem interesting but i don't know how to write for that. sorry man

phattranky commented 1 year ago

Thanks @btm2021 . Yes scanning all the coins at the background is more effective than We have to check the UI one by one =))

Vultwo commented 1 year ago

@Vultwo, i have write some function check swing high and swing low. and some func. utils. can you add to your lib ?

Is this different from the zigzag and fractal indicators that are already in the library? All functions are welcome if you're willing to share them.

btm2021 commented 1 year ago

oh, i checked, it calc base high low and high swing and your zigzag do the same. my bad.

Vultwo commented 1 year ago

Sorry for the long wait @phattranky I'm still developing the function. The function won't be perfect though. The divergence can be in multiple states at once. The historical signal values of the function also wouldn't be perfect as the states can changes as more data is coming in. I'm finishing the function over the weekend.

Vultwo commented 1 year ago

I have finished the function. It would be appreciated if someone could check the function for correctness. Beware this function can 'repaint' as new data comes available. The function is by no means perfect, that's why I have put it in the experimental section.

var data1 = [48,34,43,54,56,64,43,51,52,53,55,51,48,45,40,42,44,45];
var data2 = [76,74,43,55,34,32,45,47,48,53,54,54,50,52,49,47,48,46];
var length = 12; // array length to check
var lookback = 3; // lookback length to check for recent_low / recent_high (please check function code for more info)
var threshold_exaggerated = 0.03; // percentual change threshold for 'exaggerated' divergence
var threshold_normal = 0.01; // percentual change threshold for 'normal' and 'hidden' divergence
ta.divergence_state(data1, data2, length, lookback, threshold_exaggerated, threshold_normal);
// output (array of arrays)
// [['convergence'],['divergence'],['convergence'],['divergence'],['convergence'],['exaggerated_bearish']]
// it is possible for multiple states to exist at once
function divergence_state(data1, data2, length, lb, threshold_ex=0.03, threshold_nm=0.01) { // [close]
  if(data1.length > data2.length) data1.splice(0,data1.length-data2.length);
  if(data2.length > data1.length) data2.splice(0,data2.length-data1.length);
  for(var i = length, out = []; i < data1.length; i++) {
    var pl1 = data1.slice(i-length,i+1);
    var support1 = support(pl1, recent_low(pl1, lb));
    var support1_delta = support1.slope / support1.lowest;
    var resistance1 = resistance(pl1, recent_high(pl1, lb));
    var resistance1_delta = resistance1.slope / resistance1.highest;
    var pl2 = data2.slice(i-length,i+1);
    var support2 = support(pl2, recent_low(pl2, lb));
    var support2_delta = support2.slope / support2.lowest;
    var resistance2 = resistance(pl2, recent_high(pl2, lb));
    var resistance2_delta = resistance2.slope / resistance2.highest;
    if((data1[i] > data1[i-1] && data2[i] < data2[i-1]) || (data1[i] < data1[i-1] && data2[i] > data2[i-1])) {
      var obj = [];
      if(resistance1_delta < -threshold_ex && resistance2_delta > -threshold_nm) obj.push('exaggerated_bearish');
      if(support1_delta < threshold_nm && support2_delta > threshold_ex) obj.push('exaggerated_bullish');
      if(resistance1_delta < -threshold_nm && resistance2_delta < threshold_nm) obj.push('hidden_bearish');
      if(support1_delta > threshold_nm && support2_delta < -threshold_nm) obj.push('hidden_bullish');
      if(resistance1_delta > threshold_nm && resistance2_delta < -threshold_nm) obj.push('regular_bearish');
      if(support1_delta < -threshold_nm && support2_delta > threshold_nm) obj.push('regular_bullish');
      if(obj.length <= 0) obj.push('divergence')
      out.push(obj);
    } else {
      out.push(['convergence'])
    }
  }
  return out;
}

Please let me know if you require any changes or if something isn't working as expected.

phattranky commented 1 year ago

Great. Thanks a lot, @Vultwo . I will check and get back to you later

phattranky commented 1 year ago

Hi @Vultwo

I have some questions, Could you help me to clarify?

  1. The data1 and data2 are the closed price values and RSI values, right?
  2. The convergence is meaning the normal state, right?
  3. If the question 2 is correct. Could you identify the bearish and bullish divergence like the exaggerated_bearish and exaggerated_bullish?

Thanks.

phattranky commented 1 year ago

About the data for testing. You can use the below API

https://fapi.binance.com/fapi/v1/continuousKlines?pair=ADAUSDT&contractType=PERPETUAL&interval=1h&startTime=1679436000000&endTime=1679612400000

And use the indicator Rsi diverence with Alert to backtest

2ADAUSDTPERP 0 3870 ▲ +5 19% Vô danh 2023-03-29 18-03-33 9999ADAUSDTPERP 0 3863 ▲ +5% Vô danh 2023-03-29 18-02-16

Vultwo commented 1 year ago

Hi @phattranky,

  1. Yes.
  2. Convergence is the state when no divergence takes place.
  3. The outputted states are: "exaggerated_bearish", "exaggerated_bullish", "hidden_bearish", "hidden_bullish", "regular_bearish", "regular_bullish". When none of these states is found (according to the threshold values for exaggerated / regular) the value outputted will be "divergence".

The problem with the trading view indicator is that the states only become known after the data has past. It is hard to predict the state as the data is coming in as it easily changes the state.

phattranky commented 1 year ago

Thanks @Vultwo

I will check again. Because I'm confusing the state of your example. I see it is

// output (array of arrays)
// [['convergence'],['divergence'],['convergence'],['divergence'],['convergence'],['exaggerated_bearish']]

So I thought those are the state values.

phattranky commented 1 year ago

Hi @Vultwo

I wrote an example code here https://github.com/phattranky/ta-rsi-divergence-example/blob/main/index.js

Could you help me to check why it doesn't have any divergence? Any wrong with my configuration? I found some divergence in that range on the Chart.

You can try to change the pair and startTime endTime on my code for back testing.

5555LINKUSDTPERP 7 401 ▲ +6 57% Vô danh 2023-03-30 04-52-34 44444index js — ta-rsi-divergence-example 2023-03-30 04-50-08

Vultwo commented 1 year ago

Hi @phattranky,

I have updated the readme to be more clear. The outputted values are just of the example values.

I have sent you a pull request on your test example with a working example. I have added a smoother function to compare for divergence further back than the previous value.

phattranky commented 1 year ago

Thanks @Vultwo .

You're so nice. Seems it's working. Let's me test more and get back to you later