This strategy replicates an "One Cancels the Other" order ticket originally written for MetaTrader. It allows the trader to define up to four independent price triggers:
Buy Limit Price
Sell Limit Price
Buy Stop Price
Sell Stop Price
The strategy subscribes to Level1 data to continuously monitor the best bid and ask. When a trigger price is reached, it submits a market order in the corresponding direction. After an order is executed, stop-loss and take-profit protections are applied using distances measured in pips. These distances are automatically converted to absolute prices based on the security's PriceStep.
When the OCO mode is enabled, hitting any trigger will automatically disable all other triggers, effectively implementing the classic one-cancels-the-other behavior. If OCO mode is disabled, other triggers remain active and can open additional positions as prices continue to move.
Details
Entry Criteria:
Long when Ask <= BuyLimitPrice (Buy Limit trigger).
Long when Ask >= BuyStopPrice (Buy Stop trigger).
Short when Bid >= SellLimitPrice (Sell Limit trigger).
Short when Bid <= SellStopPrice (Sell Stop trigger).
Long/Short: Both.
Exit Criteria:
Positions are closed automatically by predefined stop-loss or take-profit levels.
Stops: Yes, stop-loss and take-profit in pips.
Default Values:
StopLossPips = 300.
TakeProfitPips = 300.
OCO Mode = enabled.
Filters:
Category: Order execution.
Direction: Both.
Indicators: None.
Stops: Yes.
Complexity: Simple.
Timeframe: Tick-based.
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>
/// OCO-style breakout strategy. Calculates dynamic buy-stop and sell-stop levels
/// from recent high/low and enters on breakout, with StdDev-based stop-loss and take-profit.
/// </summary>
public class OcoOrderStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _stdMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _recentHigh;
private decimal _recentLow;
private decimal _entryPrice;
private int _barCount;
public int LookbackPeriod { get => _lookbackPeriod.Value; set => _lookbackPeriod.Value = value; }
public decimal StdMultiplier { get => _stdMultiplier.Value; set => _stdMultiplier.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public OcoOrderStrategy()
{
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback", "Bars for high/low calculation", "General");
_stdMultiplier = Param(nameof(StdMultiplier), 1.5m)
.SetDisplay("StdDev Multiplier", "Multiplier for SL/TP distance", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_recentHigh = 0;
_recentLow = decimal.MaxValue;
_entryPrice = 0;
_barCount = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var stdDev = new StandardDeviation { Length = LookbackPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(stdDev, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal stdValue)
{
if (candle.State != CandleStates.Finished)
return;
_barCount++;
// Track rolling high/low
if (_barCount <= LookbackPeriod)
{
if (candle.HighPrice > _recentHigh)
_recentHigh = candle.HighPrice;
if (candle.LowPrice < _recentLow)
_recentLow = candle.LowPrice;
return;
}
if (stdValue <= 0)
return;
var close = candle.ClosePrice;
var slDistance = stdValue * StdMultiplier;
// Exit logic
if (Position > 0)
{
if (close <= _entryPrice - slDistance || close >= _entryPrice + slDistance * 2)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close >= _entryPrice + slDistance || close <= _entryPrice - slDistance * 2)
{
BuyMarket();
_entryPrice = 0;
}
}
// Entry: breakout above recent high or below recent low
if (Position == 0)
{
if (close > _recentHigh)
{
BuyMarket();
_entryPrice = close;
}
else if (close < _recentLow)
{
SellMarket();
_entryPrice = close;
}
}
// Update high/low
if (candle.HighPrice > _recentHigh)
_recentHigh = candle.HighPrice;
if (candle.LowPrice < _recentLow)
_recentLow = candle.LowPrice;
}
}
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.Indicators import StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class oco_order_strategy(Strategy):
def __init__(self):
super(oco_order_strategy, self).__init__()
self._lookback_period = self.Param("LookbackPeriod", 20) \
.SetDisplay("Lookback", "Bars for high/low calculation", "General")
self._std_multiplier = self.Param("StdMultiplier", 1.5) \
.SetDisplay("StdDev Multiplier", "Multiplier for SL/TP distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._recent_high = 0.0
self._recent_low = 1e18
self._entry_price = 0.0
self._bar_count = 0
@property
def lookback_period(self):
return self._lookback_period.Value
@property
def std_multiplier(self):
return self._std_multiplier.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(oco_order_strategy, self).OnReseted()
self._recent_high = 0.0
self._recent_low = 1e18
self._entry_price = 0.0
self._bar_count = 0
def OnStarted2(self, time):
super(oco_order_strategy, self).OnStarted2(time)
std_dev = StandardDeviation()
std_dev.Length = self.lookback_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(std_dev, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, std_value):
if candle.State != CandleStates.Finished:
return
self._bar_count += 1
# Track rolling high/low
if self._bar_count <= self.lookback_period:
if candle.HighPrice > self._recent_high:
self._recent_high = candle.HighPrice
if candle.LowPrice < self._recent_low:
self._recent_low = candle.LowPrice
return
if std_value <= 0:
return
close = candle.ClosePrice
sl_distance = std_value * self.std_multiplier
# Exit logic
if self.Position > 0:
if close <= self._entry_price - sl_distance or close >= self._entry_price + sl_distance * 2:
self.SellMarket()
self._entry_price = 0
elif self.Position < 0:
if close >= self._entry_price + sl_distance or close <= self._entry_price - sl_distance * 2:
self.BuyMarket()
self._entry_price = 0
# Entry: breakout above recent high or below recent low
if self.Position == 0:
if close > self._recent_high:
self.BuyMarket()
self._entry_price = close
elif close < self._recent_low:
self.SellMarket()
self._entry_price = close
# Update high/low
if candle.HighPrice > self._recent_high:
self._recent_high = candle.HighPrice
if candle.LowPrice < self._recent_low:
self._recent_low = candle.LowPrice
def CreateClone(self):
return oco_order_strategy()