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>
/// Candle breakout strategy converted from the Alexav SpeedUp M1 expert advisor.
/// Enters in the direction of strong candle bodies and manages exits with optional stop-loss,
/// take-profit, and trailing stop logic.
/// </summary>
public class AlexavSpeedUpM1Strategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<int> _minimumBodySizePips;
private readonly StrategyParam<DataType> _candleType;
private Sides? _currentDirection;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private decimal? _trailingStopDistance;
private decimal? _trailingStepDistance;
/// <summary>
/// Order volume in lots.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Trailing step in pips.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Minimum candle body size required to open a trade, expressed in pips.
/// </summary>
public int MinimumBodySizePips
{
get => _minimumBodySizePips.Value;
set => _minimumBodySizePips.Value = value;
}
/// <summary>
/// Type of candles used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="AlexavSpeedUpM1Strategy"/>.
/// </summary>
public AlexavSpeedUpM1Strategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Position size in lots", "General");
_stopLossPips = Param(nameof(StopLossPips), 30)
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk Management")
.SetOptimize(10, 100, 10);
_takeProfitPips = Param(nameof(TakeProfitPips), 90)
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk Management")
.SetOptimize(30, 180, 30);
_trailingStopPips = Param(nameof(TrailingStopPips), 10)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk Management")
.SetOptimize(5, 30, 5);
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetDisplay("Trailing Step (pips)", "Price movement required to move the trailing stop", "Risk Management")
.SetOptimize(5, 20, 5);
_minimumBodySizePips = Param(nameof(MinimumBodySizePips), 100)
.SetDisplay("Minimum Body (pips)", "Minimum candle body size to trigger entries", "Signal")
.SetOptimize(50, 200, 10);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetPositionState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (TrailingStopPips > 0 && TrailingStepPips == 0)
throw new InvalidOperationException("Trailing step must be greater than zero when trailing stop is enabled.");
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 (_currentDirection != null && Position == 0)
ResetPositionState();
if (_currentDirection != null)
{
if (ManageActivePosition(candle))
return;
}
var pipSize = GetPipSize();
var minimumBody = MinimumBodySizePips <= 0 ? 0m : MinimumBodySizePips * pipSize;
var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);
if (bodySize <= minimumBody)
return;
if (_currentDirection != null)
return;
var direction = candle.ClosePrice >= candle.OpenPrice ? Sides.Buy : Sides.Sell;
OpenPosition(direction, candle.ClosePrice);
}
private bool ManageActivePosition(ICandleMessage candle)
{
if (_currentDirection == null)
return false;
var high = candle.HighPrice;
var low = candle.LowPrice;
var close = candle.ClosePrice;
if (_currentDirection == Sides.Buy)
{
if (_stopPrice is decimal stop && low <= stop)
{
ClosePosition();
return true;
}
if (_takeProfitPrice is decimal take && high >= take)
{
ClosePosition();
return true;
}
UpdateTrailingStopForLong(close);
}
else if (_currentDirection == Sides.Sell)
{
if (_stopPrice is decimal stop && high >= stop)
{
ClosePosition();
return true;
}
if (_takeProfitPrice is decimal take && low <= take)
{
ClosePosition();
return true;
}
UpdateTrailingStopForShort(close);
}
return false;
}
private void OpenPosition(Sides direction, decimal price)
{
if (OrderVolume <= 0)
return;
var desiredPosition = direction == Sides.Buy ? OrderVolume : -OrderVolume;
var difference = desiredPosition - Position;
if (difference > 0)
BuyMarket(difference);
else if (difference < 0)
SellMarket(-difference);
_currentDirection = direction;
_entryPrice = price;
var pipSize = GetPipSize();
_stopPrice = StopLossPips > 0
? direction == Sides.Buy
? price - StopLossPips * pipSize
: price + StopLossPips * pipSize
: null;
_takeProfitPrice = TakeProfitPips > 0
? direction == Sides.Buy
? price + TakeProfitPips * pipSize
: price - TakeProfitPips * pipSize
: null;
if (TrailingStopPips > 0)
{
_trailingStopDistance = TrailingStopPips * pipSize;
_trailingStepDistance = TrailingStepPips * pipSize;
}
else
{
_trailingStopDistance = null;
_trailingStepDistance = null;
}
}
private void ClosePosition()
{
var currentPosition = Position;
if (currentPosition > 0)
SellMarket(currentPosition);
else if (currentPosition < 0)
BuyMarket(-currentPosition);
ResetPositionState();
}
private void UpdateTrailingStopForLong(decimal price)
{
if (_trailingStopDistance is not decimal trailing || _trailingStepDistance is not decimal step)
return;
if (price - _entryPrice < trailing + step)
return;
var candidate = price - trailing;
if (_stopPrice is decimal stop && stop >= candidate - step)
return;
_stopPrice = candidate;
}
private void UpdateTrailingStopForShort(decimal price)
{
if (_trailingStopDistance is not decimal trailing || _trailingStepDistance is not decimal step)
return;
if (_entryPrice - price < trailing + step)
return;
var candidate = price + trailing;
if (_stopPrice is decimal stop && stop <= candidate + step)
return;
_stopPrice = candidate;
}
private void ResetPositionState()
{
_currentDirection = null;
_entryPrice = 0m;
_stopPrice = null;
_takeProfitPrice = null;
_trailingStopDistance = null;
_trailingStepDistance = null;
}
private decimal GetPipSize()
{
var step = Security?.PriceStep ?? 0.0001m;
var decimals = Security?.Decimals ?? 5;
if (decimals == 3 || decimals == 5)
return step * 10m;
return step;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import CandleStates, Sides
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class alexav_speed_up_m1_strategy(Strategy):
"""
Candle breakout strategy converted from the Alexav SpeedUp M1 expert advisor.
Enters in the direction of strong candle bodies and manages exits with
stop-loss, take-profit, and trailing stop logic.
"""
def __init__(self):
super(alexav_speed_up_m1_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Order Volume", "Position size in lots", "General")
self._stop_loss_pips = self.Param("StopLossPips", 30) \
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk Management")
self._take_profit_pips = self.Param("TakeProfitPips", 90) \
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk Management")
self._trailing_stop_pips = self.Param("TrailingStopPips", 10) \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk Management")
self._trailing_step_pips = self.Param("TrailingStepPips", 5) \
.SetDisplay("Trailing Step (pips)", "Price movement required to move the trailing stop", "Risk Management")
self._minimum_body_size_pips = self.Param("MinimumBodySizePips", 100) \
.SetDisplay("Minimum Body (pips)", "Minimum candle body size to trigger entries", "Signal")
self._candle_type = self.Param("CandleType", tf(240)) \
.SetDisplay("Candle Type", "Type of candles for analysis", "General")
self._current_direction = None
self._entry_price = 0.0
self._stop_price = None
self._take_profit_price = None
self._trailing_stop_distance = None
self._trailing_step_distance = None
@property
def OrderVolume(self):
return self._order_volume.Value
@OrderVolume.setter
def OrderVolume(self, value):
self._order_volume.Value = value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@TakeProfitPips.setter
def TakeProfitPips(self, value):
self._take_profit_pips.Value = value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@TrailingStopPips.setter
def TrailingStopPips(self, value):
self._trailing_stop_pips.Value = value
@property
def TrailingStepPips(self):
return self._trailing_step_pips.Value
@TrailingStepPips.setter
def TrailingStepPips(self, value):
self._trailing_step_pips.Value = value
@property
def MinimumBodySizePips(self):
return self._minimum_body_size_pips.Value
@MinimumBodySizePips.setter
def MinimumBodySizePips(self, value):
self._minimum_body_size_pips.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(alexav_speed_up_m1_strategy, self).OnReseted()
self._reset_position_state()
def OnStarted2(self, time):
super(alexav_speed_up_m1_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._current_direction is not None and self.Position == 0:
self._reset_position_state()
if self._current_direction is not None:
if self._manage_active_position(candle):
return
pip_size = self._get_pip_size()
minimum_body = 0.0 if self.MinimumBodySizePips <= 0 else self.MinimumBodySizePips * pip_size
body_size = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
if body_size <= minimum_body:
return
if self._current_direction is not None:
return
direction = Sides.Buy if float(candle.ClosePrice) >= float(candle.OpenPrice) else Sides.Sell
self._open_position(direction, float(candle.ClosePrice))
def _manage_active_position(self, candle):
if self._current_direction is None:
return False
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._current_direction == Sides.Buy:
if self._stop_price is not None and low <= self._stop_price:
self._close_position()
return True
if self._take_profit_price is not None and high >= self._take_profit_price:
self._close_position()
return True
self._update_trailing_stop_for_long(close)
elif self._current_direction == Sides.Sell:
if self._stop_price is not None and high >= self._stop_price:
self._close_position()
return True
if self._take_profit_price is not None and low <= self._take_profit_price:
self._close_position()
return True
self._update_trailing_stop_for_short(close)
return False
def _open_position(self, direction, price):
if self.OrderVolume <= 0:
return
if direction == Sides.Buy:
self.BuyMarket()
else:
self.SellMarket()
self._current_direction = direction
self._entry_price = price
pip_size = self._get_pip_size()
if self.StopLossPips > 0:
if direction == Sides.Buy:
self._stop_price = price - self.StopLossPips * pip_size
else:
self._stop_price = price + self.StopLossPips * pip_size
else:
self._stop_price = None
if self.TakeProfitPips > 0:
if direction == Sides.Buy:
self._take_profit_price = price + self.TakeProfitPips * pip_size
else:
self._take_profit_price = price - self.TakeProfitPips * pip_size
else:
self._take_profit_price = None
if self.TrailingStopPips > 0:
self._trailing_stop_distance = self.TrailingStopPips * pip_size
self._trailing_step_distance = self.TrailingStepPips * pip_size
else:
self._trailing_stop_distance = None
self._trailing_step_distance = None
def _close_position(self):
current_position = self.Position
if current_position > 0:
self.SellMarket(current_position)
elif current_position < 0:
self.BuyMarket(-current_position)
self._reset_position_state()
def _update_trailing_stop_for_long(self, price):
if self._trailing_stop_distance is None or self._trailing_step_distance is None:
return
if price - self._entry_price < self._trailing_stop_distance + self._trailing_step_distance:
return
candidate = price - self._trailing_stop_distance
if self._stop_price is not None and self._stop_price >= candidate - self._trailing_step_distance:
return
self._stop_price = candidate
def _update_trailing_stop_for_short(self, price):
if self._trailing_stop_distance is None or self._trailing_step_distance is None:
return
if self._entry_price - price < self._trailing_stop_distance + self._trailing_step_distance:
return
candidate = price + self._trailing_stop_distance
if self._stop_price is not None and self._stop_price <= candidate + self._trailing_step_distance:
return
self._stop_price = candidate
def _reset_position_state(self):
self._current_direction = None
self._entry_price = 0.0
self._stop_price = None
self._take_profit_price = None
self._trailing_stop_distance = None
self._trailing_step_distance = None
def _get_pip_size(self):
return 0.0001
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return alexav_speed_up_m1_strategy()