圣诞行情策略
交易年末圣诞行情。在12月最后五个交易日内的首根符合条件的已完成 K 线入场;在1月第二个交易日结束时平仓。若缺少该收盘 K 线,则在之后首根符合条件的 K 线平仓。
使用已完成的15分钟 K 线和明确设置的 Security.Board 交易日历,包括节假日、特殊交易时段和交易板块时区。缺少日历时无法启动。只做多,每个年度季节最多尝试入场一次;止损后当季不再入场。未完成订单阻止重叠决策。
StopLoss = 2 为从实际入场成交价计算的2%本地原生保护。交易标的的 bid/ask 更新可在 K 线之间触发市价平仓。未设置止盈。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using Ecng.ComponentModel;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>Long-only rally over the last five December sessions and first two January sessions.</summary>
public class SantaClausRallyStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLoss;
private int _entryYear = -1;
private Order _pendingOrder;
/// <summary>Signal candle interval.</summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>Loss limit as a percentage of the actual entry price.</summary>
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
/// <summary>Initializes the seasonal strategy.</summary>
public SantaClausRallyStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Finished candles used for calendar decisions", "Data");
_stopLoss = Param(nameof(StopLoss), 2m).SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Percentage loss limit from the executed entry", "Risk");
OrderRegistering += order => _pendingOrder = order;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryYear = -1;
_pendingOrder = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
if (Security?.Board is null)
throw new InvalidOperationException("The instrument's trading board and calendar are required.");
base.OnStarted2(time);
_entryYear = -1;
_pendingOrder = null;
StartProtection(new Unit(), new Unit(StopLoss, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Start();
}
var candles = SubscribeCandles(CandleType);
candles.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, candles);
DrawOwnTrades(area);
}
}
private (DateTime entry, DateTime exit) GetSeason(int year)
{
var calendar = Security.Board.WorkingTime;
var entry = new DateTime(year, 12, 31);
for (var sessions = 0; ; entry = entry.AddDays(-1))
{
if (calendar.IsWorkingDate(entry) && ++sessions == 5)
break;
}
var exit = new DateTime(year + 1, 1, 1);
for (var sessions = 0; ; exit = exit.AddDays(1))
{
if (calendar.IsWorkingDate(exit) && ++sessions == 2)
break;
}
return (entry, exit);
}
private TimeSpan SessionEnd(DateTime date)
{
var calendar = Security.Board.WorkingTime;
if (!calendar.IsEnabled)
return TimeSpan.FromDays(1);
var period = calendar.GetPeriod(date);
IEnumerable<Range<TimeSpan>> sessions = period?.Times;
if (calendar.SpecialDays.TryGetValue(date, out var special))
sessions = special;
else if (period is not null && period.SpecialDays.TryGetValue(date.DayOfWeek, out var weekday))
sessions = weekday;
return sessions is not null && sessions.Any() ? sessions.Max(r => r.Max) : TimeSpan.FromDays(1);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || !IsFormedAndOnlineAndAllowTrading()
|| _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
var local = TimeZoneInfo.ConvertTimeFromUtc(candle.OpenTime, Security.Board.TimeZone);
var date = local.Date;
var year = local.Month == 12 ? local.Year : local.Year - 1;
var (entry, exit) = GetSeason(year);
if (Position > 0m)
{
var barEnd = TimeZoneInfo.ConvertTimeFromUtc(candle.OpenTime + (TimeSpan)CandleType.Arg, Security.Board.TimeZone);
if (date > exit || date == exit && barEnd >= exit + SessionEnd(exit))
SellMarket(Position);
}
else if (Position == 0m && local.Month == 12 && date >= entry && _entryYear != year
&& Security.Board.WorkingTime.IsWorkingDate(date))
{
_entryYear = year;
BuyMarket(Volume);
}
}
}
import clr
clr.AddReference("Ecng.ComponentModel")
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import DateTime, TimeSpan, TimeZoneInfo
from Ecng.ComponentModel import WorkingTimeExtensions
from StockSharp.Messages import DataType, CandleStates, OrderStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class santa_claus_rally_strategy(Strategy):
"""Long-only rally over the last five December sessions and first two January sessions."""
def __init__(self):
super(santa_claus_rally_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay(
"Candle Type", "Finished candles used for calendar decisions", "Data")
self._stop_loss = self.Param("StopLoss", 2.0).SetGreaterThanZero().SetDisplay(
"Stop Loss %", "Percentage loss limit from the executed entry", "Risk")
self._entry_year = -1
self._pending_order = None
self.OrderRegistering += self._on_order
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _on_order(self, order):
self._pending_order = order
def OnReseted(self):
super(santa_claus_rally_strategy, self).OnReseted()
self._entry_year = -1
self._pending_order = None
def OnStarted2(self, time):
if self.Security is None or self.Security.Board is None:
raise ValueError("The instrument's trading board and calendar are required")
super(santa_claus_rally_strategy, self).OnStarted2(time)
self._entry_year = -1
self._pending_order = None
self.StartProtection(Unit(), Unit(float(self._stop_loss.Value), UnitTypes.Percent),
useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Start()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _season(self, year):
calendar = self.Security.Board.WorkingTime
entry = DateTime(year, 12, 31)
sessions = 0
while True:
if WorkingTimeExtensions.IsWorkingDate(calendar, entry, True):
sessions += 1
if sessions == 5:
break
entry = entry.AddDays(-1)
exit_date = DateTime(year + 1, 1, 1)
sessions = 0
while True:
if WorkingTimeExtensions.IsWorkingDate(calendar, exit_date, True):
sessions += 1
if sessions == 2:
break
exit_date = exit_date.AddDays(1)
return entry, exit_date
def _session_end(self, date):
calendar = self.Security.Board.WorkingTime
if not calendar.IsEnabled:
return TimeSpan.FromDays(1)
period = WorkingTimeExtensions.GetPeriod(calendar, date)
sessions = list(period.Times) if period is not None else []
if calendar.SpecialDays.ContainsKey(date):
sessions = list(calendar.SpecialDays[date])
elif period is not None and period.SpecialDays.ContainsKey(date.DayOfWeek):
sessions = list(period.SpecialDays[date.DayOfWeek])
if not sessions:
return TimeSpan.FromDays(1)
end = sessions[0].Max
for session in sessions[1:]:
if session.Max > end:
end = session.Max
return end
def _process_candle(self, candle):
if candle.State != CandleStates.Finished or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
local = TimeZoneInfo.ConvertTimeFromUtc(candle.OpenTime, self.Security.Board.TimeZone)
date = local.Date
year = local.Year if local.Month == 12 else local.Year - 1
entry, exit_date = self._season(year)
if self.Position > 0:
bar_end = TimeZoneInfo.ConvertTimeFromUtc(candle.OpenTime.Add(self.candle_type.Arg), self.Security.Board.TimeZone)
if date > exit_date or (date == exit_date and bar_end >= exit_date.Add(self._session_end(exit_date))):
self.SellMarket(self.Position)
elif self.Position == 0 and local.Month == 12 and date >= entry and self._entry_year != year:
if WorkingTimeExtensions.IsWorkingDate(self.Security.Board.WorkingTime, date, True):
self._entry_year = year
self.BuyMarket(self.Volume)
def CreateClone(self):
return santa_claus_rally_strategy()