[DMcL] Divergence YOLO

beta
By dmaclennan in Trading Bots Published November 2023 👁 1,244 views 💬 0 comments

Description

Basic idea: - only 2 entries - finding divergences on the RSI, combined with isAbnormal() to filter out many. - exit when the CC Stochastic Momentum Index or RSI peaks - will hold onto a trade if the market is trending in the right direction (3 x EMAs) - will ignore opposing trades in a trending market (3 x EMAs). - trend will also act as stoploss if in an opposite postition. *Update: added option to turn off the holding onto trades in a trend & the trend acting as a stoploss
HaasScript
EnableHighSpeedUpdates(true)
HideOrderSettings()
HideTradeAmountSettings()
SetFee(-0.02) -- for backtesting


-- 5 min interval


function wholeLogicInaFunction()

-- inputs
    local orderTypes = {
        limit = 'LIMIT',
        postLimit = 'LIMIT (Post-Only)',
        market = 'MARKET'
    }

    local TOTAL_margin = Input('Order Size Margin', 100, 'margin used of quote currency (e.g. USDT) per order. 2 orders are placed total, so total margin used is double this', 'TRADE SETTINGS')

    local entry_order = InputOptions('Entry Order Type', orderTypes.market, orderTypes, 'Entry order type.', 'TRADE SETTINGS')
    local tp_scaler = Input('take profit scaler', 0.2, 'the take profit threshold is calculated from the atr(percentage) multiplied by this number. Only over this threshold will the bot look for exit conditions (a peaking SMI or RSI). In more volitile markets this threshold will automatically be higher. If you want to see this value on the chart, uncomment the atr plot at the end of the script', 'TRADE SETTINGS')
    local sl = Input('stop loss', 3, 'The percentage after the second (and final) entry the stop loss is placed', 'TRADE SETTINGS')
    local slmult = Input('stop loss mult (first entry)', 3, 'This times the regular stoploss value is where a stoploss will be placed from the first entry. E.g. 3 x 3 = 9', 'TRADE SETTINGS')

    local ent_mult = Input('Entry Ab Mult', 1, 'How sensitive entries are triggered. 1 is most sensitive. 1-3.', 'TRADE SETTINGS')
    local exit_mult = Input('Exit Ab Mult', 1, 'How sensitive exits are triggered (only when the medium ema is moving in unprofitable direction). 1 is most sensitive. 1-3', 'TRADE SETTINGS')


    local right = Input("Pivot Lookback Right", 3, 'How many bars to the right needed to find a local high/low. Will also be the delay for entries', 'PIVOT POINTS')
    local left = Input("Pivot Lookback Left", 5, 'How many bars to the left needed to find a local high/low.', 'PIVOT POINTS')
    local rangeUpper = Input("Max of Lookback Range", 60, 'After this many bars, old pivot points will be cleared', 'PIVOT POINTS')

    local lengthK = Input('Length K', 10, '', 'SMI SETTINGS')
    local lengthD = Input('Length D', 3, '', 'SMI SETTINGS')
    local lengthEMA = Input('Length EMA', 4, '', 'SMI SETTINGS')
    local smi_mult = Input('SMI Abnormal Multiplier', 1.4, '1-3. This is used for take profits. Higher settings will result in letting winners ride but potentially more losing trades', 'SMI SETTINGS')

    local rsi_period = Input('RSI period', 14, '',  'RSI SETTINGS')

    local ema_int = InputInterval('EMA interval', 45, 'Keep', 'EMA SETTING')
    local ema_period = Input('EMA short Period', 20, '', 'EMA SETTING')
    local ema_med_period = Input('EMA medium Period', 50, '', 'EMA SETTING')
    local ema_long_period = Input('EMA long Period', 100, '', 'EMA SETTING')

    local order_offset = Input('entry price offset', 0.01, 'Only used for LIMIT orders')
    local order_timeout_input = Input('order timeout (min)', 60, 'Only used for LIMIT orders')
    local short = Input('allow short', true)
    local long = Input('allow long', true)
    local ma_yolo = Input('MA Yolo', true, 'Will hold onto trades when in a trending market')
    local ma_stoploss = Input('MA stoploss', false, 'A change into an opposing trend will stoploss')


-- rescale function
    local function rescale(_src, oldMin, oldMax, newMin, newMax)
        return newMin + (newMax - newMin) * (_src - oldMin) / (oldMax - oldMin)

    end

-- data
    local ob = GetOrderbook()
    local cp = CurrentPrice()
    local h = HighPrices()
    local l = LowPrices()
    local c = ClosePrices()
    local o = OpenPrices()
    local c_ema = ClosePrices(ema_int)


    local ema = EMA(c_ema, ema_period)
    local ema_med = EMA(c_ema, ema_med_period)
    local ema_long = EMA(c_ema, ema_long_period)
    local downtrend = (ema < ema_med) and (ema_med < ema_long)
    local uptrend = (ema > ema_med) and (ema_med > ema_long)
    


