Bitcoin Intraday Seasonality
在预先设定的日内小时内做多比特币的策略。
测试显示年化收益约为45%,在加密货币市场表现最佳。
系统监控小时K线。在选定的UTC时间段内,它持有与账户价值相等的多头仓位,其余时间保持空仓。低于最小美元金额的订单会被忽略。
细节
- 入场条件:在指定的UTC小时内持有比特币多头。
- 多/空:仅多头。
- 出场条件:在非指定时间退出。
- 止损:无。
- 默认值:
HoursLong= [0, 1, 2, 3]MinTradeUsd= 200CandleType= TimeSpan.FromHours(1)
- 过滤器:
- 类别:季节性
- 方向:多头
- 指标:无
- 止损:无
- 复杂度:基础
- 时间框架:日内 (1小时)
- 季节性:是
- 神经网络:否
- 背离:否
- 风险等级:中
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that goes long on Bitcoin during predefined strong intraday hours.
/// </summary>
public class BitcoinIntradaySeasonalityStrategy : Strategy
{
private readonly StrategyParam<int[]> _hoursLong;
private readonly StrategyParam<decimal> _minUsd;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// UTC hours when the strategy holds a long position.
/// </summary>
public int[] HoursLong
{
get => _hoursLong.Value;
set => _hoursLong.Value = value;
}
/// <summary>
/// Minimum trade value in USD.
/// </summary>
public decimal MinTradeUsd
{
get => _minUsd.Value;
set => _minUsd.Value = value;
}
/// <summary>
/// The type of candles to use for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
private readonly Dictionary<Security, decimal> _latestPrices = [];
/// <summary>
/// Initializes a new instance of <see cref="BitcoinIntradaySeasonalityStrategy"/>.
/// </summary>
public BitcoinIntradaySeasonalityStrategy()
{
// Hours to stay long (UTC).
_hoursLong = Param(nameof(HoursLong), new[] { 0, 1, 2, 3 })
.SetDisplay("Long Hours", "UTC hours when the strategy stays long", "General");
// Minimum trade size.
_minUsd = Param(nameof(MinTradeUsd), 200m)
.SetGreaterThanZero()
.SetDisplay("Min Trade USD", "Minimum order value in USD", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security == null)
throw new InvalidOperationException("BTC security not set.");
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_latestPrices.Clear();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (HoursLong == null || HoursLong.Length == 0)
throw new InvalidOperationException("HoursLong cannot be empty.");
if (Security == null)
throw new InvalidOperationException("BTC security not set.");
SubscribeCandles(CandleType, true, Security)
.Bind(c => ProcessCandle(c, Security))
.Start();
}
private void ProcessCandle(ICandleMessage candle, Security security)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Store the latest closing price for this security
_latestPrices[security] = candle.ClosePrice;
OnHourClose(candle);
}
private void OnHourClose(ICandleMessage c)
{
var hour = c.OpenTime.Hour; // assume server UTC
var inSeason = c.OpenTime.DayOfWeek == DayOfWeek.Monday && c.OpenTime.Day <= 7 && HoursLong.Contains(hour);
var portfolioValue = Portfolio.CurrentValue ?? 0m;
var price = GetLatestPrice(Security);
var tgt = inSeason && price > 0 ? portfolioValue / price : 0m;
var diff = tgt - PositionBy(Security);
if (price <= 0 || Math.Abs(diff) * price < MinTradeUsd)
return;
RegisterOrder(new Order
{
Security = Security,
Portfolio = Portfolio,
Side = diff > 0 ? Sides.Buy : Sides.Sell,
Volume = Math.Abs(diff),
Type = OrderTypes.Market,
Comment = "BTCSeason",
});
}
private decimal GetLatestPrice(Security security)
{
return _latestPrices.TryGetValue(security, out var price) ? price : 0m;
}
private decimal PositionBy(Security s) => GetPositionValue(s, Portfolio) ?? 0;
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, DayOfWeek
from StockSharp.Messages import DataType, CandleStates, Sides, OrderTypes, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
from StockSharp.BusinessEntities import Order
from datatype_extensions import *
class bitcoin_intraday_seasonality_strategy(Strategy):
"""
Strategy that goes long on Bitcoin during predefined strong intraday hours.
Only trades on the first Monday of the month during specified UTC hours.
"""
def __init__(self):
super(bitcoin_intraday_seasonality_strategy, self).__init__()
self._hours_long = self.Param("HoursLong", [0, 1, 2, 3]) \
.SetDisplay("Long Hours", "UTC hours when the strategy stays long", "General")
self._min_trade_usd = self.Param("MinTradeUsd", 200.0) \
.SetGreaterThanZero() \
.SetDisplay("Min Trade USD", "Minimum order value in USD", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._latest_price = 0.0
@property
def HoursLong(self):
return self._hours_long.Value
@HoursLong.setter
def HoursLong(self, value):
self._hours_long.Value = value
@property
def MinTradeUsd(self):
return self._min_trade_usd.Value
@MinTradeUsd.setter
def MinTradeUsd(self, value):
self._min_trade_usd.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(bitcoin_intraday_seasonality_strategy, self).OnReseted()
self._latest_price = 0.0
def OnStarted2(self, time):
super(bitcoin_intraday_seasonality_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
self._latest_price = float(candle.ClosePrice)
hour = candle.OpenTime.Hour
is_first_monday = candle.OpenTime.DayOfWeek == DayOfWeek.Monday and candle.OpenTime.Day <= 7
in_season = is_first_monday and hour in self.HoursLong
price = self._latest_price
if price <= 0:
return
portfolio_value = 0.0
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
portfolio_value = float(self.Portfolio.CurrentValue)
if portfolio_value <= 0:
portfolio_value = 100000.0
tgt = portfolio_value / price if in_season else 0.0
diff = tgt - float(self.Position)
if abs(diff) * price < self.MinTradeUsd:
return
if diff > 0:
self.BuyMarket(abs(diff))
elif diff < 0:
self.SellMarket(abs(diff))
def CreateClone(self):
return bitcoin_intraday_seasonality_strategy()