Skip to content

Commit

Permalink
Misc pd/np compatibility stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
ranaroussi committed Oct 25, 2024
1 parent 3aaecd4 commit 8ce8784
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 43 deletions.
6 changes: 3 additions & 3 deletions quantstats/_plotting/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def plot_returns_bars(
hlw=None,
hlcolor="red",
hllabel="",
resample="A",
resample="YE",
title="Returns",
match_volatility=False,
log_scale=False,
Expand Down Expand Up @@ -1018,10 +1018,10 @@ def plot_distribution(
port["Monthly"] = port["Daily"].resample("ME").apply(apply_fnc)
port["Monthly"].ffill(inplace=True)

port["Quarterly"] = port["Daily"].resample("Q").apply(apply_fnc)
port["Quarterly"] = port["Daily"].resample("QE").apply(apply_fnc)
port["Quarterly"].ffill(inplace=True)

port["Yearly"] = port["Daily"].resample("A").apply(apply_fnc)
port["Yearly"] = port["Daily"].resample("YE").apply(apply_fnc)
port["Yearly"].ffill(inplace=True)

fig, ax = _plt.subplots(figsize=figsize)
Expand Down
14 changes: 7 additions & 7 deletions quantstats/_plotting/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,20 +552,20 @@ def yearly_returns(
title += " vs Benchmark"
benchmark = (
_utils._prepare_benchmark(benchmark, returns.index)
.resample("A")
.resample("YE")
.apply(_stats.comp)
.resample("A")
.resample("YE")
.last()
)

if prepare_returns:
returns = _utils._prepare_returns(returns)

if compounded:
returns = returns.resample("A").apply(_stats.comp)
returns = returns.resample("YE").apply(_stats.comp)
else:
returns = returns.resample("A").apply(_df.sum)
returns = returns.resample("A").last()
returns = returns.resample("YE").apply(_df.sum)
returns = returns.resample("YE").last()

fig = _core.plot_returns_bars(
returns,
Expand Down Expand Up @@ -646,9 +646,9 @@ def histogram(
title = "Weekly "
elif resample == "ME":
title = "Monthly "
elif resample == "Q":
elif resample == "QE":
title = "Quarterly "
elif resample == "A":
elif resample == "YE":
title = "Annual "
else:
title = ""
Expand Down
20 changes: 10 additions & 10 deletions quantstats/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def html(

if benchmark is not None:
yoy = _stats.compare(
returns, benchmark, "A", compounded=compounded, prepare_returns=False
returns, benchmark, "YE", compounded=compounded, prepare_returns=False
)
if isinstance(returns, _pd.Series):
yoy.columns = [benchmark_title, strategy_title, "Multiplier", "Won"]
Expand Down Expand Up @@ -859,7 +859,7 @@ def metrics(
# metrics['Prob. Sortino/√2 Ratio %'] = _stats.probabilistic_adjusted_sortino_ratio(df, rf, win_year, False) * pct
metrics["Smart Sortino/√2"] = metrics["Smart Sortino"] / _sqrt(2)
# metrics['Prob. Smart Sortino/√2 Ratio %'] = _stats.probabilistic_adjusted_sortino_ratio(df, rf, win_year, False, True) * pct
metrics["Omega"] = _stats.omega(df, rf, 0.0, win_year)
metrics["Omega"] = _stats.omega(df["returns"], rf, 0.0, win_year)

metrics["~~~~~~~~"] = blank
metrics["Max Drawdown %"] = blank
Expand Down Expand Up @@ -936,7 +936,7 @@ def metrics(
_stats.expected_return(df, compounded=compounded, aggregate="ME", prepare_returns=False) * pct
)
metrics["Expected Yearly %%"] = (
_stats.expected_return(df, compounded=compounded, aggregate="A", prepare_returns=False) * pct
_stats.expected_return(df, compounded=compounded, aggregate="YE", prepare_returns=False) * pct
)
metrics["Kelly Criterion %"] = (
_stats.kelly_criterion(df, prepare_returns=False) * pct
Expand All @@ -959,9 +959,9 @@ def metrics(
metrics["Gain/Pain Ratio"] = _stats.gain_to_pain_ratio(df, rf)
metrics["Gain/Pain (1M)"] = _stats.gain_to_pain_ratio(df, rf, "ME")
# if mode.lower() == 'full':
# metrics['GPR (3M)'] = _stats.gain_to_pain_ratio(df, rf, "Q")
# metrics['GPR (3M)'] = _stats.gain_to_pain_ratio(df, rf, "QE")
# metrics['GPR (6M)'] = _stats.gain_to_pain_ratio(df, rf, "2Q")
# metrics['GPR (1Y)'] = _stats.gain_to_pain_ratio(df, rf, "A")
# metrics['GPR (1Y)'] = _stats.gain_to_pain_ratio(df, rf, "YE")
metrics["~~~~~~~"] = blank

metrics["Payoff Ratio"] = _stats.payoff_ratio(df, prepare_returns=False)
Expand Down Expand Up @@ -1013,10 +1013,10 @@ def metrics(
_stats.worst(df, aggregate="ME", prepare_returns=False) * pct
)
metrics["Best Year %"] = (
_stats.best(df, compounded=compounded, aggregate="A", prepare_returns=False) * pct
_stats.best(df, compounded=compounded, aggregate="YE", prepare_returns=False) * pct
)
metrics["Worst Year %"] = (
_stats.worst(df, compounded=compounded, aggregate="A", prepare_returns=False) * pct
_stats.worst(df, compounded=compounded, aggregate="YE", prepare_returns=False) * pct
)

# dd
Expand All @@ -1041,10 +1041,10 @@ def metrics(
_stats.win_rate(df, compounded=compounded, aggregate="ME", prepare_returns=False) * pct
)
metrics["Win Quarter %%"] = (
_stats.win_rate(df, compounded=compounded, aggregate="Q", prepare_returns=False) * pct
_stats.win_rate(df, compounded=compounded, aggregate="QE", prepare_returns=False) * pct
)
metrics["Win Year %%"] = (
_stats.win_rate(df, compounded=compounded, aggregate="A", prepare_returns=False) * pct
_stats.win_rate(df, compounded=compounded, aggregate="YE", prepare_returns=False) * pct
)

if "benchmark" in df:
Expand Down Expand Up @@ -1574,7 +1574,7 @@ def _download_html(html, filename="quantstats-tearsheet.html"):
" ",
"""<script>
var bl=new Blob(['{{html}}'],{type:"text/html"});
var a=document.createElement("a");
var a=document.createElement("YE");
a.href=URL.createObjectURL(bl);
a.download="{{filename}}";
a.hidden=true;document.body.appendChild(a);
Expand Down
32 changes: 15 additions & 17 deletions quantstats/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ def get_outliers(data):
"Daily": get_outliers(daily),
"Weekly": get_outliers(daily.resample("W-MON").apply(apply_fnc)),
"Monthly": get_outliers(daily.resample("ME").apply(apply_fnc)),
"Quarterly": get_outliers(daily.resample("Q").apply(apply_fnc)),
"Yearly": get_outliers(daily.resample("A").apply(apply_fnc)),
"Quarterly": get_outliers(daily.resample("QE").apply(apply_fnc)),
"Yearly": get_outliers(daily.resample("YE").apply(apply_fnc)),
}


Expand All @@ -93,17 +93,17 @@ def expected_return(returns, aggregate=None, compounded=True, prepare_returns=Tr
if prepare_returns:
returns = _utils._prepare_returns(returns)
returns = _utils.aggregate_returns(returns, aggregate, compounded)
return _np.product(1 + returns) ** (1 / len(returns)) - 1
return _np.prod(1 + returns) ** (1 / len(returns)) - 1


def geometric_mean(retruns, aggregate=None, compounded=True):
def geometric_mean(returns, aggregate=None, compounded=True):
"""Shorthand for expected_return()"""
return expected_return(retruns, aggregate, compounded)
return expected_return(returns, aggregate, compounded)


def ghpr(retruns, aggregate=None, compounded=True):
def ghpr(returns, aggregate=None, compounded=True):
"""Shorthand for expected_return()"""
return expected_return(retruns, aggregate, compounded)
return expected_return(returns, aggregate, compounded)


def outliers(returns, quantile=0.95):
Expand Down Expand Up @@ -410,12 +410,7 @@ def probabilistic_ratio(
n = len(series)

sigma_sr = _np.sqrt(
(
1
+ (0.5 * base**2)
- (skew_no * base)
+ (((kurtosis_no - 3) / 4) * base**2)
)
(1 + (0.5 * base**2) - (skew_no * base) + (((kurtosis_no - 3) / 4) * base**2))
/ (n - 1)
)

Expand Down Expand Up @@ -493,8 +488,8 @@ def omega(returns, rf=0.0, required_return=0.0, periods=252):
return_threshold = (1 + required_return) ** (1.0 / periods) - 1

returns_less_thresh = returns - return_threshold
numer = returns_less_thresh[returns_less_thresh > 0.0].sum().values[0]
denom = -1.0 * returns_less_thresh[returns_less_thresh < 0.0].sum().values[0]
numer = returns_less_thresh[returns_less_thresh > 0.0].sum()
denom = -1.0 * returns_less_thresh[returns_less_thresh < 0.0].sum()

if denom > 0.0:
return numer / denom
Expand Down Expand Up @@ -747,7 +742,7 @@ def outlier_loss_ratio(returns, quantile=0.01, prepare_returns=True):
return returns.quantile(quantile).mean() / returns[returns < 0].mean()


def recovery_factor(returns, rf=0., prepare_returns=True):
def recovery_factor(returns, rf=0.0, prepare_returns=True):
"""Measures how fast the strategy recovers from drawdowns"""
if prepare_returns:
returns = _utils._prepare_returns(returns)
Expand Down Expand Up @@ -910,7 +905,10 @@ def information_ratio(returns, benchmark, prepare_returns=True):
returns = _utils._prepare_returns(returns)
diff_rets = returns - _utils._prepare_benchmark(benchmark, returns.index)

return diff_rets.mean() / diff_rets.std()
std = diff_rets.std()
if std != 0:
return diff_rets.mean() / diff_rets.std()
return 0


def greeks(returns, benchmark, periods=252.0, prepare_returns=True):
Expand Down
22 changes: 16 additions & 6 deletions quantstats/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def multi_shift(df, shift=3):
dfs = [df.shift(i) for i in _np.arange(shift)]
for ix, dfi in enumerate(dfs[1:]):
dfs[ix + 1].columns = [str(col) for col in dfi.columns + str(ix + 1)]
return _pd.concat(dfs, 1, sort=True)
return _pd.concat(dfs, axis=1, sort=True)


def to_returns(prices, rf=0.0):
Expand Down Expand Up @@ -133,7 +133,7 @@ def aggregate_returns(returns, period=None, compounded=True):
if "quarter" in period:
return group_returns(returns, index.quarter, compounded=compounded)

if period == "A" or any(x in period for x in ["year", "eoy", "yoy"]):
if period == "YE" or any(x in period for x in ["year", "eoy", "yoy"]):
return group_returns(returns, index.year, compounded=compounded)

if "week" in period:
Expand All @@ -145,7 +145,7 @@ def aggregate_returns(returns, period=None, compounded=True):
if "eom" in period or period == "ME":
return group_returns(returns, [index.year, index.month], compounded=compounded)

if "eoq" in period or period == "Q":
if "eoq" in period or period == "QE":
return group_returns(
returns, [index.year, index.quarter], compounded=compounded
)
Expand Down Expand Up @@ -179,7 +179,9 @@ def to_excess_returns(returns, rf, nperiods=None):
# deannualize
rf = _np.power(1 + rf, 1.0 / nperiods) - 1.0

return returns - rf
df = returns - rf
df = df.tz_localize(None)
return df


def _prepare_prices(data, base=1.0):
Expand All @@ -198,6 +200,7 @@ def _prepare_prices(data, base=1.0):
if isinstance(data, (_pd.DataFrame, _pd.Series)):
data = data.fillna(0).replace([_np.inf, -_np.inf], float("NaN"))

data = data.tz_localize(None)
return data


Expand Down Expand Up @@ -227,19 +230,26 @@ def _prepare_returns(data, rf=0.0, nperiods=None):
if function not in unnecessary_function_calls:
if rf > 0:
return to_excess_returns(data, rf, nperiods)

data = data.tz_localize(None)

This comment has been minimized.

Copy link
@g199209

g199209 Nov 24, 2024

This may raise error when data is pd.Series. See #384

return data


def download_returns(ticker, period="max", proxy=None):
params = {
"tickers": ticker,
"proxy": proxy,
"auto_adjust": True,
"multi_level_index": False,
"progress": False,
}
if isinstance(period, _pd.DatetimeIndex):
params["start"] = period[0]
else:
params["period"] = period
return _yf.download(**params)["Close"].pct_change()
df = _yf.download(**params)["Close"].pct_change()
df = df.tz_localize(None)
return df


def _prepare_benchmark(benchmark=None, period="max", rf=0.0, prepare_returns=True):
Expand Down Expand Up @@ -271,7 +281,7 @@ def _prepare_benchmark(benchmark=None, period="max", rf=0.0, prepare_returns=Tru
)
benchmark = benchmark[benchmark.index.isin(period)]

benchmark.index = benchmark.index.tz_localize(None)
benchmark = benchmark.tz_localize(None)

if prepare_returns:
return _prepare_returns(benchmark.dropna(), rf=rf)
Expand Down

0 comments on commit 8ce8784

Please sign in to comment.