The Line Order Strategy triggers a market order when price crosses a user-defined horizontal line. It is intended as a simplified conversion of the original MQL script LineOrder.mq4, providing manual line trading functionality through the high-level StockSharp API.
The strategy exposes parameters to control direction, entry level and risk management. After entering a position, optional stop-loss, take-profit and trailing stop levels are monitored on every completed candle. The logic is fully event-driven and does not maintain custom collections.
Parameters
LinePrice – price level for placing the order.
IsBuy – true for long entries, false for short entries.
StopLoss – stop-loss distance in price units (0 disables).
TakeProfit – take-profit distance in price units (0 disables).
TrailingStop – trailing stop distance in price units (0 disables).
Volume – order volume.
CandleType – candle type used to monitor price.
Trading Rules
Entry: when the closing price crosses the LinePrice in the chosen direction.
Stop-loss: closes position when loss exceeds StopLoss distance from entry.
Take-profit: closes position when profit reaches TakeProfit distance.
Trailing stop: after entry, adjusts to the most favorable price and exits when price moves against position by TrailingStop.
Notes
Works with any security supported by StockSharp.
Designed for educational purposes to illustrate translation of manual line trading from MQL.
Python version is intentionally omitted.
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>
/// Executes a market order when price crosses a moving average "line".
/// Manages stop-loss and take-profit via candle monitoring.
/// </summary>
public class LineOrderStrategy : Strategy
{
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<decimal> _takeProfitPct;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal _prevClose;
private decimal _prevMa;
private bool _hasPrev;
public int MaLength { get => _maLength.Value; set => _maLength.Value = value; }
public decimal StopLossPct { get => _stopLossPct.Value; set => _stopLossPct.Value = value; }
public decimal TakeProfitPct { get => _takeProfitPct.Value; set => _takeProfitPct.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public LineOrderStrategy()
{
_maLength = Param(nameof(MaLength), 20)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Moving average period for line", "Indicators");
_stopLossPct = Param(nameof(StopLossPct), 2m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_takeProfitPct = Param(nameof(TakeProfitPct), 3m)
.SetDisplay("Take Profit %", "Take profit percentage", "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();
_entryPrice = 0;
_prevClose = 0;
_prevMa = 0;
_hasPrev = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ma = new SimpleMovingAverage { Length = MaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal maVal)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (!_hasPrev)
{
_prevClose = close;
_prevMa = maVal;
_hasPrev = true;
return;
}
// Check stop-loss / take-profit for existing positions
if (Position > 0 && _entryPrice > 0)
{
if (close <= _entryPrice * (1 - StopLossPct / 100m) ||
close >= _entryPrice * (1 + TakeProfitPct / 100m))
{
SellMarket();
_entryPrice = 0;
_prevClose = close;
_prevMa = maVal;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (close >= _entryPrice * (1 + StopLossPct / 100m) ||
close <= _entryPrice * (1 - TakeProfitPct / 100m))
{
BuyMarket();
_entryPrice = 0;
_prevClose = close;
_prevMa = maVal;
return;
}
}
// Cross above MA line -> buy
if (_prevClose <= _prevMa && close > maVal)
{
if (Position < 0)
{
BuyMarket();
_entryPrice = 0;
}
if (Position <= 0)
{
BuyMarket();
_entryPrice = close;
}
}
// Cross below MA line -> sell
else if (_prevClose >= _prevMa && close < maVal)
{
if (Position > 0)
{
SellMarket();
_entryPrice = 0;
}
if (Position >= 0)
{
SellMarket();
_entryPrice = close;
}
}
_prevClose = close;
_prevMa = maVal;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class line_order_strategy(Strategy):
def __init__(self):
super(line_order_strategy, self).__init__()
self._ma_length = self.Param("MaLength", 20) \
.SetDisplay("MA Length", "Moving average period for line", "Indicators")
self._stop_loss_pct = self.Param("StopLossPct", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._take_profit_pct = self.Param("TakeProfitPct", 3.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._entry_price = 0.0
self._prev_close = 0.0
self._prev_ma = 0.0
self._has_prev = False
@property
def ma_length(self):
return self._ma_length.Value
@property
def stop_loss_pct(self):
return self._stop_loss_pct.Value
@property
def take_profit_pct(self):
return self._take_profit_pct.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(line_order_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_close = 0.0
self._prev_ma = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(line_order_strategy, self).OnStarted2(time)
ma = SimpleMovingAverage()
ma.Length = self.ma_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, ma_val):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
if not self._has_prev:
self._prev_close = close
self._prev_ma = ma_val
self._has_prev = True
return
# Check stop-loss / take-profit for existing positions
if self.Position > 0 and self._entry_price > 0:
if (close <= self._entry_price * (1 - self.stop_loss_pct / 100.0) or
close >= self._entry_price * (1 + self.take_profit_pct / 100.0)):
self.SellMarket()
self._entry_price = 0
self._prev_close = close
self._prev_ma = ma_val
return
elif self.Position < 0 and self._entry_price > 0:
if (close >= self._entry_price * (1 + self.stop_loss_pct / 100.0) or
close <= self._entry_price * (1 - self.take_profit_pct / 100.0)):
self.BuyMarket()
self._entry_price = 0
self._prev_close = close
self._prev_ma = ma_val
return
# Cross above MA line -> buy
if self._prev_close <= self._prev_ma and close > ma_val:
if self.Position < 0:
self.BuyMarket()
self._entry_price = 0
if self.Position <= 0:
self.BuyMarket()
self._entry_price = close
# Cross below MA line -> sell
elif self._prev_close >= self._prev_ma and close < ma_val:
if self.Position > 0:
self.SellMarket()
self._entry_price = 0
if self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._prev_close = close
self._prev_ma = ma_val
def CreateClone(self):
return line_order_strategy()