Remote Signals with Limit-Chasing Maker Order Functionality
stableDescription
This is a simple SIGNALS bot but it demonstrates how to use 'target position size' in a cycle function that constantly tries to close the Delta between what you have now, and what you want to have.
It uses Maker Orders only to try and save on fees, and "chases" the price with them until price jitter fills them.
You will need remote signals set up for this. It currently only supports 1 coin per bot code, this is an area you may want to improve upon.
HaasScript
-- Author: Strvinmarvin
-- Date: 2023-11-29
-- Notes: This only uses Signals 1, 2, 4, 5
-- It is designed to be a simple signal bot, but to use limit-chasing to secure Maker orders if possible.
-- go here to watch the video on setting up signals in TradingView: https://help.haasonline.com/docs/usage/remote-signals
----------------------------------------------------------------------------------------------------------
-- Your signal details are listed below:
-- Name: your_signal_name_here
-- Identifier: abcdabcdabcdabcd
-- Secret: wxyzwxyzwxyzwxyz
-- Webhooks
-- To make things a little easier for webhook users. We prepared the following copy and paste ready links for you;
-- Long (or buy)
-- https://production.hcdn.web.haasapi.com/SignalAPI.php?channel=STORE_SIGNAL&id=abcdabcdabcdabcd&secret=wxyzwxyzwxyzwxyz&signal=1
-- Short (or sell)
-- https://production.hcdn.web.haasapi.com/SignalAPI.php?channel=STORE_SIGNAL&id=abcdabcdabcdabcd&secret=wxyzwxyzwxyzwxyz&signal=2
-- Exit all positions
-- https://production.hcdn.web.haasapi.com/SignalAPI.php?channel=STORE_SIGNAL&id=abcdabcdabcdabcd&secret=wxyzwxyzwxyzwxyz&signal=3
-- Exit long
-- https://production.hcdn.web.haasapi.com/SignalAPI.php?channel=STORE_SIGNAL&id=abcdabcdabcdabcd&secret=wxyzwxyzwxyzwxyz&signal=4
-- Exit short
-- https://production.hcdn.web.haasapi.com/SignalAPI.php?channel=STORE_SIGNAL&id=abcdabcdabcdabcd&secret=wxyzwxyzwxyzwxyz&signal=5
-- Custom signal 1
-- https://production.hcdn.web.haasapi.com/SignalAPI.php?channel=STORE_SIGNAL&id=abcdabcdabcdabcd&secret=wxyzwxyzwxyzwxyz&signal=6
-- Custom signal 2
-- https://production.hcdn.web.haasapi.com/SignalAPI.php?channel=STORE_SIGNAL&id=abcdabcdabcdabcd&secret=wxyzwxyzwxyzwxyz&signal=7
-- High speed update are recommended for a quick responce, but its not essential
EnableHighSpeedUpdates(true)
HideTradeAmountSettings()
HideOrderSettings()
local notional_fiat_per_side = Input('Notional FIAT Per Side', 2000)
local target_leverage = Input('Target Lev', 10)
-- Use your own signal identifier for this field
signal = GetRemoteSignal('abcdabcdabcdabcd')
local current_signal = Load('current_signal', {
['LONG'] = SignalNone,
['SHORT'] = SignalNone
})
-- Process Long signals
if current_signal['LONG'] ~= SignalExitLong and signal == SignalExitLong then
current_signal['LONG'] = SignalExitLong
Log('[LONG] Signal Received: '..current_signal['LONG'], Green)
elseif current_signal['LONG'] ~= SignalLong and signal == SignalLong then
current_signal['LONG'] = SignalLong
Log('[LONG] Signal Received: '..current_signal['LONG'], Green)
end
-- Process Short signals
if current_signal['SHORT'] ~= SignalExitShort and signal == SignalExitShort then
current_signal['SHORT'] = SignalExitShort
Log('[SHORT] Signal Received: '..current_signal['SHORT'], Fuchsia)
elseif current_signal['SHORT'] ~= SignalShort and signal == SignalShort then
current_signal['SHORT'] = SignalShort
Log('[SHORT] Signal Received: '..current_signal['SHORT'], Fuchsia)
end
local hard_exit_now = Input('Hard Exit Now', false, '', 'Control')
local soft_exit_now = Input('Soft Exit Now', false, '', 'Control')
local log_inposition_holding = Input('Log when in position and holding?', false)
local log_noposition_holding = Input('Log when not in position and waiting?', false)
local mkt = PriceMarket()
local tmkt = TradeMarketContainer(mkt)
local cp = CurrentPrice(mkt)
local g = Load('g', {})
if Count(g) == 0 then
g.target_amt = {
['LONG'] = 0,
['SHORT'] = 0,
}
SetLeverage(target_leverage, mkt)
SetMarginMode(IsolatedMarginMode, mkt)
SetPositionMode(HedgePositionMode, '', mkt)
-- Check if we already have open positions.
local sides = {'LONG', 'SHORT'}
local pos_enum = {
['LONG'] = PositionLong,
['SHORT'] = PositionShort
}
local pos = {}
for side in sides do
pos[side] = UserPositionContainer({market = mkt, direction = pos_enum[side]})
if pos[side].amount > tmkt.calculatedMinTradeAmount then
g.target_amt[side] = pos[side].amount
Log('**** Found existing '..side..' Position, setting this as the starting state.', SkyBlue)
if side == 'LONG' then
current_signal[side] = SignalLong
CreatePosition(PositionLong, pos[side].enterPrice, pos[side].amount, mkt, target_leverage, BaseCurrency(mkt)..'-'..side)
elseif side == 'SHORT' then
current_signal[side] = SignalShort
CreatePosition(PositionLong, pos[side].enterPrice, pos[side].amount, mkt, target_leverage, BaseCurrency(mkt)..'-'..side)
end
end
end
end
local coin = BaseCurrency(mkt)
local sides = {'LONG', 'SHORT'}
local pos_enum = {
['LONG'] = PositionLong,
['SHORT'] = PositionShort
}
local pos = {}
for side in sides do
pos[side] = UserPositionContainer({market = mkt, direction = pos_enum[side]})
end
function heartbeat()
Log('. . '..coin..' - '..'[HeartBeat] '..'LONG Current: '..pos['LONG'].amount..' '..coin..' (PNL: $'..pos['LONG'].profit..'), SHORT Current: '..pos['SHORT'].amount..' '..coin..' (PNL: $'..pos['SHORT'].profit..')', DarkGray)
end
OptimizedForInterval(15, heartbeat)
function AdjustPositionSizes(f_side, f_pos)
local target_size = ParseTradeAmount(mkt, cp.bid, g.target_amt[f_side])
local side = f_side
local pos = f_pos[side]
local pid = coin..'-'..side
local open_orders = GetAllOpenOrders(pid)
local open_pos = GetAllOpenPositions()
if hard_exit_now then
Log('* '..coin..' HARD-EXIT enabled - Overriding Positions to Target of 0...', Orange)
target_size = 0 -- overrride to 0
end
local delta
if target_size == 0 and pos.amount == 0 then
if log_noposition_holding then
Log('. . '..coin..' - '..'[PosAdj] '..side..' Current: '..pos.amount..' '..coin..', Target: '..target_size..' '..coin..', No Position and No Target (Zero)', DarkGray)
end
if Count(open_orders) > 0 then
CancelAllOrders(pid)
end
return
elseif target_size == pos.amount then
if log_inposition_holding then
Log('. . '..coin..' - '..'[PosAdj] '..side..' Current: '..pos.amount..' '..coin..', Target: '..target_size..' '..coin..', No Delta (No Change)', SkyBlue)
end
if Count(open_orders) > 0 then
CancelAllOrders(pid)
end
return
elseif target_size > pos.amount then
delta = ParseTradeAmount(mkt, cp.bid, target_size - pos.amount)
Log('. . '..coin..' - '..'[PosAdj] '..side..' Current: '..pos.amount..' '..coin..', Target: '..target_size..' '..coin..', Delta: + '..delta..' (Accumulate)', Green)
else
delta = ParseTradeAmount(mkt, cp.bid, pos.amount - target_size)
Log('. . '..coin..' - '..'[PosAdj] '..side..' Current: '..pos.amount..' '..coin..', Target: '..target_size..' '..coin..', Delta: - '..delta..' (Distribute)', Purple)
end
if IsTradeAmountEnough(mkt, cp.bid, delta, false) then
if side == 'LONG' then
if target_size > pos.amount then --accumulate
if soft_exit_now then
Log('* '..coin..' SOFT-EXIT enabled - blocking new Long position...', Orange)
-- elseif Count(open_pos) > max_open_positions then
-- Log('* '..coin..' Max Open Positions [max: '..max_open_positions..'] exceeded [open now: '..Count(open_pos)..'] - blocking new Long position...', Yellow)
else
for key, ord in pairs(open_orders) do
if ord.isEnterOrder then
if ord.price == cp.bid then
Log('. . '..coin..' - '..'[PosAdj] Enter '..side..' order already exists at Bid price...')
return
else
Log('. . '..coin..' - '..'[PosAdj] Enter '..side..' order already exists but NOT at Bid price. Cancelling...')
CancelOrder(ord.orderId)
end
end
end
PlaceGoLongOrder(cp.bid, delta, mkt, MakerOrCancelOrderType, 'Accumulate '..side, pid, 120)
end
else --distribute
for key, ord in pairs(open_orders) do
if ord.isExitOrder then
if ord.price == cp.ask then
Log('. . '..coin..' - '..'[PosAdj] Exit '..side..' order already exists at Ask price...')
return
else
Log('. . '..coin..' - '..'[PosAdj] Exit '..side..' order already exists but NOT at Ask price. Cancelling...')
CancelOrder(ord.orderId)
end
end
end
if pos.amount < tmkt.calculatedMinTradeAmount then
PlaceGoLongOrder(cp.bid, tmkt.calculatedMinTradeAmount, mkt, MarketOrderType, 'Position too small - market buy '..side, pid)
else
PlaceExitLongOrder(cp.ask, delta, mkt, MakerOrCancelOrderType, 'Distribute '..side, pid, 120)
end
end
elseif side == 'SHORT' then
if target_size > pos.amount then --accumulate
if soft_exit_now then
Log('* '..coin..' SOFT-EXIT enabled - blocking new Short position...', Orange)
-- elseif Count(open_pos) > max_open_positions then
-- Log('* '..coin..' Max Open Positions [max: '..max_open_positions..'] exceeded [open now: '..Count(open_pos)..'] - blocking new Short position...', Yellow)
else
for key, ord in pairs(open_orders) do
if ord.isEnterOrder then
if ord.price == cp.ask then
Log('. . '..coin..' - '..'[PosAdj] Enter '..side..' order already exists at Ask price...')
return
else
Log('. . '..coin..' - '..'[PosAdj] Enter '..side..' order already exists but NOT at Ask price. Cancelling...')
CancelOrder(ord.orderId)
end
end
end
PlaceGoShortOrder(cp.ask, delta, mkt, MakerOrCancelOrderType, 'Accumulate '..side, pid, 120)
end
else --distribute
for key, ord in pairs(open_orders) do
if ord.isExitOrder then
if ord.price == cp.bid then
Log('. . '..coin..' - '..'[PosAdj] Exit '..side..' order already exists at Bid price...')
return
else
Log('. . '..coin..' - '..'[PosAdj] Exit '..side..' order already exists but NOT at Bid price. Cancelling...')
CancelOrder(ord.orderId)
end
end
end
if pos.amount < tmkt.calculatedMinTradeAmount then
PlaceGoShortOrder(cp.ask, tmkt.calculatedMinTradeAmount, mkt, MarketOrderType, 'Position too small - market buy '..side, pid)
else
PlaceExitShortOrder(cp.bid, delta, mkt, MakerOrCancelOrderType, 'Distribute '..side, pid, 120)
end
end
end
else
Log('. . '..coin..' - '..'[PosAdj] '..side..' Trade Amount too Small! ['..delta..' '..coin..']')
return
end
end
local qty_to_trade = CC_GetQtyFromFiat(notional_fiat_per_side, mkt).coin_amount
-- Process Long position
if current_signal['LONG'] == SignalLong then
if pos['LONG'].amount < qty_to_trade * 0.95 and qty_to_trade - pos['LONG'].amount >= tmkt.calculatedMinTradeAmount then
g.target_amt['LONG'] = qty_to_trade
end
elseif current_signal['LONG'] == SignalExitLong then
g.target_amt['LONG'] = 0
end
-- Process Short position
if current_signal['SHORT'] == SignalShort then
if pos['SHORT'].amount < qty_to_trade * 0.95 and qty_to_trade - pos['SHORT'].amount >= tmkt.calculatedMinTradeAmount then
g.target_amt['SHORT'] = qty_to_trade
end
elseif current_signal['SHORT'] == SignalExitShort then
g.target_amt['SHORT'] = 0
end
for side in sides do
AdjustPositionSizes(side, pos)
end
Save('g', g)
Save('current_signal', current_signal)
0 Comments
Sign in to leave a comment.
No comments yet. Be the first!