Стратегия повторяет советник MetaTrader Close_on_PROFIT_or_LOSS_inAccont_Currency. Она отслеживает текущую стоимость подключённого портфеля и, как только equity достигает заданной цели по прибыли или опускается до ограничителя по просадке, отменяет все отложенные заявки и полностью закрывает позиции. Для реализации используется высокоуровневый API StockSharp: подписка на свечи служит «пульсом», CancelActiveOrders() удаляет активные заявки, а ClosePosition() отправляет рыночные ордера на закрытие.
Алгоритм работы
После закрытия каждой контрольной свечи считывается значение Portfolio.CurrentValue.
Если equity больше либо равно параметру Positive Closure, выполняется процедура полного выхода.
Если equity меньше либо равно Negative Closure, запускается та же процедура для ограничения убытков.
При закрытии стратегия отменяет отложенные заявки, выставляет рыночные ордера противоположного направления и завершает работу (аналог ExpertRemove() в оригинале).
Важно: пороговые значения задаются в валюте счёта. Чтобы избежать мгновенного срабатывания при запуске, установите Positive Closure выше текущего equity, а Negative Closure — ниже.
Параметры
Имя
Описание
Значение по умолчанию
PositiveClosureInAccountCurrency
Уровень equity, при превышении которого закрываются все позиции.
0
NegativeClosureInAccountCurrency
Нижний предел equity, при достижении которого позиции ликвидируются.
0
CandleType
Таймфрейм «сердцебиения», на закрытии которого проверяется equity. Для более быстрой реакции выберите меньший интервал.
1 минута
Примечания
При запуске вызывается StartProtection(), повторяя защитный механизм оригинала.
Стратегия работает только с позициями и заявками, которые ведёт сама. Подключайте её к тому портфелю, который нужно защищать.
Отдельный параметр спрэда/проскальзывания не требуется: рыночные заявки StockSharp учитывают специфику коннектора.
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;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Closes all positions when portfolio equity reaches configured profit or loss thresholds.
/// Pending orders are cancelled before liquidating the exposure.
/// </summary>
public class CloseOnProfitOrLossInAccountCurrencyStrategy : Strategy
{
private readonly StrategyParam<decimal> _positiveClosure;
private readonly StrategyParam<decimal> _negativeClosure;
private readonly StrategyParam<DataType> _candleType;
private bool _closeRequested;
private SimpleMovingAverage _smaFast;
private SimpleMovingAverage _smaSlow;
/// <summary>
/// Equity level in account currency that triggers closing all positions when exceeded.
/// </summary>
public decimal PositiveClosureInAccountCurrency
{
get => _positiveClosure.Value;
set => _positiveClosure.Value = value;
}
/// <summary>
/// Equity level in account currency that triggers closing all positions when reached on drawdown.
/// </summary>
public decimal NegativeClosureInAccountCurrency
{
get => _negativeClosure.Value;
set => _negativeClosure.Value = value;
}
/// <summary>
/// Candle type used as a heartbeat to evaluate portfolio equity.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="CloseOnProfitOrLossInAccountCurrencyStrategy"/> class.
/// </summary>
public CloseOnProfitOrLossInAccountCurrencyStrategy()
{
_positiveClosure = Param(nameof(PositiveClosureInAccountCurrency), 0m)
.SetDisplay("Positive Closure", "Equity level that triggers full liquidation", "Risk");
_negativeClosure = Param(nameof(NegativeClosureInAccountCurrency), 0m)
.SetDisplay("Negative Closure", "Equity floor that forces liquidation", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Heartbeat Candle", "Candle type that triggers equity checks", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security == null)
return Array.Empty<(Security, DataType)>();
return new (Security, DataType)[] { (Security, CandleType) };
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Portfolio == null)
throw new InvalidOperationException("Portfolio cannot be null.");
if (Security == null)
throw new InvalidOperationException("Security must be set to subscribe for candles.");
_smaFast = new SimpleMovingAverage { Length = 10 };
_smaSlow = new SimpleMovingAverage { Length = 30 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_smaFast, _smaSlow, ProcessCandleWithIndicators)
.Start();
}
private void ProcessCandleWithIndicators(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (fast > slow && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (fast < slow && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
private void RequestCloseAll(string reason)
{
if (_closeRequested)
return;
_closeRequested = true;
LogInfo(reason);
// Cancel any pending orders to avoid unexpected executions during liquidation.
CancelActiveOrders();
foreach (var position in Positions.ToArray())
{
var value = GetPositionValue(position.Security, Portfolio) ?? 0m;
if (value == 0m)
continue;
// Submit a market order opposite to the current exposure.
ClosePosition(position.Security);
}
// Stop the strategy after sending exit orders, mirroring ExpertRemove behavior.
Stop();
}
}
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 close_on_profit_or_loss_in_account_currency_strategy(Strategy):
"""
SMA crossover strategy with equity-based closure.
Uses fast/slow SMA crossover for entries.
"""
def __init__(self):
super(close_on_profit_or_loss_in_account_currency_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Heartbeat Candle", "Candle type that triggers equity checks", "General")
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(close_on_profit_or_loss_in_account_currency_strategy, self).OnStarted2(time)
sma_fast = SimpleMovingAverage()
sma_fast.Length = 10
sma_slow = SimpleMovingAverage()
sma_slow.Length = 30
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma_fast, sma_slow, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma_fast)
self.DrawIndicator(area, sma_slow)
self.DrawOwnTrades(area)
def on_process(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return close_on_profit_or_loss_in_account_currency_strategy()