在 GitHub 上查看

Commission Calculator 策略

概述

Commission Calculator Strategy 是一款实用型策略,按照原始的 MetaTrader 脚本实现。策略在启动时发送一次指定类型的订单(市价、限价或止损),并根据每笔成交计算经纪商佣金。所有佣金都会累加,在策略停止时输出包含初始余额、总费用以及扣费后余额的完整报告。

与常规信号策略不同,本策略不依赖市场数据或技术指标,专注于在手动或半自动交易流程中自动统计费用。

运行流程

  1. 启动时记录组合的初始余额,并设置辅助下单方法所使用的交易量。
  2. 若同时提供入场价与防护价位,则调用 StartProtection。止损和止盈距离按绝对价格计算,最大程度地贴合 MQL 行为。
  3. 选定的下单模式只执行一次。若参数不合法(如限价单缺少入场价),策略会记录日志并跳过下单。
  4. 每次收到 MyTrade(自有成交)时,按照 价格 × 成交量 × 佣金率 / 100 计算手续费。
  5. 所有手续费累计保存,并在停止时连同最新一笔佣金一起输出详细摘要。

参数

名称 默认值 说明
Quantity 0.001 通过 BuyMarketSellLimit 等辅助方法发送的成交量。
EntryPrice 31365 用于限价/止损订单的价格,同时也是计算保护距离的基准。
StopLossPrice 31200 止损价位。当距离不为正时,不启用止损保护。
TakeProfitPrice 32100 止盈价位。当距离不为正时,不启用止盈保护。
CommissionRate 0.04 佣金率,占成交名义金额的百分比。
Mode None 启动时执行的订单类型。可选:NoneMarketBuyMarketSellBuyLimitSellLimitBuyStopSellStop

使用建议

  • 请在支持手动交易的投资组合上运行本策略,无需订阅任何行情数据。
  • 确保 CommissionRate 与经纪商实际费率一致,否则结果会被高估或低估。
  • 对于挂单,启动前务必设置有效的 EntryPrice,否则订单不会被提交。
  • 启用保护功能时,策略会要求连接器在触发后以市价退出,以便贴近原脚本的结算方式。

报告信息

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;
	}
}