BTC Outperform
Правила торговли
Завершённые суточные UTC-свечи объединяются в фиксированные периоды. Недельные интервалы отсчитываются от понедельника 1970-01-05, 90-дневные — от 1970-01-01. Закрытие периода доступно только после получения всех суточных свечей от начала до конца. Неполные стартовые интервалы и периоды с пропусками закрытия не публикуют.
Сравниваются последние полностью наблюдённые завершённые закрытия: недельное выше квартального — покупка, ниже — продажа, равенство сохраняет позицию. Из нуля объём равен Volume; при развороте — Volume+abs(Position), чтобы оставить полную противоположную позицию. Незавершённые периоды не меняют сравнение. Незавершённые заявки блокируют дубли. Ценового стопа и тейк-профита нет.
Параметры
| Параметр | По умолчанию |
|---|---|
CandleType |
1d |
WeeklyDays |
7 |
QuarterlyDays |
90 |
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>Compares completed fixed-duration weekly and quarterly closes.</summary>
public class BtcOutperformStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _weeklyDays;
private readonly StrategyParam<int> _quarterlyDays;
private DateTime _week, _quarter, _lastTime;
private int _weekCount, _quarterCount;
private decimal? _weeklyClose, _quarterlyClose;
private Order _pending;
/// <summary>Finished source candle interval.</summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>UTC Monday-anchored weekly period in days.</summary>
public int WeeklyDays
{
get => _weeklyDays.Value;
set => _weeklyDays.Value = value;
}
/// <summary>UTC epoch-anchored long period in days.</summary>
public int QuarterlyDays
{
get => _quarterlyDays.Value;
set => _quarterlyDays.Value = value;
}
/// <summary>Initializes the seven-day and ninety-day periods.</summary>
public BtcOutperformStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("CandleType", "Finished source candle interval", "Parameters");
_weeklyDays = Param(nameof(WeeklyDays), 7).SetGreaterThanZero()
.SetDisplay("WeeklyDays", "UTC Monday-anchored weekly period in days", "Parameters");
_quarterlyDays = Param(nameof(QuarterlyDays), 90).SetGreaterThanZero()
.SetDisplay("QuarterlyDays", "UTC epoch-anchored long period in days", "Parameters");
OrderRegistering += order => _pending = order;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_week = _quarter = _lastTime = default;
_weekCount = _quarterCount = 0;
_weeklyClose = _quarterlyClose = null;
_pending = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
if (!CandleType.Equals(TimeSpan.FromDays(1).TimeFrame()) || WeeklyDays >= QuarterlyDays)
throw new InvalidOperationException("Daily source candles and a weekly period shorter than the quarterly period are required.");
base.OnStarted2(time);
var candles = SubscribeCandles(CandleType);
candles.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, candles);
DrawOwnTrades(area);
}
}
private static DateTime Bucket(DateTime date, DateTime epoch, int days)
=> epoch.AddDays(Math.Floor((date - epoch).TotalDays / days) * days);
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || candle.OpenTime <= _lastTime)
return;
var date = candle.OpenTime.Date;
var contiguous = _lastTime == default || date == _lastTime.AddDays(1);
var week = Bucket(date, new DateTime(1970, 1, 5, 0, 0, 0, DateTimeKind.Utc), WeeklyDays);
var quarter = Bucket(date, DateTime.UnixEpoch, QuarterlyDays);
_weekCount = week != _week ? date == week ? 1 : 0 : contiguous && _weekCount > 0 ? _weekCount + 1 : 0;
_quarterCount = quarter != _quarter ? date == quarter ? 1 : 0 : contiguous && _quarterCount > 0 ? _quarterCount + 1 : 0;
_week = week;
_quarter = quarter;
_lastTime = date;
if (_weekCount == WeeklyDays && date.AddDays(1) == week.AddDays(WeeklyDays))
_weeklyClose = candle.ClosePrice;
if (_quarterCount == QuarterlyDays && date.AddDays(1) == quarter.AddDays(QuarterlyDays))
_quarterlyClose = candle.ClosePrice;
if (_weeklyClose is null || _quarterlyClose is null || !IsFormedAndOnlineAndAllowTrading()
|| _pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed))
return;
if (_weeklyClose > _quarterlyClose && Position <= 0m)
BuyMarket(Volume + Math.Abs(Position));
else if (_weeklyClose < _quarterlyClose && Position >= 0m)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
import math
from collections import deque
from System import TimeSpan, DateTime, DateTimeKind, Decimal, Nullable
from StockSharp.Messages import DataType, CandleStates, Sides, OrderStates, Unit, UnitTypes
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class btc_outperform_strategy(Strategy):
def __init__(self):
super(btc_outperform_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1))).SetDisplay(
"CandleType", "Finished source candle interval", "Parameters")
self._weekly_days = self.Param("WeeklyDays", 7).SetGreaterThanZero().SetDisplay(
"WeeklyDays", "UTC Monday-anchored weekly period in days", "Parameters")
self._quarterly_days = self.Param("QuarterlyDays", 90).SetGreaterThanZero().SetDisplay(
"QuarterlyDays", "UTC epoch-anchored long period in days", "Parameters")
self._reset_state()
self.OrderRegistering += self._remember_order
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def WeeklyDays(self):
return self._weekly_days.Value
@WeeklyDays.setter
def WeeklyDays(self, value):
self._weekly_days.Value = value
@property
def QuarterlyDays(self):
return self._quarterly_days.Value
@QuarterlyDays.setter
def QuarterlyDays(self, value):
self._quarterly_days.Value = value
def _reset_state(self):
self._week = DateTime.MinValue
self._quarter = DateTime.MinValue
self._last_time = DateTime.MinValue
self._week_count = self._quarter_count = 0
self._weekly_close = self._quarterly_close = None
self._pending = None
def _remember_order(self, order):
self._pending = order
def OnReseted(self):
super(btc_outperform_strategy, self).OnReseted()
self._reset_state()
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnStarted2(self, time):
if self.CandleType != DataType.TimeFrame(TimeSpan.FromDays(1)) or self.WeeklyDays >= self.QuarterlyDays:
raise ValueError("Daily source candles and a weekly period shorter than the quarterly period are required.")
super(btc_outperform_strategy, self).OnStarted2(time)
candles = self.SubscribeCandles(self.CandleType)
candles.Bind(self._process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawOwnTrades(area)
@staticmethod
def _bucket(date, epoch, days):
return epoch.AddDays(math.floor((date - epoch).TotalDays / days) * days)
def _process(self, candle):
if candle.State != CandleStates.Finished or candle.OpenTime <= self._last_time:
return
date = candle.OpenTime.Date
contiguous = self._last_time == DateTime.MinValue or date == self._last_time.AddDays(1)
week = self._bucket(date, DateTime(1970, 1, 5, 0, 0, 0, DateTimeKind.Utc), self.WeeklyDays)
quarter = self._bucket(date, DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), self.QuarterlyDays)
self._week_count = (1 if date == week else 0) if week != self._week else (self._week_count + 1 if contiguous and self._week_count > 0 else 0)
self._quarter_count = (1 if date == quarter else 0) if quarter != self._quarter else (self._quarter_count + 1 if contiguous and self._quarter_count > 0 else 0)
self._week, self._quarter, self._last_time = week, quarter, date
if self._week_count == self.WeeklyDays and date.AddDays(1) == week.AddDays(self.WeeklyDays):
self._weekly_close = candle.ClosePrice
if self._quarter_count == self.QuarterlyDays and date.AddDays(1) == quarter.AddDays(self.QuarterlyDays):
self._quarterly_close = candle.ClosePrice
if self._weekly_close is None or self._quarterly_close is None or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed):
return
if self._weekly_close > self._quarterly_close and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif self._weekly_close < self._quarterly_close and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return btc_outperform_strategy()