在 GitHub 上查看

条件持仓开启策略

概述

条件持仓开启策略 复刻了原始的 MetaTrader 脚本“当没有持仓时开多单”的思路。策略的核心非常直接:当手动开关允许做多或做空时,只有在对应方向上没有敞口时才发送市价单,从而避免重复建仓,让仓位始终符合选择的方向。

StockSharp 版本依靠框架的高级蜡烛订阅以及内置的风控助手实现,经纪商无关。止损与止盈距离以点值(价格步长)表示,因此可以适配任意品种。

策略逻辑

  1. 订阅配置好的蜡烛序列,作为周期性触发心跳。
  2. 在每根完成的蜡烛上检查当前净头寸。
  3. 如果多头开关开启且仓位为空或净空,则发送买入市价单。
  4. 如果空头开关开启且仓位为空或净多,则发送卖出市价单。
  5. 通过 StartProtection 自动处理保护性订单,将点值距离转换为实际价格偏移。

由于 StockSharp 使用净持仓模型,同时开启两个方向时会先尝试建立多头,再在成交后若仍为空仓时尝试建立空头,这与原始脚本避免每个方向重复下单的意图一致。

参数

名称 默认值 说明
Volume 1 每次入场的下单数量。
StopLossPips 100 以价格步长表示的止损距离,设为 0 可关闭。
TakeProfitPips 200 以价格步长表示的止盈距离,设为 0 可关闭。
EnableBuy false 当为 true 时,若没有多头敞口可开多单。
EnableSell false 当为 true 时,若没有空头敞口可开空单。
CandleType 1 分钟周期 用于驱动周期性检查的蜡烛序列。

备注

  • 距离值会使用合约的 PriceStep 转换为实际价格偏移;若交易所未提供步长,则直接使用原始点值作为绝对距离。
  • StartProtection 会在成交后自动附加止损与止盈,无需额外的手工订单管理。
  • 策略专注于参数化的手动触发流程,可作为主观交易模板使用。
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Conditional Position Opener" MetaTrader expert.
/// Uses Momentum indicator to conditionally open long or short positions.
/// Opens long when momentum is positive, short when negative.
/// </summary>
public class ConditionalPositionOpenerStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _momentumPeriod;

	private Momentum _momentum;
	private decimal? _prevMomentum;

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

	public int MomentumPeriod
	{
		get => _momentumPeriod.Value;
		set => _momentumPeriod.Value = value;
	}

	public ConditionalPositionOpenerStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for signal generation", "General");

		_momentumPeriod = Param(nameof(MomentumPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Period", "Momentum indicator period", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevMomentum = null;
		_momentum = new Momentum { Length = MomentumPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_momentum, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		if (!_momentum.IsFormed)
		{
			_prevMomentum = momentumValue;
			return;
		}

		if (_prevMomentum is null)
		{
			_prevMomentum = momentumValue;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Cross above 101 (positive momentum)
		var crossUp = _prevMomentum.Value <= 101m && momentumValue > 101m;
		// Cross below 99 (negative momentum)
		var crossDown = _prevMomentum.Value >= 99m && momentumValue < 99m;

		if (crossUp)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevMomentum = momentumValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_momentum = null;
		_prevMomentum = null;

		base.OnReseted();
	}
}