Coverage for src / monte_neo / metrics / numba_funcs.py: 4%

209 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-28 16:27 +0200

1from __future__ import annotations 

2 

3import numpy as np 

4from numba import njit, prange 

5 

6 

7@njit 

8def extract_trades_fast( 

9 prices: np.ndarray, 

10 highs: np.ndarray, 

11 lows: np.ndarray, 

12 signals: np.ndarray, 

13 use_sl_tp: bool = False, 

14 sl_pct: float = 0.0, 

15 tp_pct: float = 0.0, 

16 commission_pct: float = 0.0, 

17 slippage_pct: float = 0.0, 

18) -> list[tuple[int, int, float, float, int, float, float]]: 

19 """Fast trade extraction using Numba JIT.""" 

20 results = [] 

21 

22 position = 0 

23 entry_idx = 0 

24 entry_price = 0.0 

25 

26 sl_price = 0.0 

27 tp_price = 0.0 

28 

29 blocked_signal = 0 

30 

31 for i in range(len(signals)): 

32 signal = int(signals[i]) 

33 price = prices[i] 

34 high = highs[i] 

35 low = lows[i] 

36 

37 if position == 0: 

38 if signal == 0: 

39 blocked_signal = 0 

40 elif signal != blocked_signal: 

41 # Open position 

42 position = signal 

43 entry_idx = i 

44 entry_price = price * (1.0 + float(position) * slippage_pct) 

45 

46 if use_sl_tp: 

47 if position == 1: # Long 

48 sl_price = entry_price * (1.0 - sl_pct / 100.0) 

49 tp_price = entry_price * (1.0 + tp_pct / 100.0) 

50 else: # Short 

51 sl_price = entry_price * (1.0 + sl_pct / 100.0) 

52 tp_price = entry_price * (1.0 - tp_pct / 100.0) 

53 else: 

54 # Check for SL/TP first 

55 hit_exit = False 

56 exit_price = price 

57 

58 if use_sl_tp: 

59 if position == 1: # Long 

60 if low <= sl_price: 

61 exit_price = sl_price 

62 hit_exit = True 

63 elif high >= tp_price: 

64 exit_price = tp_price 

65 hit_exit = True 

66 else: # Short 

67 if high >= sl_price: 

68 exit_price = sl_price 

69 hit_exit = True 

70 elif low <= tp_price: 

71 exit_price = tp_price 

72 hit_exit = True 

73 

74 # Check for signal exit if SL/TP not hit 

75 if not hit_exit and signal == -position: 

76 exit_price = price 

77 hit_exit = True 

78 

79 if hit_exit: 

80 # Close position 

81 exit_price_adj = exit_price * (1.0 - float(position) * slippage_pct) 

82 pnl = (exit_price_adj - entry_price) * position 

83 pnl_pct = (pnl / entry_price) - commission_pct * 2.0 # Round trip commission 

84 

85 results.append( 

86 (entry_idx, i, entry_price, exit_price, position, pnl, pnl_pct) 

87 ) 

88 

89 # Reset position and block current signal 

90 blocked_signal = signal 

91 position = 0 

92 

93 return results 

94 

95 

96@njit(parallel=True) 

97def calculate_batch_fast( 

98 prices: np.ndarray, 

99 highs: np.ndarray, 

100 lows: np.ndarray, 

101 signal_matrix: np.ndarray, 

102 use_sl_tp: bool, 

103 sl_pct: float, 

104 tp_pct: float, 

105 commission_pct: float = 0.0, 

106 slippage_pct: float = 0.0, 

107) -> np.ndarray: 

108 """Calculate basic metrics for a batch of signal sets in parallel.""" 

109 n_indicators = signal_matrix.shape[0] 

110 results = np.zeros((n_indicators, 4), dtype=np.float64) # total_return, max_dd, pf, n_trades 

111 

112 for i in prange(n_indicators): 

113 signals = signal_matrix[i] 

114 

115 # Simplified extraction for speed 

116 position: int = 0 

117 entry_price: float = 0.0 

118 sl_price: float = 0.0 

119 tp_price: float = 0.0 

120 blocked_signal: int = 0 

121 

122 total_pnl_pct = 0.0 

123 gross_profit = 0.0 

124 gross_loss = 0.0 

125 n_trades = 0 

126 

127 # Equity curve for drawdown 

128 equity = 1.0 

129 max_equity = 1.0 

130 max_dd = 0.0 

131 

132 for j in range(len(signals)): 

133 signal = int(signals[j]) 

134 price = prices[j] 

135 high = highs[j] 

136 low = lows[j] 

137 

138 if position == 0: 

139 if signal == 0: 

140 blocked_signal = 0 

141 elif signal != blocked_signal: 

142 position = signal 

143 entry_price = price 

144 if use_sl_tp: 

145 if position == 1: 

146 sl_price = entry_price * (1.0 - sl_pct / 100.0) 

147 tp_price = entry_price * (1.0 + tp_pct / 100.0) 

148 else: 

149 sl_price = entry_price * (1.0 + sl_pct / 100.0) 

150 tp_price = entry_price * (1.0 - tp_pct / 100.0) 

151 else: 

152 hit_exit = False 

153 exit_price = price 

154 

155 if use_sl_tp: 

156 if position == 1: 

157 if low <= sl_price: 

