Millenium Code 策略
Millenium Code 是一种头寸策略,每天最多只开一笔交易。方向由快速和慢速均线的交叉以及近期高低点过滤决定。交易在设定的时间开仓,并在时间到达、触及止损/止盈或达到最长持仓时间时平仓。
交易逻辑
- 在指定的开仓时间,策略检查当前星期是否允许交易。
- 当快速均线上穿慢速均线并且价格突破确认时开多头;反向条件开空头。
- 每天只允许一笔交易,其余信号直到下一天才处理。
- 持仓在以下任一条件满足时平仓:
- 触及止损或止盈;
- 到达设定的平仓时间;
- 超过最大持仓时间。
参数
- Candle Type – 输入K线的周期;
- Fast MA – 快速均线周期;
- Slow MA – 慢速均线周期;
- HighLow Bars – 计算近期高低点使用的K线数量;
- Reverse – 反转买卖信号;
- Stop Loss – 止损距离(价格步长);
- Take Profit – 止盈距离(价格步长);
- Open Hour/Minute – 开始寻找入场的时间(-1 禁用);
- Close Hour/Minute – 平仓时间(-1 禁用);
- Duration – 最大持仓时间(小时,0 禁用);
- Sunday ... Friday – 各星期几是否允许交易。
备注
该策略仅使用高级 API,不直接访问指标历史。内容仅供学习参考,不构成投资建议。
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>
/// Millenium Code positional strategy.
/// Uses fast/slow MA crossover with high/low channel filter.
/// </summary>
public class MilleniumCodeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _highLowBars;
private readonly StrategyParam<bool> _reverseSignal;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<decimal> _takeProfitPct;
private Highest _highest;
private Lowest _lowest;
private decimal _prevFast;
private decimal _prevSlow;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
public int HighLowBars { get => _highLowBars.Value; set => _highLowBars.Value = value; }
public bool ReverseSignal { get => _reverseSignal.Value; set => _reverseSignal.Value = value; }
public decimal StopLossPct { get => _stopLossPct.Value; set => _stopLossPct.Value = value; }
public decimal TakeProfitPct { get => _takeProfitPct.Value; set => _takeProfitPct.Value = value; }
public MilleniumCodeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_fastLength = Param(nameof(FastLength), 10)
.SetDisplay("Fast MA", "Fast moving average length", "Indicators");
_slowLength = Param(nameof(SlowLength), 30)
.SetDisplay("Slow MA", "Slow moving average length", "Indicators");
_highLowBars = Param(nameof(HighLowBars), 10)
.SetDisplay("HighLow Bars", "Bars count for high/low search", "Indicators");
_reverseSignal = Param(nameof(ReverseSignal), true)
.SetDisplay("Reverse", "Reverse buy/sell logic", "General");
_stopLossPct = Param(nameof(StopLossPct), 2m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_takeProfitPct = Param(nameof(TakeProfitPct), 3m)
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_highest = default;
_lowest = default;
_prevFast = 0m;
_prevSlow = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new ExponentialMovingAverage { Length = FastLength };
var slow = new ExponentialMovingAverage { Length = SlowLength };
_highest = new Highest { Length = HighLowBars };
_lowest = new Lowest { Length = HighLowBars };
Indicators.Add(_highest);
Indicators.Add(_lowest);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, (candle, fastVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
var highResult = _highest.Process(candle);
var lowResult = _lowest.Process(candle);
if (!highResult.IsFormed || !lowResult.IsFormed)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var high = highResult.ToDecimal();
var low = lowResult.ToDecimal();
if (_prevFast == 0 || _prevSlow == 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var crossUp = _prevFast < _prevSlow && fastVal > slowVal;
var crossDown = _prevFast > _prevSlow && fastVal < slowVal;
var dir = 0;
if (crossUp) dir = ReverseSignal ? -1 : 1;
else if (crossDown) dir = ReverseSignal ? 1 : -1;
if (dir == 1 && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (dir == -1 && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
_prevFast = fastVal;
_prevSlow = slowVal;
}).Start();
StartProtection(
takeProfit: new Unit(TakeProfitPct, UnitTypes.Percent),
stopLoss: new Unit(StopLossPct, UnitTypes.Percent),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, Highest, Lowest, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class millenium_code_strategy(Strategy):
def __init__(self):
super(millenium_code_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._fast_length = self.Param("FastLength", 10) \
.SetDisplay("Fast MA", "Fast moving average length", "Indicators")
self._slow_length = self.Param("SlowLength", 30) \
.SetDisplay("Slow MA", "Slow moving average length", "Indicators")
self._high_low_bars = self.Param("HighLowBars", 10) \
.SetDisplay("HighLow Bars", "Bars count for high/low search", "Indicators")
self._reverse_signal = self.Param("ReverseSignal", True) \
.SetDisplay("Reverse", "Reverse buy/sell logic", "General")
self._stop_loss_pct = self.Param("StopLossPct", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._take_profit_pct = self.Param("TakeProfitPct", 3.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._highest = None
self._lowest = None
self._prev_fast = 0.0
self._prev_slow = 0.0
@property
def candle_type(self):
return self._candle_type.Value
@property
def fast_length(self):
return self._fast_length.Value
@property
def slow_length(self):
return self._slow_length.Value
@property
def high_low_bars(self):
return self._high_low_bars.Value
@property
def reverse_signal(self):
return self._reverse_signal.Value
@property
def stop_loss_pct(self):
return self._stop_loss_pct.Value
@property
def take_profit_pct(self):
return self._take_profit_pct.Value
def OnReseted(self):
super(millenium_code_strategy, self).OnReseted()
self._highest = None
self._lowest = None
self._prev_fast = 0.0
self._prev_slow = 0.0
def OnStarted2(self, time):
super(millenium_code_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self.fast_length
slow = ExponentialMovingAverage()
slow.Length = self.slow_length
self._highest = Highest()
self._highest.Length = self.high_low_bars
self._lowest = Lowest()
self._lowest.Length = self.high_low_bars
self.Indicators.Add(self._highest)
self.Indicators.Add(self._lowest)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.on_candle).Start()
self.StartProtection(
takeProfit=Unit(self.take_profit_pct, UnitTypes.Percent),
stopLoss=Unit(self.stop_loss_pct, UnitTypes.Percent),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def on_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_val)
slow_val = float(slow_val)
cv_h = CandleIndicatorValue(self._highest, candle)
high_result = self._highest.Process(cv_h)
cv_l = CandleIndicatorValue(self._lowest, candle)
low_result = self._lowest.Process(cv_l)
if not high_result.IsFormed or not low_result.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast == 0.0 or self._prev_slow == 0.0:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
cross_up = self._prev_fast < self._prev_slow and fast_val > slow_val
cross_down = self._prev_fast > self._prev_slow and fast_val < slow_val
direction = 0
if cross_up:
direction = -1 if self.reverse_signal else 1
elif cross_down:
direction = 1 if self.reverse_signal else -1
if direction == 1 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif direction == -1 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return millenium_code_strategy()