-- values
    local profit = GetBotProfit()
    local order_timeout = order_timeout_input * 60
    local timeout = Load('timeout', 0)


    local SMI = CC_Stochastic_Momentum_Index(lengthK, lengthD, lengthEMA)
    local rsi = RSI(c, rsi_period)
    local rsi_long = RSI(c_ema, rsi_period)

    local lookback_rsi = ArrayGet(rsi, right+1)
    local lookback_close = ArrayGet(c, right+1)
    local lookback_low = ArrayGet(l, right+1)
    local lookback_high = ArrayGet(h, right+1)





    local SMI_Ab = IsAbnormal(SMI[1], smi_mult)

    local RSI_Ab_long = (rsi < 50) and not (IsAbnormal(rsi[right+1], ent_mult))
    local RSI_Ab_low = (rsi < 50) and not (IsAbnormal(rsi, exit_mult))
    local RSI_Ab_short = (rsi > 50) and not (IsAbnormal(rsi[right+1], ent_mult))
    local RSI_Ab_high = (rsi > 50) and not (IsAbnormal(rsi, exit_mult))
    
    local rsi_lowLeft = GetLow(Grab(rsi, right + 1, left), left)
    local rsi_lowRight = GetLow(rsi, right)
    local rsi_highLeft = GetHigh(Grab(rsi, right + 1, left), left)
    local rsi_highRight = GetHigh(rsi, right)

    local cv = ContractValue()
    local total_amount = TOTAL_margin/cv * Leverage()


    local smi = SMI[1]
    local smiEma = SMI[2]

    local atr = (((ATR(h, l, c, 14))/cp)*1000)*tp_scaler
    

-- positions
    local pezId = Load('pezId', NewGuid())
    local position = PositionContainer(pezId)

-- memory
    local index = Load('index', 0)
    local leid = Load('leid', '')
    local seid = Load('seid', '')
    local xeid = Load('xeid', '')
    local long_exit = Load('long_exit', false)
    local short_exit = Load('short_exit', false)
    local curPivotLow = Load('curPivotLow', 0)
    local prePivotLow = Load('prePivotLow', 0)
    local curPriceLow = Load('curPriceLow', 0)
    local prePriceLow = Load('prePriceLow', 0)
    local curPivotHigh = Load('curPivotHigh', 0)
    local prePivotHigh = Load('prePivotHigh', 0)
    local curPriceHigh = Load('curPriceHigh', 0)
    local prePriceHigh = Load('prePriceHigh', 0)
    local lookback_window = Load('lookback_window', 0)

-- reset function
    local function reset()
        curPivotLow = 0
        prePivotLow = 0
        curPriceLow = 0
        prePriceLow = 0
        curPivotHigh = 0
        prePivotHigh = 0
        curPriceHigh = 0
        prePriceHigh = 0
    end


-- price adjustment functions
    function adjustBuyPrice(price)
        if ob.bidPrices[1] <= price then
            price = SubPerc(ob.bidPrices[1], order_offset)
        end
        return price
    end

    function adjustBuyPriceTrigger(price)
        price = ob.askPrices[1] + PriceStep()
        return price
    end
 
    function adjustSellPrice(price)
        if ob.askPrices[1] >= price then
            price = AddPerc(ob.askPrices[1], order_offset)
        end
        return price
    end

    function adjustSellPriceTrigger(price)
        price = ob.bidPrices[1] - PriceStep()
        return price
    end
    



-- entry order type
    function getOrderType(strType)
        if strType == orderTypes.limit then
            return LimitOrderType
        elseif strType == orderTypes.postLimit then
            return MakerOrCancelOrderType
        elseif strType == orderTypes.market then
            return MarketOrderType
        end

    end

-- new position function
    function newPosition()
        index = 0
        xeid = ''
        leid = ''
        seid = ''
        curPivotLow = 0
        prePivotLow = 0
        curPriceLow = 0
        prePriceLow = 0
        short_exit = false
        long_exit = false
        pezId = NewGuid()
    end
-- dump function
    function dump()
        if position.amount > 0 then
            PlaceExitPositionOrder(pezId, {type = MarketOrderType})
            newPosition()
        end
    end

