| Overall Statistics |
|
Total Orders 1598 Average Win 0.81% Average Loss -0.82% Compounding Annual Return -4.478% Drawdown 44.000% Expectancy -0.044 Start Equity 1000000.00 End Equity 682039.63 Net Profit -31.796% Sharpe Ratio -0.53 Sortino Ratio -0.685 Probabilistic Sharpe Ratio 0.001% Loss Rate 52% Win Rate 48% Profit-Loss Ratio 0.99 Alpha -0.056 Beta 0.042 Annual Standard Deviation 0.1 Annual Variance 0.01 Information Ratio -0.725 Tracking Error 0.18 Treynor Ratio -1.272 Total Fees $3917.83 Estimated Strategy Capacity $5000.00 Lowest Capacity Asset ZECUSD E3 Portfolio Turnover 3.37% |
# https://quantpedia.com/strategies/price-to-low-effect-in-cryptocurrencies/
#
# The investment universe for this strategy consists of a predefined list of 33 cryptocurrencies, focusing on those with larger market values and sufficient liquidity.
# (Data is from the LSEG Datastream.)
# Rationale of Strategy: The strategy utilizes a behavioral approach by incorporating anchoring points, specifically the highest and lowest prices during formation.
# 1. Calculate the primary indicator used, which is the reversal signal (REV) (by eq. (1)), as the natural logarithm of the price ratio at the start and end of the
# formation period.
# 2. Decompose this signal further into Price-to-Low (PTL) and Low-to-Price (LTP) components (by eq. (2)).
# Ranking: Cryptocurrencies are ranked based on their PTL values.
# Selected Variant Execution: Our included version focuses on the PTL 30 signal/variant. The presented strategies buy (sell) a tertile of cryptocurrencies with the
# highest (lowest) PTL values in the 30-day ranking period.
# The buy rule involves forming an extended portfolio with cryptocurrencies in the top tercile of PTL values. In contrast,
# the sell rule creates a short portfolio with those in the bottom tercile
# This approach aims to exploit short-term reversal effects. All portfolios are equal-weighted and rebalanced weekly. (Due to cryptos extreme volatility, we universally
# advise exposing a maximum of 10 % of the conservative capital allocation to any from crypto strategies.)
# region imports
from AlgorithmImports import *
# endregion
class PricetoLowEffectInCryptocurrencies(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2017, 1, 1)
self.set_cash(1_000_000)
crypto_pairs: List[str] = {
"XLMUSD", # Stellar
"XMRUSD", # Monero
"XRPUSD", # XRP
"ADAUSD", # Cardano
"DOTUSD", # Polkadot
"LINKUSD", # Chainlink
"BTCUSD", # Bitcoin
"BTGUSD", # Bitcoin Gold
"DASHUSD", # Dash
"ETCUSD", # Ethereum Classic
"ETHUSD", # Ethereum
"LTCUSD", # Litecoin
"NEOUSD", # Neo
"TRXUSD", # Tron
"XRPUSD", # XRP
"XVGUSD", # Verge
"ZECUSD", # Zcash
"ZRXUSD", # Ox
"BATUSD", # Basic Attention Token
"EOSUSD", # EOS
"IOTAUSD", # IOTA
"SOLUSD", # Solana
}
leverage: int = 10
period: int = 30
self._percentage_traded: float = .1
self._quantile: int = 10
self._data: Dict[Fundamental, SymbolData] = {}
# data subscription
for ticker in crypto_pairs:
data = self.add_crypto(ticker, Resolution.DAILY, Market.BITFINEX, leverage=leverage)
data.SetFeeModel(CustomFeeModel())
self._data[data.Symbol] = SymbolData(period)
self._selection_flag: bool = False
self.schedule.on(
self.date_rules.every(DayOfWeek.TUESDAY),
self.time_rules.at(0, 0),
self.selection
)
self.settings.minimum_order_margin_portfolio_percentage = 0.
self.settings.daily_precise_end_time = False
def on_data(self, slice: Slice) -> None:
# store daily price
for symbol, symbol_data in self._data.items():
if slice.contains_key(symbol) and slice[symbol]:
self._data[symbol].update(slice[symbol].close)
# rebalance weekly
if not self._selection_flag:
return
self._selection_flag = False
PTL: Dict[Symbol, float] = {
symbol: symbol_data.get_PTL()
for symbol, symbol_data in self._data.items()
if symbol_data.is_ready()
and symbol_data.get_PTL() != .0
}
if len(PTL) < self._quantile:
self.log('Not enought data for further calculation.')
return Universe.UNCHANGED
# sort and divide
sorted_PTL: List[Symbol] = sorted(PTL, key=PTL.get, reverse=True)
quantile: int = len(sorted_PTL) // self._quantile
long: List[Symbol] = sorted_PTL[:quantile]
short: List[Symbol] = sorted_PTL[-quantile:]
# trade execution
targets: List[PortfolioTarget] = []
for i, portfolio in enumerate([long, short]):
for symbol in portfolio:
if slice.contains_key(symbol) and slice[symbol]:
targets.append(
PortfolioTarget(
symbol, ((-1) ** i) * self._percentage_traded / len(portfolio)
)
)
self.set_holdings(targets, True)
def selection(self) -> None:
self._selection_flag = True
class SymbolData():
def __init__(self, period: int) -> None:
self._daily_price: RollingWindow = RollingWindow[float](period)
def update(self, price: float) -> None:
self._daily_price.add(price)
def get_PTL(self) -> float:
prices: List[float] = list(self._daily_price)
return np.log(prices[-1] / min(prices))
def is_ready(self) -> bool:
return self._daily_price.is_ready
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
fee: float = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))