Открыть на GitHub

Стратегия Commission Calculator

Общее описание

Commission Calculator Strategy — утилитарная стратегия, повторяющая оригинальный скрипт MetaTrader. Она отправляет одну дискреционную заявку выбранного типа (рыночная, лимитная или стоповая) и рассчитывает брокерскую комиссию по каждой сделке. Все комиссии накапливаются, а при остановке стратегия выводит подробный отчет с начальным балансом, суммарными сборами и скорректированным балансом.

В отличие от классических торговых систем стратегия не нуждается в рыночных данных и индикаторах — её задача состоит в автоматическом учете комиссий при ручной или полуавтоматической торговле.

Логика работы

  1. При запуске стратегия запоминает стартовый баланс портфеля и устанавливает объем для вспомогательных методов (BuyMarket, SellLimit и т. д.).
  2. Если заданы цены входа и защит, активируется StartProtection: дистанции стоп-лосса и тейк-профита рассчитываются в абсолютных ценовых единицах, что повторяет MQL-реализацию.
  3. Выбранный режим исполнения отрабатывается один раз. При некорректных параметрах (например, отсутствует цена входа для лимитной заявки) стратегия фиксирует проблему в журнале и не отправляет заявку.
  4. Каждый MyTrade используется для расчета комиссии по формуле цена × объем × комиссия / 100.
  5. Комиссии суммируются, последняя величина сохраняется отдельно, а при остановке стратегия выводит детальный отчет.

Параметры

Название Значение по умолчанию Описание
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;
	}
}