158 exit_price = sl_price 

159 hit_exit = True 

160 elif high >= tp_price: 

161 exit_price = tp_price 

162 hit_exit = True 

163 else: 

164 if high >= sl_price: 

165 exit_price = sl_price 

166 hit_exit = True 

167 elif low <= tp_price: 

168 exit_price = tp_price 

169 hit_exit = True 

170 

171 if not hit_exit and signal == -position: 

172 exit_price = price 

173 hit_exit = True 

174 

175 if hit_exit: 

176 pnl = (exit_price - entry_price) * position 

177 pnl_pct = pnl / entry_price 

178 total_pnl_pct += pnl_pct 

179 

180 if pnl > 0: gross_profit += pnl 

181 else: gross_loss += abs(pnl) 

182 

183 # Update equity and drawdown 

184 equity *= (1.0 + pnl_pct) 

185 if equity > max_equity: max_equity = equity 

186 dd = (max_equity - equity) / max_equity 

187 if dd > max_dd: max_dd = dd 

188 

189 blocked_signal = signal 

190 position = 0 

191 n_trades += 1 

192 

193 pf = gross_profit / gross_loss if gross_loss > 0 else 100.0 

194 results[i, 0] = total_pnl_pct 

195 results[i, 1] = max_dd 

196 results[i, 2] = pf 

197 results[i, 3] = n_trades 

198 

199 return results 

200 

201 

202@njit(parallel=True) 

203def calculate_batch_multi_price_fast( 

204 price_matrix: np.ndarray, 

205 high_matrix: np.ndarray, 

206 low_matrix: np.ndarray, 

207 signal_matrix: np.ndarray, 

208 use_sl_tp: bool, 

209 sl_pct: float, 

210 tp_pct: float, 

211 commission_pct: float = 0.0, 

212 slippage_pct: float = 0.0, 

213) -> np.ndarray: 

214 """Calculate basic metrics for a batch where each row has its own prices.""" 

215 n_rows = signal_matrix.shape[0] 

216 results = np.zeros((n_rows, 4), dtype=np.float64) # total_return, max_dd, pf, n_trades 

217 

218 for i in prange(n_rows): 

219 signals = signal_matrix[i] 

220 prices = price_matrix[i] 

221 highs = high_matrix[i] 

222 lows = low_matrix[i] 

223 

224 # Simplified extraction for speed 

225 position: int = 0 

226 entry_price: float = 0.0 

227 sl_price: float = 0.0 

228 tp_price: float = 0.0 

229 blocked_signal: int = 0 

230 

231 total_pnl_pct = 0.0 

232 gross_profit = 0.0 

233 gross_loss = 0.0 

234 n_trades = 0 

235 

236 # Equity curve for drawdown 

237 equity = 1.0 

238 max_equity = 1.0 

239 max_dd = 0.0 

240 

241 # We need to know the actual length of this row (ignoring padding) 

242 # We assume non-zero prices mean actual data 

243 row_len = signals.shape[0] 

244 while row_len > 0 and prices[row_len-1] == 0: 

245 row_len -= 1 

246 

247 for j in range(row_len): 

248 signal = int(signals[j]) 

249 price = prices[j] 

250 high = highs[j] 

251 low = lows[j] 

252 

253 if position == 0: 

254 if signal == 0: 

255 blocked_signal = 0 

256 elif signal != blocked_signal: 

257 position = signal 

258 entry_price = price 

259 if use_sl_tp: 

260 if position == 1: 

261 sl_price = entry_price * (1.0 - sl_pct / 100.0) 

262 tp_price = entry_price * (1.0 + tp_pct / 100.0) 

263 else: 

264 sl_price = entry_price * (1.0 + sl_pct / 100.0) 

265 tp_price = entry_price * (1.0 - tp_pct / 100.0) 

266 else: 

267 hit_exit = False 

268 exit_price = price 

269 

270 if use_sl_tp: 

271 if position == 1: 

272 if low <= sl_price: 

273 exit_price = sl_price 

274 hit_exit = True 

275 elif high >= tp_price: 

276 exit_price = tp_price 

277 hit_exit = True 

278 else: 

279 if high >= sl_price: 

280 exit_price = sl_price 

281 hit_exit = True 

282 elif low <= tp_price: 

283 exit_price = tp_price 

284 hit_exit = True 

285 

286 if not hit_exit and signal == -position: 

287 exit_price = price 

288 hit_exit = True 

289 

290 if hit_exit: 

291 pnl = (exit_price - entry_price) * position 

292 pnl_pct = pnl / entry_price 

293 total_pnl_pct += pnl_pct 

294 

295 if pnl > 0: gross_profit += pnl 

296 else: gross_loss += abs(pnl) 

297 

298 # Update equity and drawdown 

299 equity *= (1.0 + pnl_pct) 

300 if equity > max_equity: max_equity = equity 

301 dd = (max_equity - equity) / max_equity 

302 if dd > max_dd: max_dd = dd 

303 

304 blocked_signal = signal 

305 position = 0 

306 n_trades += 1 

307 

308 pf = gross_profit / gross_loss if gross_loss > 0 else 100.0 

309 results[i, 0] = total_pnl_pct 

310 results[i, 1] = max_dd 

311 results[i, 2] = pf 

312 results[i, 3] = n_trades 

313 

314 return results