在 GitHub 上查看

3100 Close All Positions

概述

  • 将 MQL5 工具 Close all positions 转换为 StockSharp 高阶策略实现。
  • 订阅所选时间框架的完结K线,汇总投资组合中所有未平仓头寸的浮动盈亏。
  • 当浮盈达到或超过阈值时,针对策略及其子策略涉及的所有证券发送市价单,直到全部仓位被平掉。
  • _closeAllRequested 标志复刻了 MQL 中的 m_close_all 变量,确保在仓位完全关闭之前持续发出平仓指令。

参数

名称 类型 默认值 说明
ProfitThreshold decimal 10 需要达到的浮动利润(账户货币),一旦满足即平掉所有仓位,对应 EA 中的 InpProfit
CandleType DataType 1m 时间框架 用于检测“新K线”的系列,仅在K线收盘后执行收益检查,对应原脚本的 PrevBars 判断。

交易逻辑

  1. 策略订阅 CandleType 的K线,只处理状态为 Finished 的K线,从而模拟 EA 只在新K线诞生时计算利润的行为。
  2. 每根完结K线调用 CalculateTotalProfit,优先读取 Portfolio.CurrentProfit(包含手续费和隔夜利息的浮盈)。若适配器无法提供该值,则退回到逐个累加 position.PnL
  3. 若计算得到的浮动利润低于 ProfitThreshold,策略保持观望。
  4. 一旦利润达到阈值,将 _closeAllRequested 置为 true 并立即调用 CloseAllPositions()
  5. CloseAllPositions() 收集投资组合与子策略中的所有相关证券,按当前仓位方向发送反向市价单(多头→卖出,空头→买入)。
  6. 直到 HasAnyOpenPosition() 检测到组合已空仓之前,_closeAllRequested 会一直保持为 true,这与原 EA 在全部票据关闭之前反复执行 CloseAllPositions 的逻辑一致。

额外说明

  • 按任务要求,仅提供 C# 版本,Python 文件夹保持为空。
  • 策略不会撤销挂单,因为原脚本只负责平掉市场仓位。
  • ProfitThreshold 已通过 SetOptimize 配置,可在 Designer 中进行收益阈值的参数优化测试。

文件

  • CS/CloseAllPositionsStrategy.cs
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Opens positions based on SMA trend and closes when floating PnL reaches a profit threshold.
/// Simplified from the "Close all positions" utility expert.
/// </summary>
public class CloseAllPositionsStrategy : Strategy
{
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private SimpleMovingAverage _sma;

	private decimal _entryPrice;
	private decimal _prevSma;
	private int _cooldown;

	/// <summary>
	/// SMA period for entry signals.
	/// </summary>
	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public CloseAllPositionsStrategy()
	{
		_smaPeriod = Param(nameof(SmaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "Moving average period for entry signals", "Indicators");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 300)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_sma = null;
		_entryPrice = 0;
		_prevSma = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_sma = new SimpleMovingAverage { Length = SmaPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_sma, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal smaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_sma.IsFormed)
		{
			_prevSma = smaValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevSma = smaValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevSma = smaValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevSma = smaValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevSma = smaValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevSma = smaValue;
				return;
			}
		}

		// Crossover entry: price crosses above SMA -> buy
		if (close > smaValue && candle.OpenPrice <= _prevSma && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 60;
		}
		// Price crosses below SMA -> sell
		else if (close < smaValue && candle.OpenPrice >= _prevSma && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 60;
		}

		_prevSma = smaValue;
	}
}