| Overall Statistics |
|
Total Orders 80 Average Win 3.85% Average Loss -2.19% Compounding Annual Return 7.938% Drawdown 7.400% Expectancy 0.378 Start Equity 200000 End Equity 270265.18 Net Profit 35.133% Sharpe Ratio 0.141 Sortino Ratio 0.065 Probabilistic Sharpe Ratio 11.524% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.76 Alpha 0.015 Beta 0.002 Annual Standard Deviation 0.104 Annual Variance 0.011 Information Ratio -0.154 Tracking Error 0.182 Treynor Ratio 7.599 Total Fees $11031.26 Estimated Strategy Capacity $350000.00 Lowest Capacity Asset BITO XSSNZDP7WC4L Portfolio Turnover 4.44% Drawdown Recovery 217 |
# region imports
from AlgorithmImports import *
# endregion
# Strategy overview:
# - IBIT etf is bought on Friday 15 minutes before market close if current price is at 10d high.
# - Close trade on Monday, 15 minutes before market close.
class BitcoinOvernightSession(QCAlgorithm):
_notional_value: int = 200_000
_trade_exec_minute_offset: int = 15
_period: int = 10
_trading_days: List[str] = [DayOfWeek.FRIDAY, DayOfWeek.MONDAY, DayOfWeek.TUESDAY]
# 1 - 3 trading days
_selected_trading_days: int = 1
def initialize(self) -> None:
self.set_start_date(2022, 1, 1)
self.set_cash(self._notional_value)
_ticker: str = 'IBIT' if self.live_mode else 'BITO'
self._traded_asset: Symbol = self.add_equity(_ticker, Resolution.MINUTE).symbol
self._trading_days = self._trading_days[:self._selected_trading_days]
self.log(f'Traded asset: {_ticker}')
self.log(f'Trading days: {self._trading_days}')
self._rebalance_flag: bool = False
self.schedule.on(
self.date_rules.every_day(),
self.time_rules.before_market_close(self._traded_asset, self._trade_exec_minute_offset),
self._rebalance
)
def on_data(self, slice: Slice) -> None:
if not self._rebalance_flag:
return
self._rebalance_flag = False
data_is_present: bool = slice.contains_key(self._traded_asset) and slice[self._traded_asset]
if self.live_mode:
self.log(f'New rebalance day. Data present: {data_is_present}. Weekday + 1: {self.time.weekday() + 1}')
if data_is_present and self.time.weekday() + 1 in self._trading_days:
history: DataFrame = self.history(TradeBar, self._traded_asset, self._period, Resolution.DAILY)
if history.empty or len(history) < self._period:
self.log(f'Insufficient data for a signal calculation history length {len(history)}; {self._period} needed')
return
recent_price: float = slice[self._traded_asset].close
price_high: float = history.loc[self._traded_asset].close.max()
trade_flag: bool = True if recent_price >= price_high else False
self.log(f'Trade flag: {trade_flag}. Recent price: {recent_price}. Price high: {price_high}')
if trade_flag:
if not self.portfolio[self._traded_asset].invested:
self.log('Condition met. Opening position.')
q: int = self._notional_value // slice[self._traded_asset].price
self.market_order(self._traded_asset, q)
else:
if self.portfolio[self._traded_asset].invested:
self.log('Condition not met. Liquidating position.')
self.liquidate(self._traded_asset)
else:
if self.portfolio[self._traded_asset].invested:
self.log('Not a trading day. Liquidating position.')
self.liquidate(self._traded_asset)
def _rebalance(self) -> None:
self._rebalance_flag = True