Coverage for src / jquantstats / _reports / _protocol.py: 100%

4 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-07 14:28 +0000

1"""Protocols describing the minimal interfaces required by the _reports subpackage.""" 

2 

3from __future__ import annotations 

4 

5from typing import Protocol, runtime_checkable 

6 

7import plotly.graph_objects as go 

8import polars as pl 

9 

10 

11@runtime_checkable 

12class StatsLike(Protocol): # pragma: no cover 

13 """Structural interface for the statistics methods used by :class:`~jquantstats._reports._data.Reports`.""" 

14 

15 def sharpe(self, periods: int | float | None = None) -> dict[str, float]: 

16 """Annualised Sharpe ratio per asset.""" 

17 ... 

18 

19 def smart_sharpe(self, periods: int | float | None = None) -> dict[str, float]: 

20 """Smart Sharpe ratio per asset.""" 

21 ... 

22 

23 def sortino(self, periods: int | float | None = None) -> dict[str, float]: 

24 """Annualised Sortino ratio per asset.""" 

25 ... 

26 

27 def adjusted_sortino(self, periods: int | float | None = None) -> dict[str, float]: 

28 """Adjusted Sortino (Sortino / √2) per asset.""" 

29 ... 

30 

31 def smart_sortino(self, periods: int | float | None = None) -> dict[str, float]: 

32 """Smart Sortino ratio per asset.""" 

33 ... 

34 

35 def omega(self, periods: int | float | None = None) -> dict[str, float]: 

36 """Omega ratio per asset.""" 

37 ... 

38 

39 def probabilistic_sharpe_ratio(self) -> dict[str, float]: 

40 """Probabilistic Sharpe ratio per asset.""" 

41 ... 

42 

43 def cagr(self, periods: int | float | None = None) -> dict[str, float]: 

44 """Compound annual growth rate per asset.""" 

45 ... 

46 

47 def comp(self) -> dict[str, float]: 

48 """Total compounded return per asset.""" 

49 ... 

50 

51 def exposure(self) -> dict[str, float]: 

52 """Market exposure (time in market) per asset.""" 

53 ... 

54 

55 def max_drawdown(self) -> dict[str, float]: 

56 """Maximum drawdown per asset.""" 

57 ... 

58 

59 def avg_drawdown(self) -> dict[str, float]: 

60 """Average drawdown per asset.""" 

61 ... 

62 

63 def max_drawdown_duration(self) -> dict[str, float]: 

64 """Maximum drawdown duration per asset.""" 

65 ... 

66 

67 def recovery_factor(self) -> dict[str, float]: 

68 """Recovery factor per asset.""" 

69 ... 

70 

71 def ulcer_index(self) -> dict[str, float]: 

72 """Ulcer index per asset.""" 

73 ... 

74 

75 def serenity_index(self) -> dict[str, float]: 

76 """Serenity index per asset.""" 

77 ... 

78 

79 def ulcer_performance_index(self) -> dict[str, float]: 

80 """Ulcer performance index per asset.""" 

81 ... 

82 

83 def calmar(self, periods: int | float | None = None) -> dict[str, float]: 

84 """Calmar ratio per asset.""" 

85 ... 

86 

87 def rar(self, periods: int | float = 252) -> dict[str, float]: 

88 """Risk-adjusted return per asset.""" 

89 ... 

90 

91 def risk_return_ratio(self) -> dict[str, float]: 

92 """Risk-return ratio per asset.""" 

93 ... 

94 

95 def gain_to_pain_ratio(self, aggregate: str | None = None) -> dict[str, float]: 

96 """Gain-to-pain ratio per asset.""" 

97 ... 

98 

99 def payoff_ratio(self) -> dict[str, float]: 

100 """Payoff ratio per asset.""" 

101 ... 

102 

103 def profit_factor(self) -> dict[str, float]: 

104 """Profit factor per asset.""" 

105 ... 

106 

107 def profit_ratio(self) -> dict[str, float]: 

108 """Profit ratio per asset.""" 

109 ... 

110 

111 def common_sense_ratio(self) -> dict[str, float]: 

112 """Common sense ratio per asset.""" 

113 ... 

114 

115 def cpc_index(self) -> dict[str, float]: 

116 """CPC index per asset.""" 

117 ... 

118 

119 def tail_ratio(self) -> dict[str, float]: 

120 """Tail ratio per asset.""" 

121 ... 

122 

123 def outlier_win_ratio(self) -> dict[str, float]: 

124 """Outlier win ratio per asset.""" 

125 ... 

126 

127 def outlier_loss_ratio(self) -> dict[str, float]: 

128 """Outlier loss ratio per asset.""" 

129 ... 

130 

131 def volatility(self, periods: int | float | None = None) -> dict[str, float]: 

132 """Annualised volatility per asset.""" 

133 ... 

134 

135 def value_at_risk(self, alpha: float = 0.05) -> dict[str, float]: 

136 """Value at Risk per asset.""" 

137 ... 

138 

139 def conditional_value_at_risk(self, sigma: float = 1.0, alpha: float = 0.05, **kwargs: float) -> dict[str, float]: 

140 """Conditional Value at Risk per asset.""" 

141 ... 

142 

143 def win_loss_ratio(self) -> dict[str, float]: 

144 """Win/loss ratio per asset.""" 