-- pivot points
    -- low
    if lookback_rsi < rsi_lowLeft and lookback_rsi < rsi_lowRight and RSI_Ab_long and long then
        PlotShape(2, ShapeTriangleUp, Gold, 3, false, {offset = -right}) 
        PlotShape(0, ShapeTriangleUp, Gold, 3, false, {offset = -right}) 
        prePivotLow = curPivotLow
        curPivotLow = lookback_rsi
        prePriceLow = curPriceLow
        curPriceLow = lookback_close
        lookback_window = Time() + (CurrentInterval() * rangeUpper * 60)
    end
    
    local low_not_null = (prePivotLow > 0) and
                         (curPivotLow > 0) and
                         (prePriceLow > 0) and
                         (curPriceLow > 0)

    -- high
    if lookback_rsi > rsi_highLeft and lookback_rsi > rsi_highRight and RSI_Ab_short and short then
        PlotShape(2, ShapeTriangleDown, Fuchsia(50), 3, true, {offset = -right}) 
        PlotShape(0, ShapeTriangleDown, Fuchsia(50), 3, true, {offset = -right}) 
        prePivotHigh = curPivotHigh
        curPivotHigh = lookback_rsi
        prePriceHigh = curPriceHigh
        curPriceHigh = lookback_close
        lookback_window = Time() + (CurrentInterval() * rangeUpper * 60)
    end
    
    local high_not_null = (prePivotHigh > 0) and
                          (curPivotHigh > 0) and
                          (prePriceHigh > 0) and
                          (curPriceHigh > 0)

-- entry logic
        local long_price = adjustBuyPrice(cp.bid)
        local long_price_trigger = adjustBuyPriceTrigger(cp.bid)
        local short_price = adjustSellPrice(cp.ask)
        local short_price_trigger = adjustSellPriceTrigger(cp.ask)

        if timeout < Time() then

        -- short entry logic
        local short_ok = prePivotHigh > curPivotHigh and prePriceHigh < curPriceHigh and high_not_null

            if short and short_ok and (index < 2) and TradeOncePerBar() and not uptrend then 
                if (GetPositionDirection(pezId) == NoPosition) then 
                    if leid == '' and seid == '' then
                        seid = PlaceGoShortOrder(short_price, total_amount, {type=getOrderType(entry_order), note='Short Entry 1', timeout=order_timeout, positionId=pezId})
                        index = index + 1
                        reset()
                    end
                elseif position.isShort and (index == 1) and StopLoss(1.3*atr) then
                    seid = PlaceGoShortOrder(short_price, total_amount, {type=getOrderType(entry_order), note='Short Entry 2', timeout=order_timeout, positionId=pezId})
                    index = index + 1
                end
            end

        -- long entry logic
        local long_ok = prePivotLow < curPivotLow and prePriceLow > curPriceLow and low_not_null

            if long and long_ok and (index < 2) and TradeOncePerBar() and not downtrend then
                if (GetPositionDirection(pezId) == NoPosition) then 
                    if leid == '' and seid == '' then
                        leid = PlaceGoLongOrder(long_price, total_amount, {type=getOrderType(entry_order), note='Long Entry 1', timeout=order_timeout, positionId=pezId})
                        index = index + 1
                        reset()
                    end
                elseif position.isLong and (index == 1) and StopLoss(1.3*atr) then
                    leid = PlaceGoLongOrder(long_price, total_amount, {type=getOrderType(entry_order), note='Long Entry 2', timeout=order_timeout, positionId=pezId})
                    index = index + 1

                end
            end
        end




-- exit logic (tp)

    local posRoi = GetPositionROI(pezId)


    if position.isLong and TradeOncePerBar() then 
        if downtrend then 
            if TakeProfit(0.1) then 
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Long Take Profit (down)")
                newPosition()
            end
        elseif ma_yolo then
            if RSI_Ab_high and TakeProfit(atr) and not IsRising(ema_med, 1) then 
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Long Take Profit rsi")
                newPosition()
            elseif TakeProfit(atr) and ((smi > 0) and not SMI_Ab) and not IsRising(ema_med, 1) then
                long_exit = true
            end
            if long_exit and IsFalling(c, 1) then
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Long Take Profit")
                newPosition()
            end
        else
            if RSI_Ab_high and TakeProfit(atr) then 
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Long Take Profit rsi")
                newPosition()
            elseif TakeProfit(atr) and ((smi > 0) and not SMI_Ab) then
                long_exit = true
            end
            if long_exit and IsFalling(c, 1) then
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Long Take Profit")
                newPosition()
            end
        end
    end



    if position.isShort and TradeOncePerBar() then
        if uptrend then
            if TakeProfit(0.1) then 
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Short Take Profit (up)")
                newPosition()
            end
        elseif ma_yolo then
            if RSI_Ab_low and TakeProfit(atr) and not IsFalling(ema_med, 1) then
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Short Take Profit rsi")
                newPosition()
            elseif TakeProfit(atr) and ((smi < 0) and not SMI_Ab) and not IsFalling(ema_med, 1) then
                short_exit = true
            end
            if short_exit and IsRising(c, 1) then
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Short Take Profit")
                newPosition()
            end
        else
            if RSI_Ab_low and TakeProfit(atr) then
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Short Take Profit rsi")
                newPosition()
            elseif TakeProfit(atr) and ((smi < 0) and not SMI_Ab) then
                short_exit = true
            end
            if short_exit and IsRising(c, 1) then
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Short Take Profit")
                newPosition()
            end
        end
    end

