加密再平衡溢价策略
该策略保持比特币和以太坊的等权组合。通过每周再平衡,试图捕捉两种资产波动带来的再平衡溢价。
策略监控小时K线,在每周一UTC 0点执行再平衡。如果调整金额低于设定的美元阈值,则跳过交易。
细节
- 投资范围:比特币和以太坊。
- 信号:保持BTC与ETH各占50%权重。
- 再平衡:每周一00:00(UTC)。
- 仓位:仅做多,等权配置。
- 参数:
BTC– 比特币品种。ETH– 以太坊品种。MinTradeUsd– 最小交易金额(美元)。CandleType– 使用的K线周期(默认1小时)。
- 注意:示例实现较为简化,未考虑手续费或资金费率。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Configuration;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Equal-weight crypto basket strategy that rebalances the primary and secondary instruments on a weekly schedule.
/// </summary>
public class CryptoRebalancingPremiumStrategy : Strategy
{
private readonly StrategyParam<string> _secondarySecurityId;
private readonly StrategyParam<decimal> _minTradeUsd;
private readonly StrategyParam<DataType> _candleType;
private Security _secondarySecurity = null!;
private decimal _latestPrimaryPrice;
private decimal _latestSecondaryPrice;
private DateTime _lastRebalanceTime;
/// <summary>
/// Secondary crypto security identifier.
/// </summary>
public string SecondarySecurityId
{
get => _secondarySecurityId.Value;
set => _secondarySecurityId.Value = value;
}
/// <summary>
/// Minimum trade amount in USD.
/// </summary>
public decimal MinTradeUsd
{
get => _minTradeUsd.Value;
set => _minTradeUsd.Value = value;
}
/// <summary>
/// Candle type used for both instruments.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public CryptoRebalancingPremiumStrategy()
{
_secondarySecurityId = Param(nameof(SecondarySecurityId), Paths.HistoryDefaultSecurity2)
.SetDisplay("Second Security Id", "Identifier of the secondary crypto security", "General");
_minTradeUsd = Param(nameof(MinTradeUsd), 200m)
.SetRange(10m, 10000m)
.SetDisplay("Min Trade USD", "Minimum dollar amount per trade", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
if (!SecondarySecurityId.IsEmpty())
yield return (new Security { Id = SecondarySecurityId }, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_secondarySecurity = null!;
_latestPrimaryPrice = 0m;
_latestSecondaryPrice = 0m;
_lastRebalanceTime = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security == null)
throw new InvalidOperationException("Primary crypto security is not specified.");
if (SecondarySecurityId.IsEmpty())
throw new InvalidOperationException("Secondary crypto security identifier is not specified.");
_secondarySecurity = this.LookupById(SecondarySecurityId) ?? new Security { Id = SecondarySecurityId };
var primarySubscription = SubscribeCandles(CandleType, security: Security);
var secondarySubscription = SubscribeCandles(CandleType, security: _secondarySecurity);
primarySubscription
.Bind(candle => ProcessCandle(candle, Security))
.Start();
secondarySubscription
.Bind(candle => ProcessCandle(candle, _secondarySecurity))
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, primarySubscription);
DrawCandles(area, secondarySubscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, Security security)
{
if (candle.State != CandleStates.Finished)
return;
if (security == Security)
_latestPrimaryPrice = candle.ClosePrice;
else if (security == _secondarySecurity)
_latestSecondaryPrice = candle.ClosePrice;
if (_latestPrimaryPrice <= 0m || _latestSecondaryPrice <= 0m)
return;
if (candle.OpenTime == _lastRebalanceTime)
return;
if (candle.OpenTime.DayOfWeek != DayOfWeek.Monday || candle.OpenTime.Hour != 0)
return;
_lastRebalanceTime = candle.OpenTime;
Rebalance();
}
private void Rebalance()
{
RebalanceSecurity(Security, 1m);
RebalanceSecurity(_secondarySecurity, 1m);
}
private void RebalanceSecurity(Security security, decimal targetVolume)
{
var price = security == Security ? _latestPrimaryPrice : _latestSecondaryPrice;
if (price <= 0m)
return;
var diff = targetVolume - GetPositionValue(security, Portfolio).GetValueOrDefault();
if (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 = "RebalPrem"
});
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, DayOfWeek
from StockSharp.Messages import DataType, CandleStates, Sides, OrderTypes
from StockSharp.Algo.Strategies import Strategy
from StockSharp.BusinessEntities import Order, Security
class crypto_rebalancing_premium_strategy(Strategy):
"""Equal-weight crypto basket strategy that rebalances the primary and secondary instruments on a weekly schedule."""
def __init__(self):
super(crypto_rebalancing_premium_strategy, self).__init__()
self._secondary_security_id = self.Param("SecondarySecurityId", "TONUSDT@BNBFT") \
.SetDisplay("Second Security Id", "Identifier of the secondary crypto security", "General")
self._min_trade_usd = self.Param("MinTradeUsd", 200.0) \
.SetRange(10.0, 10000.0) \
.SetDisplay("Min Trade USD", "Minimum dollar amount per trade", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._secondary_security = None
self._latest_primary_price = 0.0
self._latest_secondary_price = 0.0
self._last_rebalance_time = None
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
result = []
if self.Security is not None:
result.append((self.Security, self.candle_type))
sec2_id = str(self._secondary_security_id.Value)
if sec2_id:
s = Security()
s.Id = sec2_id
result.append((s, self.candle_type))
return result
def OnReseted(self):
super(crypto_rebalancing_premium_strategy, self).OnReseted()
self._secondary_security = None
self._latest_primary_price = 0.0
self._latest_secondary_price = 0.0
self._last_rebalance_time = None
def OnStarted2(self, time):
super(crypto_rebalancing_premium_strategy, self).OnStarted2(time)
sec2_id = str(self._secondary_security_id.Value)
if not sec2_id:
raise Exception("Secondary crypto security identifier is not specified.")
s = Security()
s.Id = sec2_id
self._secondary_security = s
primary_subscription = self.SubscribeCandles(self.candle_type, True, self.Security)
secondary_subscription = self.SubscribeCandles(self.candle_type, True, self._secondary_security)
primary_subscription.Bind(lambda candle: self.ProcessCandle(candle, True)).Start()
secondary_subscription.Bind(lambda candle: self.ProcessCandle(candle, False)).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, primary_subscription)
self.DrawCandles(area, secondary_subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, is_primary):
if candle.State != CandleStates.Finished:
return
if is_primary:
self._latest_primary_price = float(candle.ClosePrice)
else:
self._latest_secondary_price = float(candle.ClosePrice)
if self._latest_primary_price <= 0.0 or self._latest_secondary_price <= 0.0:
return
if self._last_rebalance_time is not None and candle.OpenTime == self._last_rebalance_time:
return
if candle.OpenTime.DayOfWeek != DayOfWeek.Monday or candle.OpenTime.Hour != 0:
return
self._last_rebalance_time = candle.OpenTime
self.Rebalance()
def Rebalance(self):
self.RebalanceSecurity(self.Security, 1.0, True)
self.RebalanceSecurity(self._secondary_security, 1.0, False)
def RebalanceSecurity(self, security, target_volume, is_primary):
price = self._latest_primary_price if is_primary else self._latest_secondary_price
if price <= 0.0:
return
pos_val = self.GetPositionValue(security, self.Portfolio)
current_pos = float(pos_val) if pos_val is not None else 0.0
diff = target_volume - current_pos
min_trade = float(self._min_trade_usd.Value)
if abs(diff) * price < min_trade:
return
order = Order()
order.Security = security
order.Portfolio = self.Portfolio
order.Side = Sides.Buy if diff > 0 else Sides.Sell
order.Volume = abs(diff)
order.Type = OrderTypes.Market
order.Comment = "RebalPrem"
self.RegisterOrder(order)
def CreateClone(self):
return crypto_rebalancing_premium_strategy()