145 ... 

146 

147 def win_rate(self) -> dict[str, float]: 

148 """Win rate per asset.""" 

149 ... 

150 

151 def monthly_win_rate(self) -> dict[str, float]: 

152 """Monthly win rate per asset.""" 

153 ... 

154 

155 def avg_return(self) -> dict[str, float]: 

156 """Average return per asset.""" 

157 ... 

158 

159 def avg_win(self) -> dict[str, float]: 

160 """Average win per asset.""" 

161 ... 

162 

163 def avg_loss(self) -> dict[str, float]: 

164 """Average loss per asset.""" 

165 ... 

166 

167 def best(self) -> dict[str, float]: 

168 """Best period return per asset.""" 

169 ... 

170 

171 def worst(self) -> dict[str, float]: 

172 """Worst period return per asset.""" 

173 ... 

174 

175 def skew(self) -> dict[str, float]: 

176 """Skewness per asset.""" 

177 ... 

178 

179 def kurtosis(self) -> dict[str, float]: 

180 """Kurtosis per asset.""" 

181 ... 

182 

183 def consecutive_wins(self) -> dict[str, float]: 

184 """Maximum consecutive wins per asset.""" 

185 ... 

186 

187 def consecutive_losses(self) -> dict[str, float]: 

188 """Maximum consecutive losses per asset.""" 

189 ... 

190 

191 def kelly_criterion(self) -> dict[str, float]: 

192 """Kelly criterion per asset.""" 

193 ... 

194 

195 def risk_of_ruin(self) -> dict[str, float]: 

196 """Risk of ruin per asset.""" 

197 ... 

198 

199 def expected_return(self, aggregate: str | None = None) -> dict[str, float]: 

200 """Expected return per asset.""" 

201 ... 

202 

203 def greeks(self) -> dict[str, dict[str, float]]: 

204 """Alpha and beta per asset.""" 

205 ... 

206 

207 def r2(self) -> dict[str, float]: 

208 """R-squared per asset versus benchmark.""" 

209 ... 

210 

211 def treynor_ratio(self, periods: int | float | None = None) -> dict[str, float]: 

212 """Treynor ratio per asset.""" 

213 ... 

214 

215 def drawdown_details(self) -> dict[str, pl.DataFrame]: 

216 """Drawdown period details per asset.""" 

217 ... 

218 

219 def summary(self) -> pl.DataFrame: 

220 """Full summary DataFrame (one row per metric, one column per asset).""" 

221 ... 

222 

223 

224@runtime_checkable 

225class DataLike(Protocol): # pragma: no cover 

226 """Structural interface required by the :class:`~jquantstats._reports._data.Reports` class. 

227 

228 Any object satisfying this protocol can be passed as ``data`` without a 

229 concrete dependency on :class:`~jquantstats._data.Data`. 

230 """ 

231 

232 @property 

233 def stats(self) -> StatsLike: 

234 """Statistics facade.""" 

235 ... 

236 

237 @property 

238 def all(self) -> pl.DataFrame: 

239 """Combined DataFrame of date index and all return columns.""" 

240 ... 

241 

242 

243@runtime_checkable 

244class PlotsLike(Protocol): # pragma: no cover 

245 """Structural interface for the portfolio plots facade used by :class:`~jquantstats._reports._portfolio.Report`.""" 

246 

247 def snapshot(self) -> go.Figure: 

248 """NAV + drawdown snapshot figure.""" 

249 ... 

250 

251 def rolling_sharpe_plot(self) -> go.Figure: 

252 """Rolling Sharpe figure.""" 

253 ... 

254 

255 def rolling_volatility_plot(self) -> go.Figure: 

256 """Rolling volatility figure.""" 

257 ... 

258 

259 def annual_sharpe_plot(self) -> go.Figure: 

260 """Annual Sharpe figure.""" 

261 ... 

262 

263 def monthly_returns_heatmap(self) -> go.Figure: 

264 """Monthly returns heatmap figure.""" 

265 ... 

266 

267 def correlation_heatmap(self) -> go.Figure: 

268 """Correlation heatmap figure.""" 

269 ... 

270 

271 def lead_lag_ir_plot(self) -> go.Figure: 

272 """Lead/lag IR figure.""" 

273 ... 

274 

275 def trading_cost_impact_plot(self) -> go.Figure: 

276 """Trading cost impact figure.""" 

277 ... 

278 

279 

280@runtime_checkable 

281class PortfolioLike(Protocol): # pragma: no cover 

282 """Structural interface required by the :class:`~jquantstats._reports._portfolio.Report` class. 

283 

284 Any object satisfying this protocol can be passed as ``portfolio`` without a 

285 concrete dependency on :class:`~jquantstats.portfolio.Portfolio`. 

286 """ 

287 

288 prices: pl.DataFrame 

289 aum: float 

290 

291 @property 

292 def assets(self) -> list[str]: 

293 """Asset names.""" 

294 ... 

295 

296 @property 

297 def plots(self) -> PlotsLike: 

298 """Portfolio plots facade.""" 

299 ... 

300 

301 @property 

302 def stats(self) -> StatsLike: 

303 """Statistics facade.""" 

304 ... 

305 

306 def turnover_summary(self) -> pl.DataFrame: 

307 """Turnover summary DataFrame.""" 

308 ...