Limits Bot Strategy
Places symmetric limit orders around each candle's open price and protects positions with stop-loss, take-profit and optional trailing.
Details
- Entry:
- Buy limit at
Open - StopOrderDistance * PriceStepif long trading enabled. - Sell limit at
Open + StopOrderDistance * PriceStepif short trading enabled.
- Buy limit at
- Exit: Market close on stop-loss, take-profit or trailing stop trigger.
- Long/Short: Both.
- Stops: Fixed stop-loss with trailing option.
- Default values:
StopOrderDistance= 5TakeProfit= 35StopLoss= 8TrailingStart= 40TrailingDistance= 30TrailingStep= 1CandleType= 1 minute
- Session: Trades only between
StartTimeandEndTime. - Filters:
- Category: Price action
- Direction: Both
- Indicators: None
- Stops: Yes
- Complexity: Basic
- Timeframe: Intraday
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy placing limit orders around each candle open and protecting position with stop loss and trailing stop.
/// </summary>
public class LimitsBotStrategy : Strategy
{
private readonly StrategyParam<bool> _buyAllow;
private readonly StrategyParam<bool> _sellAllow;
private readonly StrategyParam<decimal> _stopOrderDistance;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _trailingStart;
private readonly StrategyParam<decimal> _trailingDistance;
private readonly StrategyParam<decimal> _trailingStep;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private Order _buyOrder;
private Order _sellOrder;
private decimal? _entryPrice;
private decimal? _longStop, _longTake, _shortStop, _shortTake;
private decimal _lastPosition;
private int _barsSinceExit;
public bool BuyAllow { get => _buyAllow.Value; set => _buyAllow.Value = value; }
public bool SellAllow { get => _sellAllow.Value; set => _sellAllow.Value = value; }
public decimal StopOrderDistance { get => _stopOrderDistance.Value; set => _stopOrderDistance.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TrailingStart { get => _trailingStart.Value; set => _trailingStart.Value = value; }
public decimal TrailingDistance { get => _trailingDistance.Value; set => _trailingDistance.Value = value; }
public decimal TrailingStep { get => _trailingStep.Value; set => _trailingStep.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public LimitsBotStrategy()
{
_buyAllow = Param(nameof(BuyAllow), true)
.SetDisplay("Buy Allow", "Enable long orders", "Trading");
_sellAllow = Param(nameof(SellAllow), true)
.SetDisplay("Sell Allow", "Enable short orders", "Trading");
_stopOrderDistance = Param(nameof(StopOrderDistance), 5m)
.SetDisplay("Stop Order Distance", "Distance from open price", "Risk");
_takeProfit = Param(nameof(TakeProfit), 35m)
.SetDisplay("Take Profit", "Take profit in ticks", "Risk");
_stopLoss = Param(nameof(StopLoss), 8m)
.SetDisplay("Stop Loss", "Stop loss in ticks", "Risk");
_trailingStart = Param(nameof(TrailingStart), 40m)
.SetDisplay("Trailing Start", "Profit to activate trailing", "Risk");
_trailingDistance = Param(nameof(TrailingDistance), 30m)
.SetDisplay("Trailing Distance", "Trailing stop distance", "Risk");
_trailingStep = Param(nameof(TrailingStep), 1m)
.SetDisplay("Trailing Step", "Minimal move to shift trailing", "Risk");
_cooldownCandles = Param(nameof(CooldownCandles), 2)
.SetGreaterThanZero()
.SetDisplay("Cooldown Candles", "Bars to wait after an exit", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_buyOrder = null;
_sellOrder = null;
_entryPrice = null;
_longStop = _longTake = _shortStop = _shortTake = null;
_lastPosition = 0;
_barsSinceExit = CooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_buyOrder = null;
_sellOrder = null;
_entryPrice = null;
_longStop = _longTake = _shortStop = _shortTake = null;
_lastPosition = 0;
_barsSinceExit = CooldownCandles;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var priceStep = 0.01m;
_barsSinceExit++;
if (Position > 0 && _lastPosition <= 0)
{
_entryPrice = _buyOrder?.Price ?? candle.OpenPrice;
_longStop = _entryPrice - StopLoss * priceStep;
_longTake = _entryPrice + TakeProfit * priceStep;
if (_sellOrder != null)
{
CancelOrder(_sellOrder);
_sellOrder = null;
}
}
else if (Position < 0 && _lastPosition >= 0)
{
_entryPrice = _sellOrder?.Price ?? candle.OpenPrice;
_shortStop = _entryPrice + StopLoss * priceStep;
_shortTake = _entryPrice - TakeProfit * priceStep;
if (_buyOrder != null)
{
CancelOrder(_buyOrder);
_buyOrder = null;
}
}
if (Position > 0 && _entryPrice is decimal entryLong)
{
if (TrailingDistance > 0m && TrailingStart > 0m && candle.ClosePrice - entryLong >= TrailingStart * priceStep)
{
var newStop = candle.ClosePrice - TrailingDistance * priceStep;
if (_longStop == null || newStop >= _longStop.Value + TrailingStep * priceStep)
_longStop = newStop;
}
if ((_longStop.HasValue && candle.LowPrice <= _longStop) || (_longTake.HasValue && candle.HighPrice >= _longTake))
{
SellMarket();
_entryPrice = _longStop = _longTake = null;
_barsSinceExit = 0;
}
}
else if (Position < 0 && _entryPrice is decimal entryShort)
{
if (TrailingDistance > 0m && TrailingStart > 0m && entryShort - candle.ClosePrice >= TrailingStart * priceStep)
{
var newStop = candle.ClosePrice + TrailingDistance * priceStep;
if (_shortStop == null || newStop <= _shortStop.Value - TrailingStep * priceStep)
_shortStop = newStop;
}
if ((_shortStop.HasValue && candle.HighPrice >= _shortStop) || (_shortTake.HasValue && candle.LowPrice <= _shortTake))
{
BuyMarket();
_entryPrice = _shortStop = _shortTake = null;
_barsSinceExit = 0;
}
}
else if (Position == 0 && _barsSinceExit >= CooldownCandles)
{
_entryPrice = null;
_longStop = _longTake = _shortStop = _shortTake = null;
if (_buyOrder != null)
{
CancelOrder(_buyOrder);
_buyOrder = null;
}
if (_sellOrder != null)
{
CancelOrder(_sellOrder);
_sellOrder = null;
}
if (BuyAllow && candle.ClosePrice >= candle.OpenPrice)
_buyOrder = BuyLimit(candle.OpenPrice - StopOrderDistance * priceStep, Volume);
if (SellAllow && candle.ClosePrice <= candle.OpenPrice)
_sellOrder = SellLimit(candle.OpenPrice + StopOrderDistance * priceStep, Volume);
}
_lastPosition = Position;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class limits_bot_strategy(Strategy):
def __init__(self):
super(limits_bot_strategy, self).__init__()
self._buy_allow = self.Param("BuyAllow", True) \
.SetDisplay("Buy Allow", "Enable long orders", "Trading")
self._sell_allow = self.Param("SellAllow", True) \
.SetDisplay("Sell Allow", "Enable short orders", "Trading")
self._stop_order_distance = self.Param("StopOrderDistance", 5.0) \
.SetDisplay("Stop Order Distance", "Distance from open price", "Risk")
self._take_profit = self.Param("TakeProfit", 35.0) \
.SetDisplay("Take Profit", "Take profit in ticks", "Risk")
self._stop_loss = self.Param("StopLoss", 8.0) \
.SetDisplay("Stop Loss", "Stop loss in ticks", "Risk")
self._trailing_start = self.Param("TrailingStart", 40.0) \
.SetDisplay("Trailing Start", "Profit to activate trailing", "Risk")
self._trailing_distance = self.Param("TrailingDistance", 30.0) \
.SetDisplay("Trailing Distance", "Trailing stop distance", "Risk")
self._trailing_step = self.Param("TrailingStep", 1.0) \
.SetDisplay("Trailing Step", "Minimal move to shift trailing", "Risk")
self._cooldown_candles = self.Param("CooldownCandles", 2) \
.SetDisplay("Cooldown Candles", "Bars to wait after an exit", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for strategy", "General")
self._buy_order = None
self._sell_order = None
self._entry_price = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._last_position = 0.0
self._bars_since_exit = 0
@property
def buy_allow(self):
return self._buy_allow.Value
@property
def sell_allow(self):
return self._sell_allow.Value
@property
def stop_order_distance(self):
return self._stop_order_distance.Value
@property
def take_profit(self):
return self._take_profit.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def trailing_start(self):
return self._trailing_start.Value
@property
def trailing_distance(self):
return self._trailing_distance.Value
@property
def trailing_step(self):
return self._trailing_step.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(limits_bot_strategy, self).OnReseted()
self._buy_order = None
self._sell_order = None
self._entry_price = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._last_position = 0.0
self._bars_since_exit = int(self.cooldown_candles)
def OnStarted2(self, time):
super(limits_bot_strategy, self).OnStarted2(time)
self._buy_order = None
self._sell_order = None
self._entry_price = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._last_position = 0.0
self._bars_since_exit = int(self.cooldown_candles)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price_step = 0.01
self._bars_since_exit += 1
pos = float(self.Position)
close_price = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
high_price = float(candle.HighPrice)
low_price = float(candle.LowPrice)
sl = float(self.stop_loss)
tp = float(self.take_profit)
ts = float(self.trailing_start)
td = float(self.trailing_distance)
tstep = float(self.trailing_step)
cd = int(self.cooldown_candles)
if pos > 0 and self._last_position <= 0:
self._entry_price = open_price
self._long_stop = self._entry_price - sl * price_step
self._long_take = self._entry_price + tp * price_step
if self._sell_order is not None:
self.CancelOrder(self._sell_order)
self._sell_order = None
elif pos < 0 and self._last_position >= 0:
self._entry_price = open_price
self._short_stop = self._entry_price + sl * price_step
self._short_take = self._entry_price - tp * price_step
if self._buy_order is not None:
self.CancelOrder(self._buy_order)
self._buy_order = None
if pos > 0 and self._entry_price is not None:
entry_long = self._entry_price
if td > 0 and ts > 0 and close_price - entry_long >= ts * price_step:
new_stop = close_price - td * price_step
if self._long_stop is None or new_stop >= self._long_stop + tstep * price_step:
self._long_stop = new_stop
if (self._long_stop is not None and low_price <= self._long_stop) or \
(self._long_take is not None and high_price >= self._long_take):
self.SellMarket()
self._entry_price = None
self._long_stop = None
self._long_take = None
self._bars_since_exit = 0
elif pos < 0 and self._entry_price is not None:
entry_short = self._entry_price
if td > 0 and ts > 0 and entry_short - close_price >= ts * price_step:
new_stop = close_price + td * price_step
if self._short_stop is None or new_stop <= self._short_stop - tstep * price_step:
self._short_stop = new_stop
if (self._short_stop is not None and high_price >= self._short_stop) or \
(self._short_take is not None and low_price <= self._short_take):
self.BuyMarket()
self._entry_price = None
self._short_stop = None
self._short_take = None
self._bars_since_exit = 0
elif pos == 0 and self._bars_since_exit >= cd:
self._entry_price = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
if self._buy_order is not None:
self.CancelOrder(self._buy_order)
self._buy_order = None
if self._sell_order is not None:
self.CancelOrder(self._sell_order)
self._sell_order = None
sod = float(self.stop_order_distance)
if self.buy_allow and close_price >= open_price:
self._buy_order = self.BuyLimit(open_price - sod * price_step, self.Volume)
if self.sell_allow and close_price <= open_price:
self._sell_order = self.SellLimit(open_price + sod * price_step, self.Volume)
self._last_position = pos
def CreateClone(self):
return limits_bot_strategy()