LANZ Strategy 1.0 [Backtest]
LANZ Strategy 1.0 opens a single limit order each day based on the price direction between 08:00 and 18:00 New York time. The order is placed between 18:00 and 08:00 with fixed stop-loss and take-profit distances in pips. Any open position is closed at the configured New York hour.
Parameters
- Candle Type
- Account Size USD
- Risk percent
- EP offset fraction
- Stop Loss (pips)
- Take Profit (pips)
- Manual close hour (NY)
- Enable buy
- Enable sell
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// LANZ Strategy 1.0 backtest: places a limit order based on 08:00-18:00 price direction.
/// </summary>
public class Lanz10BacktestStrategy : Strategy
{
private readonly StrategyParam<decimal> _accountSizeUsd;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<decimal> _epOffsetFraction;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<int> _manualCloseHour;
private readonly StrategyParam<bool> _enableBuy;
private readonly StrategyParam<bool> _enableSell;
private readonly StrategyParam<DataType> _candleType;
private decimal _openAt0800;
private int _prevPriceDirection;
private int _todayPriceDirection;
private int _finalSignalDirection;
private decimal _baseLevel;
private bool _orderSent;
private Order _entryOrder;
private decimal _pipSize;
private readonly TimeZoneInfo _nyZone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
public decimal AccountSizeUsd { get => _accountSizeUsd.Value; set => _accountSizeUsd.Value = value; }
public decimal RiskPercent { get => _riskPercent.Value; set => _riskPercent.Value = value; }
public decimal EpOffsetFraction { get => _epOffsetFraction.Value; set => _epOffsetFraction.Value = value; }
public decimal StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
public decimal TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
public int ManualCloseHour { get => _manualCloseHour.Value; set => _manualCloseHour.Value = value; }
public bool EnableBuy { get => _enableBuy.Value; set => _enableBuy.Value = value; }
public bool EnableSell { get => _enableSell.Value; set => _enableSell.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Lanz10BacktestStrategy()
{
_accountSizeUsd = Param(nameof(AccountSizeUsd), 100000m)
.SetDisplay("Account Size USD", "Account capital", "Money");
_riskPercent = Param(nameof(RiskPercent), 1m)
.SetDisplay("Risk %", "Risk percent per trade", "Money")
;
_epOffsetFraction = Param(nameof(EpOffsetFraction), 0m)
.SetDisplay("EP Offset", "Entry price offset fraction", "Risk")
.SetRange(-5m, 5m);
_stopLossPips = Param(nameof(StopLossPips), 18m)
.SetDisplay("Stop Loss (pips)", "Stop loss distance", "Risk")
.SetGreaterThanZero();
_takeProfitPips = Param(nameof(TakeProfitPips), 54m)
.SetDisplay("Take Profit (pips)", "Take profit distance", "Risk")
.SetGreaterThanZero();
_manualCloseHour = Param(nameof(ManualCloseHour), 9)
.SetDisplay("Manual Close Hour", "Hour to close open position NY time", "Time");
_enableBuy = Param(nameof(EnableBuy), true)
.SetDisplay("Enable Buy", "Allow long entries", "Signals");
_enableSell = Param(nameof(EnableSell), true)
.SetDisplay("Enable Sell", "Allow short entries", "Signals");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_openAt0800 = 0m;
_prevPriceDirection = 0;
_todayPriceDirection = 0;
_finalSignalDirection = 0;
_baseLevel = 0m;
_orderSent = false;
_entryOrder = null;
_pipSize = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = (Security?.PriceStep ?? 1m) * 10m;
StartProtection(
stopLoss: new Unit(StopLossPips * _pipSize, UnitTypes.Absolute),
takeProfit: new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute),
useMarketOrders: true);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var nyOpen = TimeZoneInfo.ConvertTime(candle.OpenTime, _nyZone);
var nyClose = TimeZoneInfo.ConvertTime(candle.CloseTime, _nyZone);
var is0800 = nyOpen.Hour == 8 && nyOpen.Minute == 0;
if (is0800)
_openAt0800 = candle.OpenPrice;
var is1800 = nyClose.Hour == 18 && nyClose.Minute == 0;
if (is1800)
{
var priceDirection = candle.ClosePrice > _openAt0800 ? 1 : candle.ClosePrice < _openAt0800 ? -1 : 0;
_prevPriceDirection = _todayPriceDirection;
_todayPriceDirection = priceDirection;
var coinciden = priceDirection == _prevPriceDirection && _prevPriceDirection != 0;
_finalSignalDirection = coinciden ? priceDirection : -1 * priceDirection;
var fibRange = candle.HighPrice - candle.LowPrice;
_baseLevel = _finalSignalDirection == -1
? (EpOffsetFraction == 0m ? candle.LowPrice : candle.LowPrice + fibRange * -EpOffsetFraction)
: (EpOffsetFraction == 0m ? candle.HighPrice : candle.HighPrice - fibRange * -EpOffsetFraction);
_orderSent = false;
_entryOrder = null;
}
if (nyClose.Hour == ManualCloseHour && nyClose.Minute == 0 && Position != 0)
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
_orderSent = false;
_entryOrder = null;
return;
}
if (nyClose.Hour == 8 && nyClose.Minute == 0 && Position == 0 && _orderSent && _entryOrder is not null)
{
_orderSent = false;
_entryOrder = null;
}
var entryWindow = nyOpen.Hour >= 18 || nyOpen.Hour < 8;
var canPlaceOrder = !_orderSent && Position == 0 && entryWindow;
if (canPlaceOrder)
{
var riskUsd = AccountSizeUsd * (RiskPercent / 100m);
var qty = StopLossPips > 0m ? riskUsd / (StopLossPips * 10m) : 0m;
if (qty <= 0m)
return;
var isLong = _finalSignalDirection == -1;
if (isLong && EnableBuy)
{
BuyMarket();
_orderSent = true;
}
else if (!isLong && EnableSell)
{
SellMarket();
_orderSent = true;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class lanz_1_0_backtest_strategy(Strategy):
def __init__(self):
super(lanz_1_0_backtest_strategy, self).__init__()
self._enable_buy = self.Param("EnableBuy", True) \
.SetDisplay("Enable Buy", "Allow long entries", "Signals")
self._enable_sell = self.Param("EnableSell", True) \
.SetDisplay("Enable Sell", "Allow short entries", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._open_at_0800 = 0.0
self._prev_price_direction = 0
self._today_price_direction = 0
self._final_signal_direction = 0
self._order_sent = False
self._bar_count = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(lanz_1_0_backtest_strategy, self).OnReseted()
self._open_at_0800 = 0.0
self._prev_price_direction = 0
self._today_price_direction = 0
self._final_signal_direction = 0
self._order_sent = False
self._bar_count = 0
def OnStarted2(self, time):
super(lanz_1_0_backtest_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
self._bar_count += 1
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
hour = candle.OpenTime.Hour
if hour == 8:
self._open_at_0800 = open_p
if hour == 18 and self._open_at_0800 > 0.0:
if close > self._open_at_0800:
pd = 1
elif close < self._open_at_0800:
pd = -1
else:
pd = 0
self._prev_price_direction = self._today_price_direction
self._today_price_direction = pd
coinciden = pd == self._prev_price_direction and self._prev_price_direction != 0
self._final_signal_direction = pd if coinciden else -pd
self._order_sent = False
if hour == 9 and self.Position != 0:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._order_sent = False
return
entry_window = hour >= 18 or hour < 8
can_place = not self._order_sent and self.Position == 0 and entry_window
if can_place and self._bar_count > 48:
is_long = self._final_signal_direction == -1
if is_long and self._enable_buy.Value:
self.BuyMarket()
self._order_sent = True
elif not is_long and self._enable_sell.Value:
self.SellMarket()
self._order_sent = True
def CreateClone(self):
return lanz_1_0_backtest_strategy()