Commission Calculator Strategy — утилитарная стратегия, повторяющая оригинальный скрипт MetaTrader. Она отправляет одну дискреционную заявку выбранного типа (рыночная, лимитная или стоповая) и рассчитывает брокерскую комиссию по каждой сделке. Все комиссии накапливаются, а при остановке стратегия выводит подробный отчет с начальным балансом, суммарными сборами и скорректированным балансом.
В отличие от классических торговых систем стратегия не нуждается в рыночных данных и индикаторах — её задача состоит в автоматическом учете комиссий при ручной или полуавтоматической торговле.
Логика работы
При запуске стратегия запоминает стартовый баланс портфеля и устанавливает объем для вспомогательных методов (BuyMarket, SellLimit и т. д.).
Если заданы цены входа и защит, активируется StartProtection: дистанции стоп-лосса и тейк-профита рассчитываются в абсолютных ценовых единицах, что повторяет MQL-реализацию.
Выбранный режим исполнения отрабатывается один раз. При некорректных параметрах (например, отсутствует цена входа для лимитной заявки) стратегия фиксирует проблему в журнале и не отправляет заявку.
Каждый MyTrade используется для расчета комиссии по формуле цена × объем × комиссия / 100.
Комиссии суммируются, последняя величина сохраняется отдельно, а при остановке стратегия выводит детальный отчет.
Параметры
Название
Значение по умолчанию
Описание
Quantity
0.001
Объем сделки, который будет использоваться вспомогательными методами отправки заявок.
EntryPrice
31365
Цена для лимитных/стоповых заявок и база для расчета защитных дистанций.
StopLossPrice
31200
Уровень стоп-лосса. Если расстояние не положительно, защита отключается.
TakeProfitPrice
32100
Уровень тейк-профита. Если расстояние не положительно, защита отключается.
CommissionRate
0.04
Размер комиссии в процентах от оборота.
Mode
None
Тип заявки, которую необходимо отправить при старте. Варианты: None, MarketBuy, MarketSell, BuyLimit, SellLimit, BuyStop, SellStop.
Рекомендации
Запускайте стратегию на портфеле, поддерживающем ручные операции. Подписки на данные не требуются.
Убедитесь, что параметр CommissionRate соответствует тарифу брокера, иначе расчеты будут некорректными.
Для отложенных заявок заранее задайте корректную EntryPrice, иначе заявка не будет размещена.
При включенных защитах стратегия использует рыночное закрытие позиции, чтобы максимально точно повторить поведение исходного MQL-кода.
Отчетность
В методе OnStopped в журнал выводится:
начальный баланс (зафиксирован в момент запуска);
суммарная величина комиссий;
итоговый баланс с учетом удержанных комиссий.
Такой подход позволяет быстро проверять тарифы брокера и проводить «что если» расчеты в режиме бэктеста.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Commission Calculator strategy: CCI level crossover.
/// Buys when CCI crosses above -100 (oversold exit), sells when CCI crosses below 100 (overbought exit).
/// </summary>
public class CommissionCalculatorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private readonly StrategyParam<int> _signalCooldownCandles;
private decimal _prevCci;
private int _candlesSinceTrade;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int Period { get => _period.Value; set => _period.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public CommissionCalculatorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_period = Param(nameof(Period), 30)
.SetGreaterThanZero()
.SetDisplay("Period", "CCI period", "Indicators");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrev = false;
var cci = new CommodityChannelIndex { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(cci, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
if (_hasPrev)
{
if (_prevCci < -100 && cciValue >= -100 && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (_prevCci > 100 && cciValue <= 100 && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
_prevCci = cciValue;
_hasPrev = 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 CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class commission_calculator_strategy(Strategy):
def __init__(self):
super(commission_calculator_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._period = self.Param("Period", 30)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 4)
self._prev_cci = 0.0
self._candles_since_trade = 4
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def Period(self):
return self._period.Value
@Period.setter
def Period(self, value):
self._period.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(commission_calculator_strategy, self).OnReseted()
self._prev_cci = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev = False
def OnStarted2(self, time):
super(commission_calculator_strategy, self).OnStarted2(time)
self._prev_cci = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev = False
cci = CommodityChannelIndex()
cci.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._process_candle).Start()
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
cci_val = float(cci_value)
if self._has_prev:
if self._prev_cci < -100 and cci_val >= -100 and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif self._prev_cci > 100 and cci_val <= 100 and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
self._prev_cci = cci_val
self._has_prev = True
def CreateClone(self):
return commission_calculator_strategy()