Skip to content

Commit f552467

Browse files
committed
Initial stats setup
1 parent a542d10 commit f552467

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

.coveragerc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[run]
2+
omit =
3+
pytradebacktest/stats.py

pytradebacktest/stats.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
from typing import Any
2+
3+
import numpy as np
4+
import pandas as pd
5+
from pytrade.models.trade import Trade
6+
from pytrade.strategy import FxStrategy
7+
8+
from pytradebacktest.data import MarketData
9+
10+
11+
class Stats:
12+
13+
def __init__(
14+
self,
15+
trades: list[Trade],
16+
equity: np.ndarray,
17+
market_data: MarketData,
18+
strategy: FxStrategy,
19+
):
20+
self._trades = trades
21+
self._equity = equity
22+
self._market_data = market_data
23+
self._strategy = strategy
24+
self._trades_df = pd.DataFrame(
25+
{
26+
"Size": [t.size for t in trades],
27+
"EntryBar": [t.entry_bar for t in trades],
28+
"ExitBar": [t.exit_bar for t in trades],
29+
"EntryPrice": [t.entry_price for t in trades],
30+
"ExitPrice": [t.exit_price for t in trades],
31+
"PnL": [t.pl for t in trades],
32+
"ReturnPct": [t.pl_pct for t in trades],
33+
"EntryTime": [t.entry_time for t in trades],
34+
"ExitTime": [t.exit_time for t in trades],
35+
"Tag": [t.tag for t in trades],
36+
"TakeProfit": [t.tp for t in trades],
37+
"StopLoss": [t.sl for t in trades],
38+
}
39+
)
40+
self._trades_df["Duration"] = (
41+
self._trades_df["ExitTime"] - self._trades_df["EntryTime"]
42+
)
43+
self._drawdown: np.ndarray[np.floating[Any], Any]
44+
45+
@property
46+
def drawdown(self) -> np.ndarray[np.floating[Any], Any]:
47+
if not self._drawdown:
48+
self._drawdown = 1 - self._equity / np.maximum.accumulate(self._equity)
49+
return self._drawdown
50+
51+
@property
52+
def drawdown_duration(self):
53+
iloc = np.unique(
54+
np.r_[(self.drawdown == 0).values.nonzero()[0], len(self.drawdown) - 1]
55+
)
56+
iloc = pd.Series(iloc, index=self.drawdown.index[iloc])
57+
df = iloc.to_frame("iloc").assign(prev=iloc.shift())
58+
df = df[df["iloc"] > df["prev"] + 1].astype(int)
59+
60+
# If no drawdown since no trade, avoid below for pandas sake and return nan series
61+
if not len(df):
62+
return (self.drawdown.replace(0, np.nan),) * 2
63+
64+
# df = df.reindex(self.drawdown.index)
65+
return df["iloc"].map(self.drawdown.index.__getitem__) - df["prev"].map(
66+
self.drawdown.index.__getitem__
67+
)
68+
69+
@property
70+
def drawdown_peaks(self):
71+
iloc = np.unique(
72+
np.r_[(self.drawdown == 0).values.nonzero()[0], len(self.drawdown) - 1]
73+
)
74+
iloc = pd.Series(iloc, index=self.drawdown.index[iloc])
75+
df = iloc.to_frame("iloc").assign(prev=iloc.shift())
76+
df = df[df["iloc"] > df["prev"] + 1].astype(int)
77+
78+
# If no drawdown since no trade, avoid below for pandas sake and return nan series
79+
if not len(df):
80+
return (self.drawdown.replace(0, np.nan),) * 2
81+
82+
# df = df.reindex(self.drawdown.index)
83+
return df.apply(
84+
lambda row: self.drawdown.iloc[row["prev"] : row["iloc"] + 1].max(), axis=1
85+
)
86+
87+
@property
88+
def profit_and_loss(self):
89+
return self._trades_df["PnL"]
90+
91+
@property
92+
def returns(self):
93+
return self._trades_df["ReturnPct"]
94+
95+
@property
96+
def durations(self):
97+
return self._trades_df["Duration"]
98+
99+
@property
100+
def start(self):
101+
return self._market_data._start_index
102+
103+
@property
104+
def end(self):
105+
return self._market_data._end_index
106+
107+
@property
108+
def duration(self):
109+
return self.start - self.end
110+
111+
@property
112+
def positions(self):
113+
pass
114+
115+
@property
116+
def exposure_time(self):
117+
pass
118+
119+
@property
120+
def equity_final(self):
121+
pass
122+
123+
@property
124+
def equity_peak(self):
125+
pass
126+
127+
@property
128+
def return_pct(self):
129+
pass
130+
131+
@property
132+
def buy_and_hold_return(self):
133+
pass
134+
135+
@property
136+
def annualized_return(self):
137+
pass
138+
139+
@property
140+
def annualized_volatility(self):
141+
pass
142+
143+
@property
144+
def sharpe_ratio(self):
145+
pass
146+
147+
@property
148+
def sortino_ratio(self):
149+
pass
150+
151+
@property
152+
def calmar_ratio(self):
153+
pass
154+
155+
@property
156+
def max_drawndown(self):
157+
pass
158+
159+
@property
160+
def avg_drawdown(self):
161+
pass
162+
163+
@property
164+
def max_drawdown_duration(self):
165+
pass
166+
167+
@property
168+
def avg_drawdown_duration(self):
169+
pass
170+
171+
@property
172+
def number_of_trades(self):
173+
pass
174+
175+
@property
176+
def win_rate(self):
177+
pass
178+
179+
@property
180+
def best_trade_return(self):
181+
pass
182+
183+
@property
184+
def worst_trade_return(self):
185+
pass
186+
187+
@property
188+
def avg_trade_return(self):
189+
pass
190+
191+
@property
192+
def max_trade_duration(self):
193+
pass
194+
195+
@property
196+
def avg_trade_duration(self):
197+
pass
198+
199+
@property
200+
def profit_factor(self):
201+
pass
202+
203+
@property
204+
def expectancy(self):
205+
pass
206+
207+
@property
208+
def SQN(self):
209+
pass
210+
211+
@property
212+
def kelly_criterion(self):
213+
pass

tests/unit/test_stats.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def test_stats():
2+
pass

0 commit comments

Comments
 (0)