Chiến lược động lực RSI dựa trên can thiệp đa thức

Tác giả:ChaoZhang, Ngày: 2024-01-12 13:46:53
Tags:

img

Tổng quan

Chiến lược này tạo ra tín hiệu giao dịch bằng cách sử dụng chỉ số động lực RSI Delta-RSI dựa trên can thiệp đa thức. Delta-RSI làm mịn RSI thông qua hồi quy đa thức địa phương để có được phái sinh thời gian thứ nhất của nó như một chỉ số động lực. Chiến lược này kết hợp các bộ lọc bổ sung dựa trên ATR, khối lượng và RSI để lọc ra một số tín hiệu false.

Nguyên tắc

Chỉ số cốt lõi của chiến lược này là Delta-RSI.

  1. Lấy chuỗi thời gian RSI với chiều dài rsi_l như đầu vào
  2. Phù hợp RSI bằng cách can thiệp đa thức trong cửa sổ trượt của cửa sổ chiều dài
  3. Tính toán phái sinh thứ nhất của đường cong phù hợp tại điểm hiện tại như Delta-RSI
  4. Đi qua Delta-RSI trên 0 tạo ra tín hiệu mua, đi dưới 0 tạo ra tín hiệu bán
  5. Các tín hiệu giao dịch cũng có thể được tạo bằng cách sử dụng đường tín hiệu Delta-RSI

Chiến lược lọc tín hiệu bằng cách sử dụng bộ lọc ATR, âm lượng và RSI:

  1. Bộ lọc ATR: ATR N-period hiện tại cao hơn ATR M-period cho thấy sự biến động tăng lên
  2. Bộ lọc khối lượng: khối lượng hiện tại lớn hơn N lần khối lượng trung bình trong M thời gian
  3. Bộ lọc RSI: RSI giữa ngưỡng 1 và ngưỡng 2 bộ lọc khu vực mua/bán quá mức

Ưu điểm

Những lợi thế của chiến lược này bao gồm:

  1. Delta-RSI nhạy cảm hơn để phát hiện sự đảo ngược xu hướng sớm
  2. Các bộ lọc có thể loại bỏ hầu hết các tín hiệu sai và cải thiện chất lượng tín hiệu
  3. Các thông số đa thức và bộ lọc có thể tùy chỉnh phù hợp với các thị trường khác nhau
  4. Loại dài / ngắn riêng biệt cho phép phù hợp với các khuynh hướng khác nhau
  5. Kiểm soát dừng lỗ/lấy lợi nhuận cho mỗi lỗ/lợi nhuận giao dịch

Rủi ro

Những rủi ro của chiến lược này bao gồm:

  1. Chế độ điều chỉnh tham số kém có thể gây ra quá mịn hoặc quá lọc
  2. Mất lỗ vị trí dài/giá ngắn
  3. Các điểm dừng quá rộng có thể tăng theo lỗ giao dịch

Chúng có thể được kiểm soát thông qua tối ưu hóa tham số, điều chỉnh bộ lọc và dừng chặt hơn.

Cơ hội gia tăng

Chiến lược này có thể được cải thiện hơn nữa bằng cách:

  1. Tối ưu hóa các thông số mô hình Delta-RSI
  2. Tích hợp bộ lọc thích nghi dựa trên học máy
  3. Các thông số điều chỉnh cho các sản phẩm khác nhau
  4. Thêm các phương pháp kết hợp mô hình v.v. để tăng độ bền

Kết luận

Bằng cách khai thác độ nhạy cao của Delta-RSI và các cơ chế lọc nghiêm ngặt, chiến lược này có thể cải thiện chất lượng trong khi kiểm soát rủi ro.


