Стратегия Crypto Rebalancing Premium
Стратегия поддерживает равновесный портфель из биткоина и эфира. Еженедельная перебалансировка позволяет извлекать премию, возникающую из-за волатильности между этими активами.
Отслеживаются часовые свечи, а перебалансировка выполняется в первый час каждого понедельника. Сделки пропускаются, если требуемая корректировка меньше заданного долларового порога.
Детали
- Вселенная: тикеры Bitcoin и Ethereum.
- Сигнал: поддерживать веса BTC и ETH по 50/50.
- Перебалансировка: еженедельно в 00:00 UTC в понедельник.
- Позиционирование: только длинные позиции, равные доли.
- Параметры:
BTC– инструмент биткоина.ETH– инструмент эфира.MinTradeUsd– минимальная сумма сделки.CandleType– таймфрейм свечей (по умолчанию 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()