EnableHighSpeedUpdates(true) -- KGB: -- use 1 active slots per side. once slot order is filled, activate next and take the placement from -- the band value in the moment of fill. place take-profit taking the average entry price into account. -- use either hard SL -- or re-activate slots -- or decrease take-profit distance as time goes on (should have setting to allow it to move to the losing side or to limit it to break-even) InputGroupHeader('Trade Settings') local allow_longs = Input('Allow Longs', true) local allow_shorts = Input('Allow Shorts', true) local deact_nopos = Input('Deactivate on NoPosition', false) local force_close = Input('Force Position Close', false) InputGroupHeader('General Settings') local slot_count = Input('1. Max Slots', 10, 'Maximum count of possible price levels/slots. This does not limit how many fills you can get, but limits the range where orders can fill.') local max_fills = Input('2. Max Fills', 100, 'Maximum count of fills allowed per position. If your trade amount is 2 and max fills set to 100, your total position size is limited to 200 (or less, if some orders dont fill 100%).') local size_mult = Input('3. Slot Size Multiplier', 1.25, 'Exponential multiplier for slot sizes. The multiplication is applied based on the previous slot size, so if size is 1 and multiplier is 2, we got 1, 2, 4, 8, 16 and so on. Be careful with this!!') local max_take_profit_prc = Input('4.1. Max. Take-Profit %', 1, 'Maximum TP % value that is price change based, not ROI.') local min_take_profit_prc = Input('4.2. Min. Take-Profit %', 0, 'Minimum TP % value that is price change based, not ROI. When bot has made MAX FILLS, this TP will be used. If bot has half filled (of MAX FILLS), the take-profit will be in the middle of min. and max. TP %. In other words, the TP decreases as position size increases! Setting this to zero means BREAKEVEN at max position size.') InputGroupHeader('Indicator Settings') local base_ma_len = Input('1. Base MA Length', 10, 'Length of the MA that is used as an anchor-point for slot calculations.') local base_ma_type = InputMaTypes('2. Base MA Type', EmaType, 'Type of the base MA.') local atr_len = Input('3. ATR Length', 50, 'Length of the ATR. ATR value is used to calculate slot levels, away from the base MA.') local price_int = InputInterval('4. Price Data Timeframe', 1, 'Timeframe/Interval for the prices used to calculate base MA and ATR values.') local h = HighPrices(price_int) local l = LowPrices(price_int) local c = ClosePrices(price_int) local cp = CurrentPrice() local pos_id_long = Load('poidl', NewGuid()) local pos_id_short = Load('poids', NewGuid()) local pos_long = PositionContainer(pos_id_long) local pos_short = PositionContainer(pos_id_short) if deact_nopos then if pos_long.amount > 0 and pos_short.amount <= 0 then if IsAnyOrderOpen(pos_id_short) then CancelAllOrders(pos_id_short) end allow_shorts = false elseif pos_long.amount <= 0 and pos_short.amount > 0 then if IsAnyOrderOpen(pos_id_long) then CancelAllOrders(pos_id_long) end allow_longs = false else DeactivateBot('Full NoPosition detected, deactivated bot.', true) end end local long_index = Load('lix', 1) local short_index = Load('six', 1) local base_ma = MA(c, base_ma_len, base_ma_type) local atr = ATR(h, l, c, atr_len) function getSlotAmount(index) return TradeAmount() * Pow(size_mult, index-1) end function updateSlot(index, is_long) if (is_long and long_index > max_fills) or (not is_long and short_index > max_fills) then -- print a warning ONCE. reset when TP if Load('fills_warning', true) then LogWarning('------ Maximum fills reached. No more trades allowed. ------') Save('fills_warning', false) end return end local prefix = is_long and 'L' or 'S' local mem_id = prefix..index local atr2 = is_long and atr * -index or atr * index local trigger_price = base_ma + atr2 local oid = Load(mem_id..'oid', '') local is_filled = Load(mem_id..'if', false) local amt = getSlotAmount(index) if is_long and cp.close < trigger_price then Plot(0, mem_id, trigger_price, {c = Green, id = pos_id_long..index}) elseif not is_long and cp.close > trigger_price then Plot(0, mem_id, trigger_price, {c = Red, id = pos_id_short..index}) end if is_long then if not is_filled then if oid == '' and cp.close < trigger_price then oid = PlaceGoLongOrder(SubPerc(cp.bid, 0.01), amt, {type=MakerOrCancelOrderType, note=mem_id, positionId=pos_id_long, timeout=604800}) elseif oid != '' and IsOrderOpen(oid) then CancelOrder(oid) end end if is_filled and cp.close > trigger_price then is_filled = false end else if not is_filled then if oid == '' and cp.close > trigger_price then oid = PlaceGoShortOrder(AddPerc(cp.ask, 0.01), amt, {type=MakerOrCancelOrderType, note=mem_id, positionId=pos_id_short, timeout=604800}) elseif oid != '' and IsOrderOpen(oid) then CancelOrder(oid) end end if is_filled and cp.close < trigger_price then is_filled = false end end if oid != '' and IsOrderOpen(oid) == false then if IsOrderFilled(oid) then is_filled = true if is_long then long_index = long_index + 1 else short_index = short_index + 1 end end oid = '' end Save(mem_id..'oid', oid) Save(mem_id..'if', is_filled) end function updatePosition(pos_id) local position = PositionContainer(pos_id) local take_profit_prc = position.isLong and max_take_profit_prc - (long_index -2) / max_fills * (max_take_profit_prc - min_take_profit_prc) or max_take_profit_prc - (short_index -2) / max_fills * (max_take_profit_prc - min_take_profit_prc) local price = position.isLong and AddPerc(position.enterPrice, take_profit_prc) or SubPerc(position.enterPrice, take_profit_prc) local prefix = position.isLong and 'LX' or 'SX' local oid = Load(prefix..'oid', '') local last_amt = Load(prefix..'la', position.amount) if not (position.isLong or position.isShort) then return pos_id else if position.isLong then Plot(0, 'long_pos', position.enterPrice, {c = Cyan, id = position.positionId, w = 2}) else Plot(0, 'short_pos', position.enterPrice, {c = Purple, id = position.positionId, w = 2}) end end if position.amount != last_amt and oid != '' and IsOrderOpen(oid) then CancelOrder(oid) oid = '' elseif oid == '' then Log('Current TP is ' .. take_profit_prc .. ' %') oid = PlaceExitPositionOrder({price=price, type=MakerOrCancelOrderType, note=prefix, positionId=pos_id, timeout=604800}) else if IsOrderOpen(oid) == false then if IsOrderFilled(oid) then pos_id = NewGuid() Save('fills_warning', true) -- reset warning switch if position.isLong then long_index = 1 elseif position.isShort then short_index = 1 end end oid = '' end end if oid != '' and IsOrderOpen(oid) then local order = OrderContainer(oid) Plot(0, prefix, order.price, {c=Gold(50), id=pos_id}) end Save(prefix..'oid', oid) Save(prefix..'la', position.amount) return pos_id end for i = 1, slot_count do if not force_close and allow_longs then updateSlot(i, true) end if not force_close and allow_shorts then updateSlot(i, false) end end if not force_close then pos_id_long = updatePosition(pos_id_long) pos_id_short = updatePosition(pos_id_short) else local long_pos = PositionContainer(pos_id_long) local short_pos = PositionContainer(pos_id_short) if IsAnyOrderOpen(pos_id_long) then CancelAllOrders(pos_id_long) end if IsAnyOrderOpen(pos_id_short) then CancelAllOrders(pos_id_short) end if long_pos.amount > 0 then PlaceExitPositionOrder(pos_id_long, {type = MarketOrderType, note = 'Forced Close'}) pos_id_long = NewGuid() end if short_pos.amount > 0 then PlaceExitPositionOrder(pos_id_short, {type = MarketOrderType, note = 'Forced Close'}) pos_id_short = NewGuid() end end Save('poidl', pos_id_long) Save('poids', pos_id_short) Save('lix', long_index) Save('six', short_index)