/*backtest
start: 2024-01-04 00:00:00
end: 2024-01-11 00:00:00
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © tbiktag
//
// Delta-RSI Oscillator Strategy With Filters
//
// This is a version of the Delta-RSI Oscillator Strategy compatible with 
// the Strategy Tester.
//
// This version also allows filtering the trade signals generated by Delts-RSI
// by means of volatility (defined by ATR), relative volume and RSI(14).
//
// Delta-RSI (© tbiktag) is a smoothed time derivative of the RSI designed
// as a momentum indicator. For the original publication, see link below:
// https://www.tradingview.com/script/OXQVFTQD-Delta-RSI-Oscillator/
// 
// D-RSI model parameters:
// RSI Length: The timeframe of the RSI that serves as an input to D-RSI.
// Frame Length: The length of the lookback frame used for local regression.
// Polynomial Order: The order of the local polynomial function used to interpolate 
// the RSI.
// Trade signals are generated based on three optional conditions:
// - Zero-crossing: bullish when D-RSI crosses zero from negative to positive 
// values (bearish otherwise)
// - Signal Line Crossing: bullish when D-RSI crosses from below to above the signal 
// line (bearish otherwise)
// - Direction Change: bullish when D-RSI was negative and starts ascending 
// (bearish otherwise)
// 
//@version=4
strategy(title="Delta-RSI Strategy with Filters", shorttitle = "D-RSI with filters", overlay = true)

// ---Subroutines---
matrix_get(_A,_i,_j,_nrows) =>
    // Get the value of the element of an implied 2d matrix
    //input: 
    // _A :: array: pseudo 2d matrix _A = [[column_0],[column_1],...,[column_(n-1)]]
    // _i :: integer: row number
    // _j :: integer: column number
    // _nrows :: integer: number of rows in the implied 2d matrix
    array.get(_A,_i+_nrows*_j)

matrix_set(_A,_value,_i,_j,_nrows) =>
    // Set a value to the element of an implied 2d matrix
    //input: 
    // _A :: array, changed on output: pseudo 2d matrix _A = [[column_0],[column_1],...,[column_(n-1)]]
    // _value :: float: the new value to be set
    // _i :: integer: row number
    // _j :: integer: column number
    // _nrows :: integer: number of rows in the implied 2d matrix
    array.set(_A,_i+_nrows*_j,_value)

transpose(_A,_nrows,_ncolumns) =>
    // Transpose an implied 2d matrix
    // input:
    // _A :: array: pseudo 2d matrix _A = [[column_0],[column_1],...,[column_(n-1)]]
    // _nrows :: integer: number of rows in _A
    // _ncolumns :: integer: number of columns in _A
    // output:
    // _AT :: array: pseudo 2d matrix with implied dimensions: _ncolums x _nrows
    var _AT = array.new_float(_nrows*_ncolumns,0)
    for i = 0 to _nrows-1
        for j = 0 to _ncolumns-1
            matrix_set(_AT, matrix_get(_A,i,j,_nrows),j,i,_ncolumns)
    _AT

multiply(_A,_B,_nrowsA,_ncolumnsA,_ncolumnsB) => 
    // Calculate scalar product of two matrices
    // input: 
    // _A :: array: pseudo 2d matrix
    // _B :: array: pseudo 2d matrix
    // _nrowsA :: integer: number of rows in _A
    // _ncolumnsA :: integer: number of columns in _A
    // _ncolumnsB :: integer: number of columns in _B
    // output:
    // _C:: array: pseudo 2d matrix with implied dimensions _nrowsA x _ncolumnsB
    var _C = array.new_float(_nrowsA*_ncolumnsB,0)
    int _nrowsB = _ncolumnsA
    float elementC= 0.0
    for i = 0 to _nrowsA-1
        for j = 0 to _ncolumnsB-1
            elementC := 0
            for k = 0 to _ncolumnsA-1
                elementC := elementC + matrix_get(_A,i,k,_nrowsA)*matrix_get(_B,k,j,_nrowsB)
            matrix_set(_C,elementC,i,j,_nrowsA)
    _C

vnorm(_X,_n) =>
    //Square norm of vector _X with size _n
    float _norm = 0.0
    for i = 0 to _n-1
        _norm := _norm + pow(array.get(_X,i),2)
    sqrt(_norm)

qr_diag(_A,_nrows,_ncolumns) => 
    //QR Decomposition with Modified Gram-Schmidt Algorithm (Column-Oriented)
    // input:
    // _A :: array: pseudo 2d matrix _A = [[column_0],[column_1],...,[column_(n-1)]]
    // _nrows :: integer: number of rows in _A
    // _ncolumns :: integer: number of columns in _A
    // output:
    // _Q: unitary matrix, implied dimenstions _nrows x _ncolumns
    // _R: upper triangular matrix, implied dimansions _ncolumns x _ncolumns
    var _Q = array.new_float(_nrows*_ncolumns,0)
    var _R = array.new_float(_ncolumns*_ncolumns,0)
    var _a = array.new_float(_nrows,0)
    var _q = array.new_float(_nrows,0)
    float _r = 0.0
    float _aux = 0.0
    //get first column of _A and its norm:
    for i = 0 to _nrows-1
        array.set(_a,i,matrix_get(_A,i,0,_nrows))
    _r := vnorm(_a,_nrows)
    //assign first diagonal element of R and first column of Q
    matrix_set(_R,_r,0,0,_ncolumns)
    for i = 0 to _nrows-1
        matrix_set(_Q,array.get(_a,i)/_r,i,0,_nrows)
    if _ncolumns != 1
        //repeat for the rest of the columns
        for k = 1 to _ncolumns-1
            for i = 0 to _nrows-1
                array.set(_a,i,matrix_get(_A,i,k,_nrows))
            for j = 0 to k-1
                //get R_jk as scalar product of Q_j column and A_k column:
                _r := 0
                for i = 0 to _nrows-1
                    _r := _r + matrix_get(_Q,i,j,_nrows)*array.get(_a,i)
                matrix_set(_R,_r,j,k,_ncolumns)
                //update vector _a
                for i = 0 to _nrows-1
                    _aux := array.get(_a,i) - _r*matrix_get(_Q,i,j,_nrows)
                    array.set(_a,i,_aux)
            //get diagonal R_kk and Q_k column
            _r := vnorm(_a,_nrows)
            matrix_set(_R,_r,k,k,_ncolumns)
            for i = 0 to _nrows-1
                matrix_set(_Q,array.get(_a,i)/_r,i,k,_nrows)
    [_Q,_R]
    
pinv(_A,_nrows,_ncolumns) =>
    //Pseudoinverse of matrix _A calculated using QR decomposition
    // Input: 
    // _A:: array: implied as a (_nrows x _ncolumns) matrix 
    //.             _A = [[column_0],[column_1],...,[column_(_ncolumns-1)]]
    // Output: 
    // _Ainv:: array implied as a (_ncolumns x _nrows) matrix 
    //              _A = [[row_0],[row_1],...,[row_(_nrows-1)]]
    // ----
    // First find the QR factorization of A: A = QR,
    // where R is upper triangular matrix.
    // Then _Ainv = R^-1*Q^T.
    // ----
    [_Q,_R] = qr_diag(_A,_nrows,_ncolumns)
    _QT = transpose(_Q,_nrows,_ncolumns)
    // Calculate Rinv:
    var _Rinv = array.new_float(_ncolumns*_ncolumns,0)
    float _r = 0.0
    matrix_set(_Rinv,1/matrix_get(_R,0,0,_ncolumns),0,0,_ncolumns)
    if _ncolumns != 1
        for j = 1 to _ncolumns-1
            for i = 0 to j-1
                _r := 0.0
                for k = i to j-1
                    _r := _r + matrix_get(_Rinv,i,k,_ncolumns)*matrix_get(_R,k,j,_ncolumns)
                matrix_set(_Rinv,_r,i,j,_ncolumns)
            for k = 0 to j-1
                matrix_set(_Rinv,-matrix_get(_Rinv,k,j,_ncolumns)/matrix_get(_R,j,j,_ncolumns),k,j,_ncolumns)
            matrix_set(_Rinv,1/matrix_get(_R,j,j,_ncolumns),j,j,_ncolumns)
    //
    _Ainv = multiply(_Rinv,_QT,_ncolumns,_ncolumns,_nrows)
    _Ainv

norm_rmse(_x, _xhat) =>
    // Root Mean Square Error normalized to the sample mean
    // _x.   :: array float, original data
    // _xhat :: array float, model estimate
    // output
    // _nrmse:: float
    float _nrmse = 0.0
    if array.size(_x) != array.size(_xhat)
        _nrmse := na
    else
        int _N = array.size(_x)
        float _mse = 0.0
        for i = 0 to _N-1
            _mse := _mse + pow(array.get(_x,i) - array.get(_xhat,i),2)/_N
        _xmean = array.sum(_x)/_N
        _nrmse := sqrt(_mse) /_xmean
    _nrmse
    

diff(_src,_window,_degree) =>
    // Polynomial differentiator
    // input:
    // _src:: input series
    // _window:: integer: wigth of the moving lookback window
    // _degree:: integer: degree of fitting polynomial
    // output:
    // _diff :: series: time derivative
    // _nrmse:: float: normalized root mean square error
    //
    // Vandermonde matrix with implied dimensions (window x degree+1)
    // Linear form: J = [ [z]^0, [z]^1, ... [z]^degree], 
    //              with z = [ (1-window)/2 to (window-1)/2 ] 
    var _J = array.new_float(_window*(_degree+1),0)
    for i = 0 to _window-1 
        for j = 0 to _degree
            matrix_set(_J,pow(i,j),i,j,_window)
    // Vector of raw datapoints:
    var _Y_raw = array.new_float(_window,na)
    for j = 0 to _window-1
        array.set(_Y_raw,j,_src[_window-1-j]) 
    // Calculate polynomial coefficients which minimize the loss function
    _C = pinv(_J,_window,_degree+1)
    _a_coef = multiply(_C,_Y_raw,_degree+1,_window,1)
    // For first derivative, approximate the last point (i.e. z=window-1) by 
    float _diff = 0.0
    for i = 1 to _degree
        _diff := _diff + i*array.get(_a_coef,i)*pow(_window-1,i-1)
    // Calculates data estimate (needed for rmse)
    _Y_hat = multiply(_J,_a_coef,_window,_degree+1,1)
    float _nrmse = norm_rmse(_Y_raw,_Y_hat)
    [_diff,_nrmse]

/// --- main ---
degree = input(title="Polynomial Order", group = "Model Parameters:",
              inline = "linepar1", type = input.integer, defval=3, minval = 1)
rsi_l = input(title = "RSI Length", group = "Model Parameters:", 
              inline = "linepar1", type = input.integer, defval = 21, minval = 1,
              tooltip="The period length of RSI that is used as input.")
window = input(title="Length ( > Order)", group = "Model Parameters:",
              inline = "linepar2", type = input.integer, defval=50, minval = 2)
signalLength = input(title="Signal Length", group = "Model Parameters:",
              inline = "linepar2", type=input.integer, defval=9,
              tooltip="The signal line is a EMA of the D-RSI time series.")
islong = input(title = "Long", group = "Allowed Entries:",
              inline = "lineent",type = input.bool, defval = true)
isshort = input(title = "Short", group = "Allowed Entries:",
              inline = "lineent", type = input.bool, defval= true)
buycond = input(title="Buy", group = "Entry and Exit Conditions:", 
              inline = "linecond",type = input.string, defval="Signal Line Crossing", 
              options=["Zero-Crossing", "Signal Line Crossing","Direction Change"])
sellcond = input(title="Sell", group = "Entry and Exit Conditions:", 
              inline = "linecond",type = input.string, defval="Signal Line Crossing", 
              options=["Zero-Crossing", "Signal Line Crossing","Direction Change"])
endcond = input(title="Exit", group = "Entry and Exit Conditions:", 
              inline = "linecond",type = input.string, defval="Signal Line Crossing", 
              options=["Zero-Crossing", "Signal Line Crossing","Direction Change"])
filterlong =input(title = "Long Entries", inline = 'linefilt', group = 'Apply Filters to', 
               type = input.bool, defval = true)
filtershort =input(title = "Short Enties", inline = 'linefilt', group = 'Apply Filters to', 
               type = input.bool, defval = true)
filterend =input(title = "Exits", inline = 'linefilt', group = 'Apply Filters to', 
               type = input.bool, defval = true)
usevol =input(title = "", inline = 'linefiltvol', group = 'Relative Volume Filter:', 
               type = input.bool, defval = false)
rvol = input(title = "Volume >", inline = 'linefiltvol', group = 'Relative Volume Filter:', 
               type = input.integer, defval = 1)
len_vol = input(title = "Avg. Volume Over Period", inline = 'linefiltvol', group = 'Relative Volume Filter:', 
               type = input.integer, defval = 30, minval = 1,
               tooltip="The current volume must be greater than N times the M-period average volume.")
useatr =input(title = "", inline = 'linefiltatr', group = 'Volatility Filter:', 
               type = input.bool, defval = false)
len_atr1 = input(title = "ATR", inline = 'linefiltatr', group = 'Volatility Filter:', 
               type = input.integer, defval = 5, minval = 1)
len_atr2 = input(title = "> ATR", inline = 'linefiltatr', group = 'Volatility Filter:', 
               type = input.integer, defval = 30, minval = 1,
               tooltip="The N-period ATR must be greater than the M-period ATR.")
usersi =input(title = "", inline = 'linersi', group = 'Overbought/Oversold Filter:', 
               type = input.bool, defval = false)
rsitrhs1 = input(title = "", inline = 'linersi', group = 'Overbought/Oversold Filter:', 
               type = input.integer, defval = 0, minval=0, maxval=100)
rsitrhs2 = input(title = "< RSI (14) >", inline = 'linersi', group = 'Overbought/Oversold Filter:', 
               type = input.integer, defval = 100, minval=0, maxval=100,
               tooltip="RSI(14) must be in the range between N and M.")
issl =  input(title = "SL", inline = 'linesl1', group = 'Stop Loss / Take Profit:', 
               type = input.bool, defval = false)
slpercent =  input(title = ", %", inline = 'linesl1', group = 'Stop Loss / Take Profit:', 
               type = input.float, defval = 10, minval=0.0)
istrailing =  input(title = "Trailing", inline = 'linesl1', group = 'Stop Loss / Take Profit:', 
               type = input.bool, defval = false)
istp =  input(title = "TP", inline = 'linetp1', group = 'Stop Loss / Take Profit:', 
               type = input.bool, defval = false)
tppercent =  input(title = ", %", inline = 'linetp1', group = 'Stop Loss / Take Profit:', 
               type = input.float, defval = 20)
fixedstart =input(title="", group = "Fixed Backtest Period Start/End Dates:",
              inline = "linebac1", type = input.bool, defval = true)
backtest_start=input(title = "", type = input.time, inline = "linebac1", 
              group = "Fixed Backtest Period Start/End Dates:",
              defval = timestamp("01 Jan 2017 13:30 +0000"),
              tooltip="If deactivated, backtest staring from the first available price bar.")
fixedend =  input(title="", group = "Fixed Backtest Period Start/End Dates:",
              inline = "linebac2", type = input.bool, defval = false)
backtest_end =input(title = "", type = input.time, inline = "linebac2", 
              group = "Fixed Backtest Period Start/End Dates:",
              defval = timestamp("30 Dec 2080 23:30 +0000"),
              tooltip="If deactivated, backtesting ends at the last available price bar.")

if window < degree
    window := degree+1

src = rsi(close,rsi_l)
[drsi,nrmse] = diff(src,window,degree)
signalline = ema(drsi, signalLength)

// Conditions for D-RSI
dirchangeup = (drsi>drsi[1]) and (drsi[1]<drsi[2]) and drsi[1]<0.0
dirchangedw = (drsi<drsi[1]) and (drsi[1]>drsi[2]) and drsi[1]>0.0
crossup = crossover(drsi,0.0)
crossdw = crossunder(drsi,0.0)
crosssignalup = crossover(drsi,signalline)
crosssignaldw = crossunder(drsi,signalline)

// D-RSI signals
drsilong = (buycond=="Direction Change"?dirchangeup:(buycond=="Zero-Crossing"?crossup:crosssignalup)) 
drsishort= (sellcond=="Direction Change"?dirchangedw:(sellcond=="Zero-Crossing"?crossdw:crosssignaldw)) 
drisendlong = (endcond=="Direction Change"?dirchangedw:(endcond=="Zero-Crossing"?crossdw:crosssignaldw)) 
drisendshort= (endcond=="Direction Change"?dirchangeup:(endcond=="Zero-Crossing"?crossup:crosssignalup)) 

// Filters
rsifilter = usersi?(rsi(close,14) > rsitrhs1 and rsi(close,14) < rsitrhs2):true
volatilityfilter = useatr?(atr(len_atr1) > atr(len_atr2)):true
volumefilter = usevol?(volume > rvol*sma(volume,len_vol)):true
totalfilter = volatilityfilter and volumefilter and rsifilter

//Filtered signals
golong  = drsilong  and islong  and (filterlong?totalfilter:true) 
goshort = drsishort and isshort and (filtershort?totalfilter:true)
endlong  = drisendlong and (filterend?totalfilter:true)
endshort = drisendlong and (filterend?totalfilter:true)

// Backtest period
//backtest_start = timestamp(syminfo.timezone, startYear, startMonth, startDate, 0, 0)
//backtest_end = timestamp(syminfo.timezone, endYear, endMonth, endDate, 0, 0)
isinrange = true

// Entry price / Take profit / Stop Loss
startprice = valuewhen(condition=golong or goshort, source=close, occurrence=0)
pm = golong?1:goshort?-1:1/sign(strategy.position_size)
takeprofit = startprice*(1+pm*tppercent*0.01)
// fixed stop loss
stoploss = startprice * (1-pm*slpercent*0.01)
// trailing stop loss
if istrailing and strategy.position_size>0
    stoploss := max(close*(1 - slpercent*0.01),stoploss[1])
else if istrailing and strategy.position_size<0
    stoploss := min(close*(1 + slpercent*0.01),stoploss[1])

tpline = plot(takeprofit,color=color.blue,transp=100, title="TP")
slline = plot(stoploss,  color=color.red, transp=100, title="SL")
p1 = plot(close,transp=100,color=color.white, title="Dummy Close")
fill(p1, tpline, color=color.green, transp=istp?70:100, title="TP")
fill(p1, slline, color=color.red,   transp=issl?70:100, title="SL")

// Backtest: Basic Entry and Exit Conditions
if golong and isinrange and islong
    strategy.entry("long",   true )
    alert("D-RSI Long " + syminfo.tickerid, alert.freq_once_per_bar_close) 
if goshort and isinrange and isshort
    strategy.entry("short",  false)
    alert("D-RSI Short " + syminfo.tickerid, alert.freq_once_per_bar_close) 
if endlong
    strategy.close("long",  alert_message="Close Long")
    alert("D-RSI Exit Long " + syminfo.tickerid, alert.freq_once_per_bar_close) 
if endshort
    strategy.close("short", alert_message="Close Short")
    alert("D-RSI Exit Short " + syminfo.tickerid, alert.freq_once_per_bar_close) 

// Exit via SL or TP
strategy.exit(id="sl/tp long", from_entry="long", stop=issl?stoploss:na, 
              limit=istp?takeprofit:na, alert_message="Close Long")
strategy.exit(id="sl/tp short",from_entry="short",stop=issl?stoploss:na, 
              limit=istp?takeprofit:na, alert_message="Stop Loss Short")

// Close if outside the range
if (not isinrange)
    strategy.close_all()


Thêm nữa