Estrategia de Prima de Rebalanceo Cripto
La Estrategia de Prima de Rebalanceo Cripto mantiene una cartera de igual ponderación entre Bitcoin y Ethereum. Al rebalancear la cesta cada semana, intenta capturar la prima generada por la volatilidad entre los dos activos.
La estrategia observa velas por hora y realiza un rebalanceo en la primera hora de cada lunes. Las operaciones se omiten si el ajuste requerido es menor que un umbral en USD definido por el usuario.
Detalles
- Universo: Símbolos de Bitcoin y Ethereum.
- Señal: Mantener BTC y ETH en pesos 50/50.
- Rebalanceo: Semanal, el lunes a las 00:00 UTC.
- Posicionamiento: Solo largos, igual ponderación.
- Parámetros:
BTC– activo Bitcoin.ETH– activo Ethereum.MinTradeUsd– valor mínimo de operación en USD.CandleType– marco temporal de las velas (predeterminado: 1 hora).
- Nota: La implementación es simplificada y no incluye comisiones ni costes de financiación.
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()