Synthetic Lending Rates
This strategy exploits differences between synthetic lending rates derived from derivative markets and on‑chain lending yields. By borrowing where rates are low and lending where rates are high, it captures the spread between them.
Positions are rebalanced regularly to maintain neutrality, and risk is controlled through rate change thresholds and liquidity filters.
Details
- Data: Perpetual swap funding and DeFi lending rates.
- Entry: Borrow in low‑rate venue and lend in high‑rate venue when spread > threshold.
- Exit: Close when spread mean reverts or liquidity deteriorates.
- Instruments: Perpetual swaps and DeFi platforms.
- Risk: Spread cap and liquidity stop.
// SyntheticLendingRatesStrategy.cs
// -----------------------------------------------------------------------------
// Uses change in synthetic lending-rate intensity derived from price momentum
// to take directional positions.
// Compares short-term and long-term moving averages as a proxy for
// synthetic lending-rate shifts. Buys when short-term momentum increases,
// sells when it decreases, with cooldown management.
// -----------------------------------------------------------------------------
// Date: 2 Aug 2025
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trades based on changes in synthetic lending-rate intensity derived from price momentum.
/// </summary>
public class SyntheticLendingRatesStrategy : Strategy
{
private readonly StrategyParam<int> _shortPeriod;
private readonly StrategyParam<int> _longPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Short-term momentum period.
/// </summary>
public int ShortPeriod
{
get => _shortPeriod.Value;
set => _shortPeriod.Value = value;
}
/// <summary>
/// Long-term momentum period.
/// </summary>
public int LongPeriod
{
get => _longPeriod.Value;
set => _longPeriod.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// The type of candles to use for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
private ExponentialMovingAverage _shortEma;
private ExponentialMovingAverage _longEma;
private decimal _prevShortValue;
private decimal _prevLongValue;
private int _cooldownRemaining;
public SyntheticLendingRatesStrategy()
{
_shortPeriod = Param(nameof(ShortPeriod), 5)
.SetDisplay("Short Period", "Short-term momentum period", "Parameters");
_longPeriod = Param(nameof(LongPeriod), 20)
.SetDisplay("Long Period", "Long-term momentum period", "Parameters");
_cooldownBars = Param(nameof(CooldownBars), 30)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).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);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_shortEma = null;
_longEma = null;
_prevShortValue = 0;
_prevLongValue = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_shortEma = new ExponentialMovingAverage { Length = ShortPeriod };
_longEma = new ExponentialMovingAverage { Length = LongPeriod };
SubscribeCandles(CandleType)
.Bind(_shortEma, _longEma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal shortValue, decimal longValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_shortEma.IsFormed || !_longEma.IsFormed)
{
_prevShortValue = shortValue;
_prevLongValue = longValue;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevShortValue = shortValue;
_prevLongValue = longValue;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevShortValue = shortValue;
_prevLongValue = longValue;
return;
}
// Synthetic intensity: short-term momentum relative to long-term
var currentIntensity = shortValue - longValue;
var prevIntensity = _prevShortValue - _prevLongValue;
// Intensity increasing (short EMA rising faster) -> buy signal
if (currentIntensity > 0 && prevIntensity <= 0 && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Intensity decreasing (short EMA falling relative to long) -> sell signal
else if (currentIntensity < 0 && prevIntensity >= 0 && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
_prevShortValue = shortValue;
_prevLongValue = longValue;
}
}
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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class synthetic_lending_rates_strategy(Strategy):
"""Trades based on changes in synthetic lending-rate intensity derived from price momentum."""
def __init__(self):
super(synthetic_lending_rates_strategy, self).__init__()
self._short_period = self.Param("ShortPeriod", 5) \
.SetDisplay("Short Period", "Short-term momentum period", "Parameters")
self._long_period = self.Param("LongPeriod", 20) \
.SetDisplay("Long Period", "Long-term momentum period", "Parameters")
self._cooldown_bars = self.Param("CooldownBars", 30) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._short_ema = None
self._long_ema = None
self._prev_short = 0.0
self._prev_long = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(synthetic_lending_rates_strategy, self).OnReseted()
self._short_ema = None
self._long_ema = None
self._prev_short = 0.0
self._prev_long = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(synthetic_lending_rates_strategy, self).OnStarted2(time)
self._short_ema = ExponentialMovingAverage()
self._short_ema.Length = int(self._short_period.Value)
self._long_ema = ExponentialMovingAverage()
self._long_ema.Length = int(self._long_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription \
.Bind(self._short_ema, self._long_ema, self._process_candle) \
.Start()
def _process_candle(self, candle, short_val, long_val):
if candle.State != CandleStates.Finished:
return
sv = float(short_val)
lv = float(long_val)
if not self._short_ema.IsFormed or not self._long_ema.IsFormed:
self._prev_short = sv
self._prev_long = lv
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_short = sv
self._prev_long = lv
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_short = sv
self._prev_long = lv
return
current_intensity = sv - lv
prev_intensity = self._prev_short - self._prev_long
if current_intensity > 0 and prev_intensity <= 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = int(self._cooldown_bars.Value)
elif current_intensity < 0 and prev_intensity >= 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = int(self._cooldown_bars.Value)
self._prev_short = sv
self._prev_long = lv
def CreateClone(self):
return synthetic_lending_rates_strategy()