在 GitHub 上查看

交易量计算策略

概述

交易量计算策略 复刻了原始 MetaTrader 智能交易系统的逻辑,通过设定止损价与止盈价来计算建议持仓量。策略启动后会读取配置的价格、获取所选证券的当前市价,并结合账户资金推导出风险指标。

该策略不会发送任何委托,它仅在日志中输出详细的资金管理统计,同时通过只读属性暴露计算结果。这样可以帮助手动交易者在下单之前验证仓位控制规则。

参数

  • Stop Loss Price – 计划持仓的止损价格。
  • Take Profit Price – 计划持仓的止盈价格。
  • Max Loss % – 单笔交易愿意承担的最大账户比例。策略用该百分比乘以账户权益得到可接受的最大货币亏损。
  • Is Long Position – 指定计划持仓方向,为 true 表示做多,为 false 表示做空。方向用于计算当前价格与止损/止盈之间的距离。

Max Loss % 外的参数都禁止参与优化,保持与原版脚本相同的手动输入体验。

计算细节

  1. 账户权益 – 读取 Portfolio.CurrentValue(缺失时退回到 Portfolio.BeginValue)来衡量可用资金;如果没有值,计算会被中止并给出警告。
  2. 价格步长验证 – 必须提供 Security.PriceStepSecurity.StepPrice,因为它们负责把价格距离转换为最小变动点数及对应货币价值。若缺失则无法继续。
  3. 当前价格获取 – 优先使用最新成交价,其次使用买一/卖一均价,最后退回到最近一次已知价格。
  4. 步数计算 – 止损和止盈距离会被除以价格步长并使用 decimal.Ceiling 向上取整,与原脚本中的 MathCeil 行为一致,保证风险估计保守。
  5. 风险金额 – 最大允许亏损等于 PortfolioValue * MaxLoss% / 100
  6. 建议手数 – 单步亏损为 MaxLoss / StopSteps,再除以 StepPrice 即得到保持风险受控的持仓量。
  7. 潜在盈利 – 将止盈步数乘以 StepPrice 再乘以建议手数,得到在触及目标价时的理论收益。
  8. 盈亏比 – 止盈步数与止损步数的比值,与原脚本使用点值计算的结果相同。

策略会把所有结果写入日志,并打印清晰的英文提示。如果盈亏比大于或等于 3,将提示 "You can trade";否则会给出风险过高的警告。

使用流程

  1. 在 StockSharp 环境中将策略绑定到目标证券和投资组合。
  2. 配置计划交易对应的止损价与止盈价。
  3. 设定可接受的风险百分比以及计划的持仓方向。
  4. 启动策略——日志会立即输出全部指标。
  5. 在手动下单前,检查建议手数与盈亏比是否符合交易计划。

注意事项

  • 若证券缺少价格步长或最小变动价值等元数据,请向交易所申请或在证券设置中手工补充。
  • 计算为静态结果;若市场环境或风险参数变化,需要重新启动策略以刷新数据。
  • 策略从不下单,因此可在回测或实时环境中安全使用,仅作为分析工具。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Volume Calculator strategy: EMA + volume confirmation.
/// Buys when price above EMA with increasing volume, sells below EMA with increasing volume.
/// </summary>
public class VolumeCalculatorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;

	private decimal _prevVolume;
	private bool _wasBullishSignal;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }

	public VolumeCalculatorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevVolume = 0;
		_wasBullishSignal = false;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevVolume = 0;
		_wasBullishSignal = false;
		_hasPrev = 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;

		if (_hasPrev)
		{
			var volumeUp = candle.TotalVolume > _prevVolume;
			var bullishSignal = candle.ClosePrice > emaValue && volumeUp;
			var bearishSignal = candle.ClosePrice < emaValue && volumeUp;
			var crossedUp = bullishSignal && !_wasBullishSignal;
			var crossedDown = bearishSignal && _wasBullishSignal;

			if (crossedUp && Position <= 0)
				BuyMarket();
			else if (crossedDown && Position >= 0)
				SellMarket();

			if (bullishSignal || bearishSignal)
				_wasBullishSignal = bullishSignal;
		}

		_prevVolume = candle.TotalVolume;
		_hasPrev = true;
	}
}