在 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 判断。 |
交易逻辑
- 策略订阅
CandleType 的K线,只处理状态为 Finished 的K线,从而模拟 EA 只在新K线诞生时计算利润的行为。
- 每根完结K线调用
CalculateTotalProfit,优先读取 Portfolio.CurrentProfit(包含手续费和隔夜利息的浮盈)。若适配器无法提供该值,则退回到逐个累加 position.PnL。
- 若计算得到的浮动利润低于
ProfitThreshold,策略保持观望。
- 一旦利润达到阈值,将
_closeAllRequested 置为 true 并立即调用 CloseAllPositions()。
CloseAllPositions() 收集投资组合与子策略中的所有相关证券,按当前仓位方向发送反向市价单(多头→卖出,空头→买入)。
- 直到
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;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class close_all_positions_strategy(Strategy):
"""
Opens positions based on SMA trend and closes when SL/TP reached.
"""
def __init__(self):
super(close_all_positions_strategy, self).__init__()
self._sma_period = self.Param("SmaPeriod", 100) \
.SetDisplay("SMA Period", "Moving average period for entry signals", "Indicators")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 300) \
.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk")
self._entry_price = 0.0
self._prev_sma = 0.0
self._cooldown = 0
@property
def candle_type(self):
return DataType.TimeFrame(TimeSpan.FromMinutes(5))
def OnReseted(self):
super(close_all_positions_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_sma = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(close_all_positions_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._sma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def on_process(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
if self._prev_sma == 0.0:
self._prev_sma = sma_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_sma = sma_val
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None and float(self.Security.PriceStep) > 0:
step = float(self.Security.PriceStep)
if self.Position > 0 and self._entry_price > 0:
if self._stop_loss_points.Value > 0 and close <= self._entry_price - self._stop_loss_points.Value * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_sma = sma_val
return
if self._take_profit_points.Value > 0 and close >= self._entry_price + self._take_profit_points.Value * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_sma = sma_val
return
elif self.Position < 0 and self._entry_price > 0:
if self._stop_loss_points.Value > 0 and close >= self._entry_price + self._stop_loss_points.Value * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_sma = sma_val
return
if self._take_profit_points.Value > 0 and close <= self._entry_price - self._take_profit_points.Value * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_sma = sma_val
return
if close > sma_val and open_price <= self._prev_sma and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 60
elif close < sma_val and open_price >= self._prev_sma and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 60
self._prev_sma = sma_val
def CreateClone(self):
return close_all_positions_strategy()