Skip to content

Commit

Permalink
refactor for correct type hints
Browse files Browse the repository at this point in the history
  • Loading branch information
richklee committed Jun 9, 2024
1 parent 6d0097f commit 73dd1ae
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 50 deletions.
59 changes: 31 additions & 28 deletions alphavec/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
DEFAULT_RISK_FREE_RATE = 0.02


def zero_commission(weights: pd.DataFrame, prices: pd.DataFrame) -> float:
def zero_commission(weights: pd.DataFrame, prices: pd.DataFrame) -> pd.DataFrame:
"""Zero trading commission.
Args:
Expand All @@ -17,10 +17,12 @@ def zero_commission(weights: pd.DataFrame, prices: pd.DataFrame) -> float:
Returns:
Always returns 0.
"""
return 0
return pd.DataFrame(0, index=weights.index, columns=weights.columns)


def flat_commission(weights: pd.DataFrame, prices: pd.DataFrame, fee: float) -> float:
def flat_commission(
weights: pd.DataFrame, prices: pd.DataFrame, fee: float
) -> pd.DataFrame:
"""Flat commission applies a fixed fee per trade.
Args:
Expand All @@ -37,7 +39,9 @@ def flat_commission(weights: pd.DataFrame, prices: pd.DataFrame, fee: float) ->
return commissions


def pct_commission(weights: pd.DataFrame, prices: pd.DataFrame, fee: float) -> float:
def pct_commission(
weights: pd.DataFrame, prices: pd.DataFrame, fee: float
) -> pd.DataFrame:
"""Percentage commission applies a percentage fee per trade.
Args:
Expand All @@ -60,7 +64,9 @@ def backtest(
freq_day: int = 1,
trading_days_year: int = DEFAULT_TRADING_DAYS_YEAR,
shift_periods: int = 1,
commission_func: Callable[[pd.DataFrame, pd.DataFrame], float] = zero_commission,
commission_func: Callable[
[pd.DataFrame, pd.DataFrame], pd.DataFrame
] = zero_commission,
ann_borrow_rate: float = 0,
spread_pct: float = 0,
ann_risk_free_rate: float = DEFAULT_RISK_FREE_RATE,
Expand All @@ -69,7 +75,7 @@ def backtest(
]:
"""Backtest a trading strategy.
Strategy is simulated using the given weights, returns, and cost parameters.
Strategy is simulated using the given weights, prices, and cost parameters.
Zero costs are calculated by default: no commission, no borrowing, no spread.
To prevent look-ahead bias the returns will be shifted 1 interval by default relative to the weights during backtest.
Expand All @@ -89,7 +95,7 @@ def backtest(
Index should be a DatetimeIndex.
Shape must match returns.
prices:
Prices of the assets at each interval used to calculate returns ans costs.
Prices of the assets at each interval used to calculate returns and costs.
Each column should be the mark prices for a specific asset, with the column name being the asset name.
Column names should match weights.
Index should be a DatetimeIndex.
Expand Down Expand Up @@ -129,16 +135,16 @@ def backtest(
asset_cum = (1 + asset_rets).cumprod() - 1
asset_perf = pd.concat(
[
asset_rets.apply(
_ann_sharpe, periods=freq_year, risk_free_rate=ann_risk_free_rate
_ann_sharpe(
asset_rets, periods=freq_year, risk_free_rate=ann_risk_free_rate
),
asset_rets.apply(_ann_vol, periods=freq_year),
asset_rets.apply(_cagr, periods=freq_year),
asset_rets.apply(_max_drawdown),
_ann_vol(asset_rets, periods=freq_year),
_cagr(asset_rets, periods=freq_year),
_max_drawdown(asset_rets),
],
keys=["annual_sharpe", "annual_volatility", "cagr", "max_drawdown"],
axis=1,
)
) # type: ignore

# Backtest a cost-aware strategy as defined by the given weights:
# 1. Calc costs
Expand Down Expand Up @@ -173,12 +179,12 @@ def backtest(
# Evaluate the strategy asset-wise performance
strat_perf = pd.concat(
[
strat_rets.apply(
_ann_sharpe, periods=freq_year, risk_free_rate=ann_risk_free_rate
_ann_sharpe(
strat_rets, periods=freq_year, risk_free_rate=ann_risk_free_rate
),
strat_rets.apply(_ann_vol, periods=freq_year),
strat_rets.apply(_cagr, periods=freq_year),
strat_rets.apply(_max_drawdown),
_ann_vol(strat_rets, periods=freq_year),
_cagr(strat_rets, periods=freq_year),
_max_drawdown(strat_rets),
strat_ann_turnover,
_trade_count(weights) / strat_total_days,
],
Expand All @@ -191,7 +197,7 @@ def backtest(
"trades_per_day",
],
axis=1,
)
) # type: ignore

# Evaluate the strategy portfolio performance
port_rets = strat_rets.sum(axis=1)
Expand All @@ -213,8 +219,7 @@ def backtest(
index=["portfolio"],
)

# Combine the asset and strategy performance metrics
# into a single dataframe for comparison
# Combine the asset and strategy performance metrics into a single dataframe for comparison
perf = pd.concat(
[asset_perf, strat_perf],
keys=["asset", "strategy"],
Expand Down Expand Up @@ -264,7 +269,7 @@ def _ann_sharpe(
rets: pd.DataFrame | pd.Series,
risk_free_rate: float = DEFAULT_RISK_FREE_RATE,
periods: int = DEFAULT_TRADING_DAYS_YEAR,
) -> float:
) -> pd.DataFrame | pd.Series:
ann_rfr = (1 + risk_free_rate) ** (1 / periods) - 1
mu = rets.mean()
sigma = rets.std()
Expand All @@ -290,11 +295,9 @@ def _cagr(
) -> pd.DataFrame | pd.Series:
cumprod = (1 + rets).cumprod().dropna()
if len(cumprod) == 0:
return 0
return rets * 0

final = cumprod.iloc[-1]
if final <= 0:
return 0

n = len(cumprod) / periods
cagr = final ** (1 / n) - 1
Expand Down Expand Up @@ -327,8 +330,8 @@ def _turnover(
diff = weights.fillna(0).diff()
# Capital is fixed (uncompounded) for each interval so we can calculate the trade volume
# Sum the volume of the buy and sell trades
buy_volume = (diff.where(diff > 0, 0).abs() * capital).sum()
sell_volume = (diff.where(diff < 0, 0).abs() * capital).sum()
buy_volume = (diff.where(lambda x: x.gt(0), 0).abs() * capital).sum()
sell_volume = (diff.where(lambda x: x.lt(0), 0).abs() * capital).sum()
# Trade volume is the minimum of the buy and sell volumes
# Wrap in Series in case of scalar volume sum (when weights is a Series)
trade_volume = pd.concat(
Expand All @@ -341,7 +344,7 @@ def _turnover(
return turnover


def _trade_count(weights: pd.DataFrame | pd.Series) -> pd.DataFrame | pd.Series:
def _trade_count(weights: pd.DataFrame | pd.Series) -> int | pd.Series:
diff = weights.fillna(0).diff().abs() != 0
tx = diff.astype(int)
return tx.sum()
Expand Down
42 changes: 21 additions & 21 deletions example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -703,11 +703,11 @@
" <th>BTCUSDT</th>\n",
" <td>0.784522</td>\n",
" <td>0.676503</td>\n",
" <td>0.371469</td>\n",
" <td>0.697523</td>\n",
" <td>0.766293</td>\n",
" <td>2.082824</td>\n",
" <td>0.512587</td>\n",
" <td>1.616165</td>\n",
" <td>4.004693</td>\n",
" <td>0.632456</td>\n",
" <td>2.944701</td>\n",
" <td>1.000637</td>\n",
Expand All @@ -716,11 +716,11 @@
" <th>DOGEUSDT</th>\n",
" <td>1.021940</td>\n",
" <td>2.489353</td>\n",
" <td>1.826904</td>\n",
" <td>3.422070</td>\n",
" <td>0.923328</td>\n",
" <td>0.877637</td>\n",
" <td>1.058797</td>\n",
" <td>0.699507</td>\n",
" <td>1.134931</td>\n",
" <td>0.659074</td>\n",
" <td>2.331747</td>\n",
" <td>1.000745</td>\n",
Expand All @@ -729,11 +729,11 @@
" <th>ETHUSDT</th>\n",
" <td>1.029580</td>\n",
" <td>0.877444</td>\n",
" <td>0.695604</td>\n",
" <td>1.421963</td>\n",
" <td>0.793027</td>\n",
" <td>0.688909</td>\n",
" <td>0.385551</td>\n",
" <td>0.233350</td>\n",
" <td>0.420768</td>\n",
" <td>0.497637</td>\n",
" <td>4.970033</td>\n",
" <td>1.000637</td>\n",
Expand All @@ -742,11 +742,11 @@
" <th>MATICUSDT</th>\n",
" <td>1.409684</td>\n",
" <td>1.405686</td>\n",
" <td>1.879349</td>\n",
" <td>3.913546</td>\n",
" <td>0.879694</td>\n",
" <td>0.398154</td>\n",
" <td>0.615083</td>\n",
" <td>0.081175</td>\n",
" <td>0.124623</td>\n",
" <td>0.585446</td>\n",
" <td>5.556876</td>\n",
" <td>1.000708</td>\n",
Expand All @@ -768,11 +768,11 @@
" <th>XRPUSDT</th>\n",
" <td>0.673812</td>\n",
" <td>1.135899</td>\n",
" <td>0.190893</td>\n",
" <td>0.339995</td>\n",
" <td>0.832385</td>\n",
" <td>-0.063805</td>\n",
" <td>0.408337</td>\n",
" <td>-0.086424</td>\n",
" <td>-0.140457</td>\n",
" <td>0.659143</td>\n",
" <td>8.762681</td>\n",
" <td>1.000637</td>\n",
Expand All @@ -785,22 +785,22 @@
" asset \\\n",
" annual_sharpe annual_volatility cagr max_drawdown \n",
"symbol \n",
"BTCUSDT 0.784522 0.676503 0.371469 0.766293 \n",
"DOGEUSDT 1.021940 2.489353 1.826904 0.923328 \n",
"ETHUSDT 1.029580 0.877444 0.695604 0.793027 \n",
"MATICUSDT 1.409684 1.405686 1.879349 0.879694 \n",
"BTCUSDT 0.784522 0.676503 0.697523 0.766293 \n",
"DOGEUSDT 1.021940 2.489353 3.422070 0.923328 \n",
"ETHUSDT 1.029580 0.877444 1.421963 0.793027 \n",
"MATICUSDT 1.409684 1.405686 3.913546 0.879694 \n",
"SOLUSDT 0.784384 1.216761 0.250883 0.962695 \n",
"XRPUSDT 0.673812 1.135899 0.190893 0.832385 \n",
"XRPUSDT 0.673812 1.135899 0.339995 0.832385 \n",
"\n",
" strategy \\\n",
" annual_sharpe annual_volatility cagr max_drawdown, \n",
"symbol \n",
"BTCUSDT 2.082824 0.512587 1.616165 0.632456 \n",
"DOGEUSDT 0.877637 1.058797 0.699507 0.659074 \n",
"ETHUSDT 0.688909 0.385551 0.233350 0.497637 \n",
"MATICUSDT 0.398154 0.615083 0.081175 0.585446 \n",
"BTCUSDT 2.082824 0.512587 4.004693 0.632456 \n",
"DOGEUSDT 0.877637 1.058797 1.134931 0.659074 \n",
"ETHUSDT 0.688909 0.385551 0.420768 0.497637 \n",
"MATICUSDT 0.398154 0.615083 0.124623 0.585446 \n",
"SOLUSDT 0.533112 0.477024 0.174253 0.459495 \n",
"XRPUSDT -0.063805 0.408337 -0.086424 0.659143 \n",
"XRPUSDT -0.063805 0.408337 -0.140457 0.659143 \n",
"\n",
" \n",
" annual_turnover trades_per_day \n",
Expand Down Expand Up @@ -953,7 +953,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.7"
"version": "3.11.9"
}
},
"nbformat": 4,
Expand Down
1 change: 0 additions & 1 deletion tests/test_backtest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import sys
import os
from pathlib import PurePath
from functools import partial
import logging

import numpy as np
Expand Down

0 comments on commit 73dd1ae

Please sign in to comment.