The January Barometer states that the market’s performance in January sets the tone for the remainder of the year. This strategy invests in an equity ETF for the rest of the year only if January closes higher; otherwise it stays in a cash proxy. The allocation decision is made once per year and held until year end.
On the first trading day of February the algorithm measures the total return of the equity ETF during January. If the return is positive and the order value exceeds the minimum threshold, it buys the equity ETF and holds it through December. If January was negative, it holds the cash ETF instead. The process repeats each year.
Details
Entry Criteria:
On the first trading day of February calculate the total January return of EquityETF.
Buy EquityETF if the return is positive and order size >= MinTradeUsd; otherwise hold CashETF.
Long/Short: Long equities or cash only.
Exit Criteria: Close the equity position on the last trading day of the year.
Stops: None.
Default Values:
EquityETF – ETF representing the equity market.
CashETF – cash proxy ETF.
CandleType = 1 day.
MinTradeUsd – minimum trade value.
Filters:
Category: Seasonal.
Direction: Long only.
Timeframe: Long‑term.
Rebalance: Annually.
using System;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// January barometer strategy generalized to any month.
/// Measures the return over the first N candles of each evaluation period,
/// then goes long if bullish or short if bearish for the remainder.
/// Re-evaluates at the start of each new period.
/// </summary>
public class JanuaryBarometerStrategy : Strategy
{
private readonly StrategyParam<int> _measureCandles;
private readonly StrategyParam<int> _periodCandles;
private readonly StrategyParam<DataType> _candleType;
private int _candleCount;
private decimal _periodOpen;
private decimal _measureClose;
private bool _measured;
/// <summary>
/// Number of candles in the measurement (barometer) window.
/// </summary>
public int MeasureCandles
{
get => _measureCandles.Value;
set => _measureCandles.Value = value;
}
/// <summary>
/// Total candles per evaluation period before resetting.
/// </summary>
public int PeriodCandles
{
get => _periodCandles.Value;
set => _periodCandles.Value = value;
}
/// <summary>
/// The type of candles to use for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public JanuaryBarometerStrategy()
{
_measureCandles = Param(nameof(MeasureCandles), 50)
.SetGreaterThanZero()
.SetDisplay("Measure Candles", "Number of candles for barometer measurement", "General");
_periodCandles = Param(nameof(PeriodCandles), 200)
.SetGreaterThanZero()
.SetDisplay("Period Candles", "Total candles per evaluation period", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candleCount = 0;
_periodOpen = 0m;
_measureClose = 0m;
_measured = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_candleCount++;
// Start of a new period
if (_candleCount == 1)
{
_periodOpen = candle.OpenPrice;
_measured = false;
}
// End of measurement window
if (_candleCount == MeasureCandles && !_measured)
{
_measureClose = candle.ClosePrice;
_measured = true;
if (_periodOpen > 0m)
{
var barometerReturn = (_measureClose - _periodOpen) / _periodOpen;
var bullish = barometerReturn > 0m;
// Enter position based on barometer reading
if (bullish && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (!bullish && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
// End of period: close position and reset for next period
if (_candleCount >= PeriodCandles)
{
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
_candleCount = 0;
_periodOpen = 0m;
_measureClose = 0m;
_measured = false;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class january_barometer_strategy(Strategy):
"""
January barometer strategy generalized to any month.
Measures the return over the first N candles of each evaluation period,
then goes long if bullish or short if bearish for the remainder.
Re-evaluates at the start of each new period.
"""
def __init__(self):
super(january_barometer_strategy, self).__init__()
self._measure_candles = self.Param("MeasureCandles", 50) \
.SetGreaterThanZero() \
.SetDisplay("Measure Candles", "Number of candles for barometer measurement", "General")
self._period_candles = self.Param("PeriodCandles", 200) \
.SetGreaterThanZero() \
.SetDisplay("Period Candles", "Total candles per evaluation period", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._candle_count = 0
self._period_open = 0
self._measure_close = 0
self._measured = False
@property
def MeasureCandles(self):
return self._measure_candles.Value
@property
def PeriodCandles(self):
return self._period_candles.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(january_barometer_strategy, self).OnReseted()
self._candle_count = 0
self._period_open = 0
self._measure_close = 0
self._measured = False
def OnStarted2(self, time):
super(january_barometer_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._candle_count += 1
# Start of a new period
if self._candle_count == 1:
self._period_open = candle.OpenPrice
self._measured = False
# End of measurement window
if self._candle_count == self.MeasureCandles and not self._measured:
self._measure_close = candle.ClosePrice
self._measured = True
if self._period_open > 0:
barometer_return = (self._measure_close - self._period_open) / self._period_open
bullish = barometer_return > 0
# Enter position based on barometer reading
if bullish and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif not bullish and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
# End of period: close position and reset for next period
if self._candle_count >= self.PeriodCandles:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._candle_count = 0
self._period_open = 0
self._measure_close = 0
self._measured = False
def CreateClone(self):
return january_barometer_strategy()