-- exit logic (sl)

    if ma_stoploss then
        if uptrend then 
            if position.isShort then
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Short StopLoss (crossover)")
                newPosition()
                timeout = Time() + order_timeout
            end
        end
        if downtrend then 
            if position.isLong then
                PlaceExitPositionOrder(pezId, cp, MarketOrderType, "Long StopLoss (crossover)")
                newPosition()
                timeout = Time() + order_timeout
            end
        end
    end
    -- sl

    if (GetPositionDirection(pezId) != NoPosition) and (index == 1) and not IsAnyOrderOpen(pezId) then
        if StopLoss(sl*slmult) then
            PlaceExitPositionOrder(pezId, cp, MarketOrderType, 'STOPLOSS')
            newPosition()
            timeout = Time() + order_timeout
        end
    end

    if (GetPositionDirection(pezId) != NoPosition) and (index == 2) and not IsAnyOrderOpen(pezId) then
        if StopLoss(sl) then
            PlaceExitPositionOrder(pezId, cp, MarketOrderType, 'STOPLOSS')
            newPosition()
            timeout = Time() + order_timeout
        end
    end


-- clear timed-out orders
    if (GetPositionDirection(pezId) == NoPosition) then
        if seid != '' and not IsOrderOpen(seid) then
            newPosition()
        end
        if leid != '' and not IsOrderOpen(leid) then
            newPosition()
        end
    end


-- plot
    if not SMI_Ab then
        PlotShape(1, ShapeCross, Yellow(90), 2)
    end
    
    if position.isShort and RSI_Ab_low and TakeProfit(atr) and not IsFalling(ema_med, 1) then
        PlotShape(2, ShapeCross, Green, 3)
    end

    if position.isLong and RSI_Ab_high and TakeProfit(atr) and not IsRising(ema_med, 1) then
        PlotShape(2, ShapeCross, Red, 3)
    end

-- dump
    function stopExit()
        if IsAnyOrderOpen() then
            CancelAllOrders()
        end
        dump()
        Log('Stop & Dump!', Purple)
    end
    InputButton('Stop And Exit', stopExit, 'Optimize for Interval must be unchecked', 'EXIT SETTINGS')


-- lookback window
    if lookback_window < Time() then
        reset()
    end


-- plot technical indicators    
    ChartSetOptions(0, '', 0.75)
    ChartSetOptions(1, 'smi', 0.25)
    ChartSetOptions(2, 'rsi', 0.25)
    -- ChartSetOptions(3, 'atr', 0.25)
    ChartSetOptions(8, 'PnL', 0.25)

    local prof_plot = Plot(8, "PnL", profit, Green(40))
    Plot(0, 'ema', ema)
    Plot(0, 'ema_med', ema_med, Yellow)
    Plot(0, 'ema_long', ema_long, Orange)
    Plot(2, "rsi", rsi)
    -- Plot(3, "atr", atr)
    PlotDoubleColor(prof_plot, 0, Red)




    Save('timeout', timeout)
    Save('index', index)
    Save('pezId', pezId)
    Save('leid', leid)
    Save('seid', seid)
    Save('xeid', xeid)
    Save('long_exit', long_exit)
    Save('short_exit', short_exit)
    Save('curPivotLow', curPivotLow)
    Save('prePivotLow', prePivotLow)
    Save('curPriceLow', curPriceLow)
    Save('prePriceLow', prePriceLow)
    Save('curPivotHigh', curPivotHigh)
    Save('prePivotHigh', prePivotHigh)
    Save('curPriceHigh', curPriceHigh)
    Save('prePriceHigh', prePriceHigh)
    Save('lookback_window', lookback_window)


end

local optimize = Input('Backtest optimization', true, '', 'OPTIMIZE')
local opt_interval = Input('Backtest optimization interval', 0, '', 'OPTIMIZE')

if optimize then
    OptimizedForInterval(opt_interval, wholeLogicInaFunction)
else
    wholeLogicInaFunction()
end

    local report = GetTradingReport()
    local los_pos = (report.losingPositions)
    local posPnL = GetPositionProfit(pezId)

    local maxPnLDD = Load('maxPnLDD', 0)
    if posPnL < maxPnLDD then
        maxPnLDD = posPnL
    end
    Save('maxPnLDD', maxPnLDD)


    Finalize(function()
        CustomReport('Max Drawdown', Round(maxPnLDD, 2))
        CustomReport('Losing Positions', los_pos)
    end)

0 Comments

Sign in to leave a comment.

No comments yet. Be the first!