跟踪止损与止盈策略
概述
Trailing Stop And Take Strategy 是 MQL/19963 中 MetaTrader 专家顾问的 StockSharp 版本。策略专注于头寸管理:开仓后立即设置初始止损与止盈,然后在价格波动时动态跟踪两者。跟踪调整遵循可配置的最小步长、保本保护,并可选择禁止在亏损区间内移动。
策略基于单一标的的收盘 K 线运行。当没有持仓时,它会按照最近一根 K 线的实体方向开仓(阳线买入、阴线卖出),以复现原 MQL 脚本的测试行为,从而为跟踪模块提供持续的持仓样本。
工作流程
- 订阅配置的 K 线类型,仅处理收盘的 K 线。
- 空仓时根据 K 线方向开多或开空(遵循仓位类型过滤器)。
- 新持仓建立后,使用
InitialStopLossPoints/InitialTakeProfitPoints设置初始止损与止盈;若为零,则改用对应的跟踪距离。 - 每根 K 线收盘时计算新的跟踪价格:
- 价格朝有利方向移动超过跟踪步长后才推动止损靠近价格。
- 价格向不利方向回调超过跟踪步长时才收紧止盈。
- 当
AllowTrailingLoss关闭时,保本阈值阻止止损/止盈回到亏损区间。
- 当价格穿越任何跟踪止损或止盈时,使用市价单离场并重置所有记录的价格。
跟踪逻辑
多头
- 初始止损至少要距离开仓价
SpreadMultiplier * PriceStep。 - 初始止盈同样至少高于开仓价这一最小距离。
- 跟踪止损按
TrailingStopLossPoints与收盘价保持距离,在满足步长与保本条件后上移。 - 跟踪止盈在价格回调时下移,且在禁止亏损跟踪时不会低于保本线。
空头
- 初始止损位于开仓价上方,距离不少于乘以点差倍数的最小距离。
- 初始止盈位于开仓价下方,也遵循相同的距离规则。
- 跟踪止损随价格下跌而下移,但若禁用亏损跟踪则不会高于保本线。
- 跟踪止盈在价格反弹时上移,并在需要时限制在保本价位。
参数
| 参数 | 说明 |
|---|---|
CandleType |
用于计算的 K 线周期。 |
Volume |
开仓与平仓的默认数量。 |
PositionType |
仅管理多头、空头或同时管理两者。 |
InitialStopLossPoints |
初始止损距离(若为零则使用跟踪止损距离)。 |
InitialTakeProfitPoints |
初始止盈距离(若为零则使用跟踪止盈距离)。 |
TrailingStopLossPoints |
跟踪止损与价格之间的距离。 |
TrailingTakeProfitPoints |
跟踪止盈与价格之间的距离。 |
TrailingStepPoints |
更新止损或止盈所需的最小价格变动。 |
AllowTrailingLoss |
是否允许在亏损区间内移动止损/止盈。 |
BreakevenPoints |
用于计算保本价的点数偏移。 |
SpreadMultiplier |
近似模拟 MQL StopLevel 的最小距离倍数。 |
说明
- 触发止损或止盈时使用市价单离场,简单直观,同时保留原脚本修改仓位的风格。
SpreadMultiplier近似 MQL 中的点差限制,可根据交易所或经纪商调整。- 根据要求,本策略仅提供 C# 实现,不包含 Python 版本。
- 如需与自定义信号结合,可关闭内置入场逻辑并从外部注入订单,仅使用该跟踪模块。
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>
/// Strategy that manages trailing stop-loss and take-profit levels similar to the original MQL Expert Advisor.
/// </summary>
public class TrailingStopAndTakeStrategy : Strategy
{
public enum TrailingPositionTypes
{
All,
Long,
Short,
}
private readonly StrategyParam<decimal> _epsilon;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<TrailingPositionTypes> _positionType;
private readonly StrategyParam<decimal> _initialStopLossPoints;
private readonly StrategyParam<decimal> _initialTakeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopLossPoints;
private readonly StrategyParam<decimal> _trailingTakeProfitPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<bool> _allowTrailingLoss;
private readonly StrategyParam<decimal> _breakevenPoints;
private readonly StrategyParam<int> _spreadMultiplier;
private decimal _priceStep;
private decimal _previousPosition;
private decimal? _longStop;
private decimal? _longTake;
private decimal? _shortStop;
private decimal? _shortTake;
private decimal _entryPrice;
/// <summary>
/// Initializes a new instance of the <see cref="TrailingStopAndTakeStrategy"/>.
/// </summary>
public TrailingStopAndTakeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Candle Type", "Candle aggregation used for trailing decisions", "General");
_positionType = Param(nameof(PositionType), TrailingPositionTypes.All)
.SetDisplay("Position Filter", "Positions managed by the trailing engine", "Trading");
_initialStopLossPoints = Param(nameof(InitialStopLossPoints), 400m)
.SetRange(0m, 10000m)
.SetDisplay("Initial Stop", "Initial stop-loss size in price points", "Risk")
;
_initialTakeProfitPoints = Param(nameof(InitialTakeProfitPoints), 400m)
.SetRange(0m, 10000m)
.SetDisplay("Initial Take", "Initial take-profit size in price points", "Risk")
;
_trailingStopLossPoints = Param(nameof(TrailingStopLossPoints), 200m)
.SetRange(0m, 10000m)
.SetDisplay("Trailing Stop", "Trailing stop distance in price points", "Risk")
;
_trailingTakeProfitPoints = Param(nameof(TrailingTakeProfitPoints), 200m)
.SetRange(0m, 10000m)
.SetDisplay("Trailing Take", "Trailing take-profit distance in price points", "Risk")
;
_trailingStepPoints = Param(nameof(TrailingStepPoints), 10m)
.SetRange(0m, 1000m)
.SetDisplay("Trailing Step", "Minimum movement required before adjusting targets", "Risk");
_epsilon = Param(nameof(Epsilon), 0.0000001m)
.SetGreaterThanZero()
.SetDisplay("Trailing Epsilon", "Minimum trailing step size", "Risk");
_allowTrailingLoss = Param(nameof(AllowTrailingLoss), false)
.SetDisplay("Trail In Loss", "Allow trailing while position is not yet profitable", "Risk");
_breakevenPoints = Param(nameof(BreakevenPoints), 6m)
.SetRange(0m, 1000m)
.SetDisplay("Breakeven Points", "Profit offset used for breakeven protection", "Risk");
_spreadMultiplier = Param(nameof(SpreadMultiplier), 2)
.SetRange(1, 20)
.SetDisplay("Spread Multiplier", "Multiplier applied to minimal stop distance", "Execution");
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Position filter managed by the trailing logic.
/// </summary>
public TrailingPositionTypes PositionType
{
get => _positionType.Value;
set => _positionType.Value = value;
}
/// <summary>
/// Initial stop-loss size expressed in price points.
/// </summary>
public decimal InitialStopLossPoints
{
get => _initialStopLossPoints.Value;
set => _initialStopLossPoints.Value = value;
}
/// <summary>
/// Initial take-profit size expressed in price points.
/// </summary>
public decimal InitialTakeProfitPoints
{
get => _initialTakeProfitPoints.Value;
set => _initialTakeProfitPoints.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in price points.
/// </summary>
public decimal TrailingStopLossPoints
{
get => _trailingStopLossPoints.Value;
set => _trailingStopLossPoints.Value = value;
}
/// <summary>
/// Trailing take-profit distance expressed in price points.
/// </summary>
public decimal TrailingTakeProfitPoints
{
get => _trailingTakeProfitPoints.Value;
set => _trailingTakeProfitPoints.Value = value;
}
/// <summary>
/// Minimum movement required before stops or targets are updated.
/// </summary>
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
/// <summary>
/// Minimum trailing step size used as a floor.
/// </summary>
public decimal Epsilon
{
get => _epsilon.Value;
set => _epsilon.Value = value;
}
/// <summary>
/// Enables trailing adjustments while the position remains in the loss zone.
/// </summary>
public bool AllowTrailingLoss
{
get => _allowTrailingLoss.Value;
set => _allowTrailingLoss.Value = value;
}
/// <summary>
/// Profit offset in points used to define the breakeven level.
/// </summary>
public decimal BreakevenPoints
{
get => _breakevenPoints.Value;
set => _breakevenPoints.Value = value;
}
/// <summary>
/// Multiplier applied to the minimal stop distance approximation.
/// </summary>
public int SpreadMultiplier
{
get => _spreadMultiplier.Value;
set => _spreadMultiplier.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 1m;
if (_priceStep <= 0m)
_priceStep = 1m;
_previousPosition = 0m;
ResetLevels();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_priceStep = 0m;
_previousPosition = 0m;
_entryPrice = 0m;
ResetLevels();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Handle long positions first.
if (Position > 0m)
{
if (PositionType == TrailingPositionTypes.Short)
{
ResetLongLevels();
}
else
{
if (_previousPosition <= 0m)
ResetShortLevels();
EnsureLongInitialized();
UpdateLongTrailing(candle);
ManageLongExits(candle);
}
}
else if (Position < 0m)
{
if (PositionType == TrailingPositionTypes.Long)
{
ResetShortLevels();
}
else
{
if (_previousPosition >= 0m)
ResetLongLevels();
EnsureShortInitialized();
UpdateShortTrailing(candle);
ManageShortExits(candle);
}
}
else
{
ResetLevels();
}
// Try to open a new position once flat.
TryEnter(candle);
_previousPosition = Position;
}
private void TryEnter(ICandleMessage candle)
{
if (Position != 0m || Volume <= 0m)
return;
// Simple directional entry mirroring the tester behavior from the MQL script.
if (PositionType == TrailingPositionTypes.Long)
{
if (candle.ClosePrice > candle.OpenPrice)
BuyMarket(Volume);
}
else if (PositionType == TrailingPositionTypes.Short)
{
if (candle.ClosePrice < candle.OpenPrice)
SellMarket(Volume);
}
else
{
if (candle.ClosePrice > candle.OpenPrice)
BuyMarket(Volume);
else if (candle.ClosePrice < candle.OpenPrice)
SellMarket(Volume);
}
}
private void EnsureLongInitialized()
{
if (Position <= 0m)
return;
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
var minDistance = GetMinStopDistance();
if (_longStop == null)
{
var points = InitialStopLossPoints > 0m
? InitialStopLossPoints
: TrailingStopLossPoints > 0m ? TrailingStopLossPoints : 0m;
if (points > 0m)
{
var candidate = entryPrice - points * _priceStep;
var minAllowed = entryPrice - minDistance;
_longStop = Math.Min(candidate, minAllowed);
}
}
if (_longTake == null)
{
var points = InitialTakeProfitPoints > 0m
? InitialTakeProfitPoints
: TrailingTakeProfitPoints > 0m ? TrailingTakeProfitPoints : 0m;
if (points > 0m)
{
var candidate = entryPrice + points * _priceStep;
var minAllowed = entryPrice + minDistance;
_longTake = Math.Max(candidate, minAllowed);
}
}
}
private void EnsureShortInitialized()
{
if (Position >= 0m)
return;
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
var minDistance = GetMinStopDistance();
if (_shortStop == null)
{
var points = InitialStopLossPoints > 0m
? InitialStopLossPoints
: TrailingStopLossPoints > 0m ? TrailingStopLossPoints : 0m;
if (points > 0m)
{
var candidate = entryPrice + points * _priceStep;
var minAllowed = entryPrice + minDistance;
_shortStop = Math.Max(candidate, minAllowed);
}
}
if (_shortTake == null)
{
var points = InitialTakeProfitPoints > 0m
? InitialTakeProfitPoints
: TrailingTakeProfitPoints > 0m ? TrailingTakeProfitPoints : 0m;
if (points > 0m)
{
var candidate = entryPrice - points * _priceStep;
var minAllowed = entryPrice - minDistance;
_shortTake = Math.Min(candidate, minAllowed);
}
}
}
private void UpdateLongTrailing(ICandleMessage candle)
{
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
var breakeven = entryPrice + BreakevenPoints * _priceStep;
var trailingStep = Math.Max(TrailingStepPoints * _priceStep, Epsilon);
var minDistance = GetMinStopDistance();
if (TrailingStopLossPoints > 0m)
{
var candidate = candle.ClosePrice - TrailingStopLossPoints * _priceStep;
var minAllowed = candle.ClosePrice - minDistance;
var newStop = Math.Min(candidate, minAllowed);
if (!AllowTrailingLoss && newStop < breakeven)
{
// Skip moving the stop into the loss area when disabled.
}
else if (_longStop == null || newStop > _longStop.Value + trailingStep)
{
_longStop = newStop;
}
}
if (TrailingTakeProfitPoints > 0m)
{
var candidate = candle.ClosePrice + TrailingTakeProfitPoints * _priceStep;
var minAllowed = candle.ClosePrice + minDistance;
var newTake = Math.Max(candidate, minAllowed);
if (!AllowTrailingLoss && newTake < breakeven)
newTake = breakeven;
if (_longTake == null || newTake < _longTake.Value - trailingStep)
_longTake = newTake;
}
}
private void UpdateShortTrailing(ICandleMessage candle)
{
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
var breakeven = entryPrice - BreakevenPoints * _priceStep;
var trailingStep = Math.Max(TrailingStepPoints * _priceStep, Epsilon);
var minDistance = GetMinStopDistance();
if (TrailingStopLossPoints > 0m)
{
var candidate = candle.ClosePrice + TrailingStopLossPoints * _priceStep;
var minAllowed = candle.ClosePrice + minDistance;
var newStop = Math.Max(candidate, minAllowed);
if (!AllowTrailingLoss && newStop > breakeven)
{
// Skip moving the stop into the loss area when disabled.
}
else if (_shortStop == null || newStop < _shortStop.Value - trailingStep)
{
_shortStop = newStop;
}
}
if (TrailingTakeProfitPoints > 0m)
{
var candidate = candle.ClosePrice - TrailingTakeProfitPoints * _priceStep;
var minAllowed = candle.ClosePrice - minDistance;
var newTake = Math.Min(candidate, minAllowed);
if (!AllowTrailingLoss && newTake > breakeven)
newTake = breakeven;
if (_shortTake == null || newTake > _shortTake.Value + trailingStep)
_shortTake = newTake;
}
}
private void ManageLongExits(ICandleMessage candle)
{
if (_longStop.HasValue && candle.LowPrice <= _longStop.Value)
{
SellMarket(Position);
ResetLongLevels();
return;
}
if (_longTake.HasValue && candle.HighPrice >= _longTake.Value)
{
SellMarket(Position);
ResetLongLevels();
}
}
private void ManageShortExits(ICandleMessage candle)
{
if (_shortStop.HasValue && candle.HighPrice >= _shortStop.Value)
{
BuyMarket(Math.Abs(Position));
ResetShortLevels();
return;
}
if (_shortTake.HasValue && candle.LowPrice <= _shortTake.Value)
{
BuyMarket(Math.Abs(Position));
ResetShortLevels();
}
}
private decimal GetMinStopDistance()
{
var multiplier = SpreadMultiplier < 1 ? 1 : SpreadMultiplier;
return _priceStep * multiplier;
}
private void ResetLevels()
{
ResetLongLevels();
ResetShortLevels();
}
private void ResetLongLevels()
{
_longStop = null;
_longTake = null;
}
private void ResetShortLevels()
{
_shortStop = null;
_shortTake = null;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Trade == null) return;
if (Position != 0 && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0)
_entryPrice = 0m;
}
}
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 DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class trailing_stop_and_take_strategy(Strategy):
POS_ALL = 0
POS_LONG = 1
POS_SHORT = 2
def __init__(self):
super(trailing_stop_and_take_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1)))
self._position_type = self.Param("PositionType", self.POS_ALL)
self._initial_stop_loss_points = self.Param("InitialStopLossPoints", 400.0)
self._initial_take_profit_points = self.Param("InitialTakeProfitPoints", 400.0)
self._trailing_stop_loss_points = self.Param("TrailingStopLossPoints", 200.0)
self._trailing_take_profit_points = self.Param("TrailingTakeProfitPoints", 200.0)
self._trailing_step_points = self.Param("TrailingStepPoints", 10.0)
self._epsilon = self.Param("Epsilon", 0.0000001)
self._allow_trailing_loss = self.Param("AllowTrailingLoss", False)
self._breakeven_points = self.Param("BreakevenPoints", 6.0)
self._spread_multiplier = self.Param("SpreadMultiplier", 2)
self._price_step = 1.0
self._previous_position = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def PositionType(self):
return self._position_type.Value
@property
def InitialStopLossPoints(self):
return self._initial_stop_loss_points.Value
@property
def InitialTakeProfitPoints(self):
return self._initial_take_profit_points.Value
@property
def TrailingStopLossPoints(self):
return self._trailing_stop_loss_points.Value
@property
def TrailingTakeProfitPoints(self):
return self._trailing_take_profit_points.Value
@property
def TrailingStepPoints(self):
return self._trailing_step_points.Value
@property
def Epsilon(self):
return self._epsilon.Value
@property
def AllowTrailingLoss(self):
return self._allow_trailing_loss.Value
@property
def BreakevenPoints(self):
return self._breakeven_points.Value
@property
def SpreadMultiplier(self):
return self._spread_multiplier.Value
def OnStarted2(self, time):
super(trailing_stop_and_take_strategy, self).OnStarted2(time)
sec = self.Security
self._price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
self._previous_position = 0.0
self._reset_levels()
subscription = self.SubscribeCandles(self.CandleType)
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
pos = float(self.Position)
if pos > 0:
if self.PositionType == self.POS_SHORT:
self._reset_long_levels()
else:
if self._previous_position <= 0:
self._reset_short_levels()
self._ensure_long_initialized()
self._update_long_trailing(candle)
self._manage_long_exits(candle)
elif pos < 0:
if self.PositionType == self.POS_LONG:
self._reset_short_levels()
else:
if self._previous_position >= 0:
self._reset_long_levels()
self._ensure_short_initialized()
self._update_short_trailing(candle)
self._manage_short_exits(candle)
else:
self._reset_levels()
self._entry_price = 0.0
self._try_enter(candle)
self._previous_position = float(self.Position)
def _try_enter(self, candle):
pos = float(self.Position)
vol = float(self.Volume)
if pos != 0 or vol <= 0:
return
close = float(candle.ClosePrice)
if self.PositionType == self.POS_LONG:
if close > float(candle.OpenPrice):
self.BuyMarket(vol)
self._entry_price = close
elif self.PositionType == self.POS_SHORT:
if close < float(candle.OpenPrice):
self.SellMarket(vol)
self._entry_price = close
else:
if close > float(candle.OpenPrice):
self.BuyMarket(vol)
self._entry_price = close
elif close < float(candle.OpenPrice):
self.SellMarket(vol)
self._entry_price = close
def _ensure_long_initialized(self):
if float(self.Position) <= 0:
return
entry = self._entry_price
if entry <= 0:
return
min_dist = self._get_min_stop_distance()
if self._long_stop is None:
pts = float(self.InitialStopLossPoints) if float(self.InitialStopLossPoints) > 0 else (float(self.TrailingStopLossPoints) if float(self.TrailingStopLossPoints) > 0 else 0.0)
if pts > 0:
candidate = entry - pts * self._price_step
min_allowed = entry - min_dist
self._long_stop = min(candidate, min_allowed)
if self._long_take is None:
pts = float(self.InitialTakeProfitPoints) if float(self.InitialTakeProfitPoints) > 0 else (float(self.TrailingTakeProfitPoints) if float(self.TrailingTakeProfitPoints) > 0 else 0.0)
if pts > 0:
candidate = entry + pts * self._price_step
min_allowed = entry + min_dist
self._long_take = max(candidate, min_allowed)
def _ensure_short_initialized(self):
if float(self.Position) >= 0:
return
entry = self._entry_price
if entry <= 0:
return
min_dist = self._get_min_stop_distance()
if self._short_stop is None:
pts = float(self.InitialStopLossPoints) if float(self.InitialStopLossPoints) > 0 else (float(self.TrailingStopLossPoints) if float(self.TrailingStopLossPoints) > 0 else 0.0)
if pts > 0:
candidate = entry + pts * self._price_step
min_allowed = entry + min_dist
self._short_stop = max(candidate, min_allowed)
if self._short_take is None:
pts = float(self.InitialTakeProfitPoints) if float(self.InitialTakeProfitPoints) > 0 else (float(self.TrailingTakeProfitPoints) if float(self.TrailingTakeProfitPoints) > 0 else 0.0)
if pts > 0:
candidate = entry - pts * self._price_step
min_allowed = entry - min_dist
self._short_take = min(candidate, min_allowed)
def _update_long_trailing(self, candle):
entry = self._entry_price
if entry <= 0:
return
breakeven = entry + float(self.BreakevenPoints) * self._price_step
trailing_step = max(float(self.TrailingStepPoints) * self._price_step, float(self.Epsilon))
min_dist = self._get_min_stop_distance()
if float(self.TrailingStopLossPoints) > 0:
candidate = float(candle.ClosePrice) - float(self.TrailingStopLossPoints) * self._price_step
min_allowed = float(candle.ClosePrice) - min_dist
new_stop = min(candidate, min_allowed)
if not self.AllowTrailingLoss and new_stop < breakeven:
pass
elif self._long_stop is None or new_stop > self._long_stop + trailing_step:
self._long_stop = new_stop
if float(self.TrailingTakeProfitPoints) > 0:
candidate = float(candle.ClosePrice) + float(self.TrailingTakeProfitPoints) * self._price_step
min_allowed = float(candle.ClosePrice) + min_dist
new_take = max(candidate, min_allowed)
if not self.AllowTrailingLoss and new_take < breakeven:
new_take = breakeven
if self._long_take is None or new_take < self._long_take - trailing_step:
self._long_take = new_take
def _update_short_trailing(self, candle):
entry = self._entry_price
if entry <= 0:
return
breakeven = entry - float(self.BreakevenPoints) * self._price_step
trailing_step = max(float(self.TrailingStepPoints) * self._price_step, float(self.Epsilon))
min_dist = self._get_min_stop_distance()
if float(self.TrailingStopLossPoints) > 0:
candidate = float(candle.ClosePrice) + float(self.TrailingStopLossPoints) * self._price_step
min_allowed = float(candle.ClosePrice) + min_dist
new_stop = max(candidate, min_allowed)
if not self.AllowTrailingLoss and new_stop > breakeven:
pass
elif self._short_stop is None or new_stop < self._short_stop - trailing_step:
self._short_stop = new_stop
if float(self.TrailingTakeProfitPoints) > 0:
candidate = float(candle.ClosePrice) - float(self.TrailingTakeProfitPoints) * self._price_step
min_allowed = float(candle.ClosePrice) - min_dist
new_take = min(candidate, min_allowed)
if not self.AllowTrailingLoss and new_take > breakeven:
new_take = breakeven
if self._short_take is None or new_take > self._short_take + trailing_step:
self._short_take = new_take
def _manage_long_exits(self, candle):
if self._long_stop is not None and float(candle.LowPrice) <= self._long_stop:
self.SellMarket(float(self.Position))
self._reset_long_levels()
return
if self._long_take is not None and float(candle.HighPrice) >= self._long_take:
self.SellMarket(float(self.Position))
self._reset_long_levels()
def _manage_short_exits(self, candle):
if self._short_stop is not None and float(candle.HighPrice) >= self._short_stop:
self.BuyMarket(abs(float(self.Position)))
self._reset_short_levels()
return
if self._short_take is not None and float(candle.LowPrice) <= self._short_take:
self.BuyMarket(abs(float(self.Position)))
self._reset_short_levels()
def _get_min_stop_distance(self):
mult = self.SpreadMultiplier if self.SpreadMultiplier >= 1 else 1
return self._price_step * mult
def _reset_levels(self):
self._reset_long_levels()
self._reset_short_levels()
def _reset_long_levels(self):
self._long_stop = None
self._long_take = None
def _reset_short_levels(self):
self._short_stop = None
self._short_take = None
def OnOwnTradeReceived(self, trade):
super(trailing_stop_and_take_strategy, self).OnOwnTradeReceived(trade)
if trade is None or trade.Trade is None:
return
pos = float(self.Position)
if pos != 0 and self._entry_price == 0:
self._entry_price = float(trade.Trade.Price)
if pos == 0:
self._entry_price = 0.0
def OnReseted(self):
super(trailing_stop_and_take_strategy, self).OnReseted()
self._price_step = 0.0
self._previous_position = 0.0
self._entry_price = 0.0
self._reset_levels()
def CreateClone(self):
return trailing_stop_and_take_strategy()