This strategy enters a position when the completed candle's close differs from its open by at least Price Step. If the difference is positive, it buys; if negative, it sells.
After opening a position, every additional move of Price Step in the trade's favor triggers an additional market order in the same direction up to Max Orders. Protective stop-loss and take-profit are applied for every position using absolute price distances.
Parameters
Price Step – minimum price move (in absolute units) required to open or add to a position.
Max Orders – maximum number of market orders allowed in one direction.
Stop Loss – distance from entry price where a protective stop is placed.
Take Profit – distance from entry price where a profit target is placed.
Candle Type – candle timeframe used for calculations (default 1 minute).
Trading Logic
Wait for a finished candle.
If no position is open:
Buy if Close - Open >= Price Step.
Sell if Open - Close >= Price Step.
When a position exists:
If price advances by Price Step from last entry, add another order in the same direction.
Stop adding orders once the number reaches Max Orders.
Stop-loss and take-profit are managed automatically for each order.
The strategy is adapted from the MQL5 expert "Exp Fishing" and demonstrates a simple grid-style trend-following approach.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend-following strategy that adds to position every time price moves by configured step.
/// Based on MQL5 Exp_Fishing expert.
/// </summary>
public class ExpFishingStrategy : Strategy
{
private readonly StrategyParam<decimal> _priceStep;
private readonly StrategyParam<int> _maxOrders;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private int _ordersCount;
private bool _isLong;
/// <summary>
/// Initializes a new instance of <see cref="ExpFishingStrategy"/>.
/// </summary>
public ExpFishingStrategy()
{
_priceStep = Param(nameof(PriceStep), 300m)
.SetGreaterThanZero()
.SetDisplay("Price Step", "Minimum price move to enter or add", "Parameters")
;
_maxOrders = Param(nameof(MaxOrders), 10)
.SetGreaterThanZero()
.SetDisplay("Max Orders", "Maximum number of orders in one direction", "Parameters")
;
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss distance in price units", "Parameters")
;
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit distance in price units", "Parameters")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for analysis", "Parameters");
}
/// <summary>
/// Price move required to open or add position.
/// </summary>
public decimal PriceStep
{
get => _priceStep.Value;
set => _priceStep.Value = value;
}
/// <summary>
/// Maximum number of orders in single direction.
/// </summary>
public int MaxOrders
{
get => _maxOrders.Value;
set => _maxOrders.Value = value;
}
/// <summary>
/// Stop loss distance in price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take profit distance in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Candle type used for strategy calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_ordersCount = 0;
_isLong = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(new Unit(TakeProfit, UnitTypes.Absolute), new Unit(StopLoss, UnitTypes.Absolute));
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var move = candle.ClosePrice - candle.OpenPrice;
if (Position == 0)
{
_ordersCount = 0;
if (move >= PriceStep)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_ordersCount = 1;
_isLong = true;
}
else if (move <= -PriceStep)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_ordersCount = 1;
_isLong = false;
}
return;
}
if (_ordersCount >= MaxOrders)
return;
if (_isLong)
{
if (candle.ClosePrice - _entryPrice >= PriceStep)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_ordersCount++;
}
}
else
{
if (_entryPrice - candle.ClosePrice >= PriceStep)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_ordersCount++;
}
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class exp_fishing_strategy(Strategy):
"""
Trend-following strategy that adds to position every time price moves by configured step.
Uses StartProtection for TP/SL.
"""
def __init__(self):
super(exp_fishing_strategy, self).__init__()
self._price_step = self.Param("PriceStep", 300.0) \
.SetDisplay("Price Step", "Minimum price move to enter or add", "Parameters")
self._max_orders = self.Param("MaxOrders", 10) \
.SetDisplay("Max Orders", "Maximum orders in one direction", "Parameters")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss", "Stop loss distance in price units", "Parameters")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit", "Take profit distance in price units", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "Parameters")
self._entry_price = 0.0
self._orders_count = 0
self._is_long = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_fishing_strategy, self).OnReseted()
self._entry_price = 0.0
self._orders_count = 0
self._is_long = False
def OnStarted2(self, time):
super(exp_fishing_strategy, self).OnStarted2(time)
tp = float(self._take_profit.Value)
sl = float(self._stop_loss.Value)
self.StartProtection(
Unit(tp, UnitTypes.Absolute),
Unit(sl, UnitTypes.Absolute)
)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
move = close - open_p
ps = float(self._price_step.Value)
if self.Position == 0:
self._orders_count = 0
if move >= ps:
self.BuyMarket()
self._entry_price = close
self._orders_count = 1
self._is_long = True
elif move <= -ps:
self.SellMarket()
self._entry_price = close
self._orders_count = 1
self._is_long = False
return
if self._orders_count >= self._max_orders.Value:
return
if self._is_long:
if close - self._entry_price >= ps:
self.BuyMarket()
self._entry_price = close
self._orders_count += 1
else:
if self._entry_price - close >= ps:
self.SellMarket()
self._entry_price = close
self._orders_count += 1
def CreateClone(self):
return exp_fishing_strategy()