| Overall Statistics |
|
Total Orders 540 Average Win 4.54% Average Loss -3.85% Compounding Annual Return -1.994% Drawdown 82.000% Expectancy 0.055 Start Equity 100000 End Equity 83539.14 Net Profit -16.461% Sharpe Ratio 0.065 Sortino Ratio 0.065 Probabilistic Sharpe Ratio 0.119% Loss Rate 52% Win Rate 48% Profit-Loss Ratio 1.18 Alpha 0.021 Beta 0.022 Annual Standard Deviation 0.347 Annual Variance 0.12 Information Ratio -0.17 Tracking Error 0.378 Treynor Ratio 1.011 Total Fees $316.61 Estimated Strategy Capacity $2000.00 Lowest Capacity Asset TRXUSD E3 Portfolio Turnover 1.93% Drawdown Recovery 374 |
# https://quantpedia.com/strategies/crypto-skewness-strategy/
#
# The investment universe for this strategy consists of the 45 most popular cryptocurrencies based on their trading history. The selection criteria require that each cryptocurrency
# has a trading history from at least January 1, 2018, to the present. The dataset includes daily exchange rates in USD, obtained from Coinmetrics. This ensures that the selected
# cryptocurrencies have sufficient historical data for skewness calculations and performance evaluation. The primary tool used in this strategy is the skewness of daily returns.
# Skewness is calculated over two different periods: 30 days for the Monthly Model and 360 days for the Yearly Model. For the Monthly Model, at the end of each month, cryptocurrencies
# are sorted into quartiles based on their 30-day skewness. Two portfolios are constructed: one containing the top quartile (highest skewness) and the other containing the bottom
# quartile (lowest skewness). The adjusted trading rule for the Monthly Model is to long the top quartile portfolio and short the bottom quartile portfolio. For the Yearly Model,
# the same process is followed, but skewness is calculated over the past 360 days. The trading rule for the Yearly Model is to long the bottom quartile portfolio and short the top
# quartile portfolio. Portfolios are rebalanced at the end of each month based on the updated skewness calculations. The number of positions in each portfolio depends on the sorting
# method. For quartiles, each portfolio contains 11 cryptocurrencies. Position sizes are typically equal-weighted to ensure diversification and manage risk. Risk management techniques
# include monitoring performance metrics such as cumulative returns, Sharpe ratio, and maximum drawdown. Additionally, traders should consider implementing stop-loss orders and position
# sizing rules to limit potential losses. Transaction costs and slippage should also be accounted for, as they can impact net performance. Regularly reviewing and adjusting the strategy
# parameters based on changing market conditions can help maintain its effectiveness.
# region imports
from AlgorithmImports import *
from scipy.stats import skew
from scipy.stats import linregress
# endregion
class CryptoSkewnessStrategy(QCAlgorithm):
def initialize(self):
self.set_start_date(2017, 1, 1)
# Exclued stablecoins from universe
stablecoins: List[str] = [
"USDTUSD",
"USDCUSD",
"DAIUSD",
"TUSDUSD",
"FDUSDUSD",
"BUSDUSD",
"USDPUSD",
"GUSDUSD",
"USDDUSD",
"EURTUSD",
"EURSUSD",
"FRAXUSD",
"LUSDUSD",
"PYUSDUSD",
]
self.set_account_currency("USD")
self._market = Market.BITFINEX
self._market_pairs = [
x.key.symbol
for x in self.symbol_properties_database.get_symbol_properties_list(self._market)
if x.value.quote_currency == self.account_currency
and x.key.symbol not in stablecoins
]
self._top_coins: int = 20
self._period: int = 360 + 1
self._quantile: int = 4
self._leverage: int = 10
self._trade_multiplier: float = .5
self.set_warmup(self._period, Resolution.DAILY)
self._price_data: Dict[Symbol, RollingWindow] = {}
# Add a universe of Cryptocurrencies.
self._universe = self.add_universe(CoinGeckoUniverse, 'CoinGeckoUniverse', Resolution.DAILY, self._select_assets)
self._daily_flag: bool = False
self.schedule.on(
self.date_rules.every_day(),
self.time_rules.at(0, 0),
self._daily_close
)
self.schedule.on(
self.date_rules.month_start(),
self.time_rules.at(0, 0),
self._rebalance
)
def on_securities_changed(self, changes: SecurityChanges) -> None:
for security in changes.added_securities:
security.set_fee_model(CustomFeeModel())
security.set_leverage(self._leverage)
self._price_data.setdefault(security.symbol, RollingWindow[float](self._period))
def _select_assets(self, data: List[CoinGecko]) -> List[Symbol]:
tradable_coins: List[CoinGecko] = [d for d in data if d.coin + self.account_currency in self._market_pairs]
# Select the largest coins and create their Symbol objects.
current_universe: List[Symbol] = [
c.create_symbol(self._market, self.account_currency)
for c in sorted(tradable_coins, key=lambda x: x.market_cap)[-self._top_coins:]
]
return current_universe
def on_data(self, slice: Slice) -> None:
# Update price daily
if not self._daily_flag:
return
self._daily_flag = False
for symbol, price_data in self._price_data.items():
if slice.contains_key(symbol) and slice[symbol]:
price_data.add(slice[symbol].close)
def _daily_close(self) -> None:
self._daily_flag = True
def _rebalance(self) -> None:
if self.is_warming_up: return
if not self._universe.selected:
return
symbol_skew: Dict[Symbol, float] = {}
beta: Dict[Symbol, float] = {}
for symbol in self._price_data:
if symbol.value == 'BTCUSD':
if self._price_data[symbol].is_ready:
benchmark_daily_prices: np.ndarray = np.array(list(self._price_data[symbol]))
benchmark_daily_returns: np.ndarray = benchmark_daily_prices[:-1] / benchmark_daily_prices[1:] - 1
continue
if symbol not in self._universe.selected or not self._price_data[symbol].is_ready:
continue
if self._price_data[symbol].is_ready:
daily_prices: np.ndarray = np.array(list(self._price_data[symbol]))
daily_returns: np.ndarray = daily_prices[:-1] / daily_prices[1:] - 1
# calculate skew
symbol_skew[symbol] = skew(daily_returns)
# calculate beta
# beta_coeff,_,_,_,_ = linregress(daily_returns, benchmark_daily_returns)
# beta[symbol] = beta_coeff
long: List[Symbol] = []
short: List[Symbol] = []
targets: List[PortfolioTarget] = []
if len(symbol_skew) >= self._quantile:
# long the bottom portfolios and short the top portfolios
sorted_by_skew: List[Symbol] = sorted(symbol_skew, key = symbol_skew.get, reverse = True)
quantile: int = int(len(sorted_by_skew) / self._quantile)
long = sorted_by_skew[-quantile:]
short = sorted_by_skew[:quantile]
# order execution
# long_beta: float = sum([beta[s] for s in long]) / len(long)
# short_beta: float = sum([beta[s] for s in short]) / len(short)
# coeff: float = long_beta / short_beta
coeff: float = 1.
for i, portfolio in enumerate([long, short]):
for ticker in portfolio:
targets.append(PortfolioTarget(ticker, ((((-1) ** i) / len(portfolio)) * (coeff if i == 1 else 1.)) * self._trade_multiplier))
self.set_holdings(targets, True)
class CustomFeeModel(FeeModel):
''' Models custom trade fees. '''
def get_order_fee(self, parameters: OrderFeeParameters) -> OrderFee:
fee: float = parameters.security.price * parameters.order.absolute_quantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))