Стратегия «Утилита Basket Close» повторяет работу советника MetaTrader «Basket Close 2». Она постоянно отслеживает плавающий финансовый результат всех открытых позиций в портфеле и при достижении заданной цели по прибыли или лимита по убытку отправляет рыночные заявки, чтобы полностью закрыть все позиции по каждому инструменту. Дополнительно можно автоматически открывать тестовую сделку, если портфель пуст, что удобно при тестировании защиты капитала.
Параметры
Название
Описание
LossMode
Выбор метода контроля убытка: по проценту или по денежной сумме.
LossPercentage
Порог просадки в процентах (модуль значения), при достижении которого закрываются все позиции, если выбран режим Percentage.
LossCurrency
Плавающий убыток в валюте счёта, который запускает закрытие при режиме Currency.
ProfitMode
Выбор метода фиксации прибыли: по проценту или по денежной сумме.
ProfitPercentage
Процент прибыли, при достижении которого закрываются все позиции в режиме Percentage.
ProfitCurrency
Плавающая прибыль в валюте счёта, при достижении которой закрываются все позиции в режиме Currency.
CandleType
Таймфрейм, по закрытию которого выполняются периодические проверки PnL.
EnableTestOrders
При включении автоматически отправляет рыночную покупку, когда открытых позиций нет.
TestOrderVolume
Объём ордера, используемый тестовой сделкой.
Логика работы
Подписаться на выбранный таймфрейм и выполнять проверку только после закрытия свечи, как это делал исходный советник.
Рассчитать суммарный плавающий PnL всех открытых позиций. Если портфель предоставляет агрегированное значение, используется оно, иначе значения позиций суммируются вручную.
Рассчитать процентное изменение относительно баланса счёта, зафиксированного в момент запуска стратегии.
Запустить защиту от убытка, когда плавающий результат достигает отрицательного порога. Запустить фиксацию прибыли, когда плавающий PnL или процент прибыли достигает заданной цели.
После срабатывания условий продолжать отправлять рыночные заявки, пока каждая позиция в портфеле (включая дочерние стратегии) не будет полностью закрыта.
Если включены тестовые ордера, после обнуления позиций снова отправить тестовую сделку, чтобы повторно проверить работу защиты.
Примечания
В оригинальном советнике информация выводилась на график, в StockSharp ключевые показатели фиксируются через LogInfo.
Комиссии и свопы, которые в MQL добавлялись вручную, уже входят в плавающий PnL портфеля или отдельных позиций.
Пороговые значения по проценту используют баланс, сохранённый на старте. При значительном изменении капитала корректируйте параметры.
Тестовый ордер выставляется повторно каждый раз, когда все позиции были закрыты защитными условиями.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Basket Close strategy: EMA trend following with profit/loss close thresholds.
/// Enters on EMA direction, closes when accumulated P&L hits target or stop.
/// </summary>
public class BasketCloseStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private decimal _entryPrice;
private bool _wasBullish;
private bool _hasPrevSignal;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public BasketCloseStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_emaPeriod = Param(nameof(EmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_wasBullish = false;
_hasPrevSignal = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_hasPrevSignal = false;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
var isBullish = close > emaValue;
if (_hasPrevSignal && isBullish != _wasBullish)
{
if (isBullish && Position <= 0)
{
BuyMarket();
_entryPrice = close;
}
else if (!isBullish && Position >= 0)
{
SellMarket();
_entryPrice = close;
}
}
_wasBullish = isBullish;
_hasPrevSignal = true;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class basket_close_strategy(Strategy):
def __init__(self):
super(basket_close_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._ema_period = self.Param("EmaPeriod", 50) \
.SetDisplay("EMA Period", "EMA period", "Indicators")
self._entry_price = 0.0
self._was_bullish = False
self._has_prev_signal = False
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
@property
def ema_period(self):
return self._ema_period.Value
@ema_period.setter
def ema_period(self, value):
self._ema_period.Value = value
def OnReseted(self):
super(basket_close_strategy, self).OnReseted()
self._entry_price = 0.0
self._was_bullish = False
self._has_prev_signal = False
def OnStarted2(self, time):
super(basket_close_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._has_prev_signal = False
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
is_bullish = close > ema_value
if self._has_prev_signal and is_bullish != self._was_bullish:
if is_bullish and self.Position <= 0:
self.BuyMarket()
self._entry_price = float(close)
elif not is_bullish and self.Position >= 0:
self.SellMarket()
self._entry_price = float(close)
self._was_bullish = is_bullish
self._has_prev_signal = True
def CreateClone(self):
return basket_close_strategy()