ffn
ffn copied to clipboard
Sortino ratio incorrectly uses np.minimum
Hi all,
It looks like calc_stats uses ffn.core.calc_sortino_ratio and this has the below calc:
negative_returns = np.minimum(er[1:], 0.0)
In this case, it inadvertently includes zeroes when a value is not negative. As such, this artificially decreases the downside deviation and inflates the sortino ratio.
Example code below.
import pandas as pd
import numpy as np
import ffn
def calculate_sortino_ratios(prices, risk_free_rate=0.0, periods_per_year=252):
"""
Calculate Sortino ratio using different methods to demonstrate the differences
Parameters:
prices: DataFrame with asset prices
risk_free_rate: Annual risk-free rate (default 0)
periods_per_year: Number of trading periods per year (default 252 for daily data)
"""
# Get returns
returns = prices.pct_change().dropna()
# Calculate excess returns over risk-free rate
rf_daily = (1 + risk_free_rate) ** (1/periods_per_year) - 1
excess_returns = returns - rf_daily
# Method 1: FFN's built-in calculation
stats = prices.calc_stats()
ffn_sortino = stats.stats.loc['daily_sortino']
# Method 2: Matching FFN's calculation
avg_excess_return = excess_returns.mean()
neg_returns_ffn = np.minimum(excess_returns, 0.0)
downside_std_ffn = np.sqrt(np.mean(neg_returns_ffn ** 2))
annualized_return = (1 + avg_excess_return) ** periods_per_year - 1
annualized_downside_std_ffn = downside_std_ffn * np.sqrt(periods_per_year)
matching_ffn_sortino = annualized_return / annualized_downside_std_ffn
# Method 3: Traditional calculation (only negative returns)
neg_returns = excess_returns[excess_returns < 0]
downside_std = np.sqrt(np.mean(neg_returns ** 2))
annualized_downside_std = downside_std * np.sqrt(periods_per_year)
traditional_sortino = annualized_return / annualized_downside_std
return {
'FFN Built-in': ffn_sortino['aapl'],
'Matching FFN': matching_ffn_sortino['aapl'],
'Traditional': traditional_sortino['aapl']
}
# Example usage:
prices = ffn.get('aapl', start='2010-01-01')
sortino_ratios = calculate_sortino_ratios(prices)
print("\nSortino Ratios:")
for name, value in sortino_ratios.items():
print(f"{name}: {value:.4f}")