2
2
3
3
namespace Stochastix \Command ;
4
4
5
+ use Psr \EventDispatcher \EventDispatcherInterface ;
5
6
use Stochastix \Domain \Backtesting \Dto \BacktestConfiguration ;
7
+ use Stochastix \Domain \Backtesting \Event \BacktestPhaseEvent ;
6
8
use Stochastix \Domain \Backtesting \Repository \BacktestResultRepositoryInterface ;
7
9
use Stochastix \Domain \Backtesting \Service \Backtester ;
8
10
use Stochastix \Domain \Backtesting \Service \BacktestResultSaver ;
16
18
use Symfony \Component \Console \Output \OutputInterface ;
17
19
use Symfony \Component \Console \Style \SymfonyStyle ;
18
20
use Symfony \Component \Stopwatch \Stopwatch ;
19
- use Symfony \Component \Stopwatch \StopwatchEvent ;
20
21
21
22
#[AsCommand(
22
23
name: 'stochastix:backtesting ' ,
@@ -34,7 +35,8 @@ public function __construct(
34
35
private readonly Backtester $ backtester ,
35
36
private readonly ConfigurationResolver $ configResolver ,
36
37
private readonly BacktestResultRepositoryInterface $ resultRepository ,
37
- private readonly BacktestResultSaver $ resultSaver
38
+ private readonly BacktestResultSaver $ resultSaver ,
39
+ private readonly EventDispatcherInterface $ eventDispatcher ,
38
40
) {
39
41
parent ::__construct ();
40
42
}
@@ -61,21 +63,38 @@ protected function execute(InputInterface $input, OutputInterface $output): int
61
63
$ strategyAlias = $ input ->getArgument ('strategy-alias ' );
62
64
63
65
$ stopwatch = new Stopwatch (true );
64
- $ stopwatch -> start ( ' backtest_execute ' ) ;
66
+ $ runId = null ;
65
67
66
- $ io ->title (sprintf ('🚀 Stochastix Backtester Initializing: %s 🚀 ' , $ strategyAlias ));
68
+ $ listener = function (BacktestPhaseEvent $ event ) use ($ stopwatch , &$ runId ) {
69
+ if ($ event ->runId !== $ runId ) {
70
+ return ;
71
+ }
72
+
73
+ $ phaseName = $ event ->phase ;
74
+
75
+ if ($ event ->eventType === 'start ' && !$ stopwatch ->isStarted ($ phaseName )) {
76
+ $ stopwatch ->start ($ phaseName );
77
+ } elseif ($ event ->eventType === 'stop ' && $ stopwatch ->isStarted ($ phaseName )) {
78
+ $ stopwatch ->stop ($ phaseName );
79
+ }
80
+ };
81
+
82
+ $ this ->eventDispatcher ->addListener (BacktestPhaseEvent::class, $ listener );
67
83
68
84
try {
85
+ $ io ->title (sprintf ('🚀 Stochastix Backtester Initializing: %s 🚀 ' , $ strategyAlias ));
86
+
87
+ $ stopwatch ->start ('configuration ' );
69
88
$ io ->text ('Resolving configuration... ' );
70
89
$ config = $ this ->configResolver ->resolve ($ input );
71
90
$ io ->text ('Configuration resolved. ' );
72
91
$ io ->newLine ();
92
+ $ stopwatch ->stop ('configuration ' );
73
93
74
94
if ($ savePath = $ input ->getOption ('save-config ' )) {
75
95
$ this ->saveConfigToJson ($ config , $ savePath );
76
96
$ io ->success ("Configuration saved to {$ savePath }. Exiting as requested. " );
77
- $ event = $ stopwatch ->stop ('backtest_execute ' );
78
- $ this ->displayExecutionTime ($ io , $ event );
97
+ $ this ->displayExecutionTime ($ io , $ stopwatch );
79
98
80
99
return Command::SUCCESS ;
81
100
}
@@ -104,27 +123,28 @@ protected function execute(InputInterface $input, OutputInterface $output): int
104
123
$ io ->definitionList (...$ definitions );
105
124
106
125
$ io ->section ('Starting Backtest Run... ' );
107
- $ results = $ this ->backtester ->run ($ config );
108
126
$ runId = $ this ->resultRepository ->generateRunId ($ config ->strategyAlias );
109
127
$ io ->note ("Generated Run ID: {$ runId }" );
110
128
129
+ $ results = $ this ->backtester ->run ($ config , $ runId );
130
+
131
+ $ stopwatch ->start ('saving ' );
111
132
$ this ->resultSaver ->save ($ runId , $ results );
133
+ $ stopwatch ->stop ('saving ' );
112
134
113
135
$ io ->section ('Backtest Performance Summary ' );
114
136
$ this ->displaySummaryStats ($ io , $ results );
115
137
$ this ->displayTradesLog ($ io , $ results ['closedTrades ' ]);
116
138
$ this ->displayOpenPositionsLog ($ io , $ results ['openPositions ' ] ?? []); // NEW
117
139
118
140
$ io ->newLine ();
119
- $ event = $ stopwatch ->stop ('backtest_execute ' );
120
- $ this ->displayExecutionTime ($ io , $ event );
141
+ $ this ->displayExecutionTime ($ io , $ stopwatch );
121
142
$ io ->newLine ();
122
143
$ io ->success (sprintf ('Backtest for "%s" finished successfully. ' , $ strategyAlias ));
123
144
124
145
return Command::SUCCESS ;
125
146
} catch (\Exception $ e ) {
126
- $ event = $ stopwatch ->stop ('backtest_execute ' );
127
- $ this ->displayExecutionTime ($ io , $ event , true );
147
+ $ this ->displayExecutionTime ($ io , $ stopwatch , true );
128
148
129
149
$ io ->error ([
130
150
'💥 An error occurred: ' ,
@@ -137,17 +157,47 @@ protected function execute(InputInterface $input, OutputInterface $output): int
137
157
}
138
158
139
159
return Command::FAILURE ;
160
+ } finally {
161
+ $ this ->eventDispatcher ->removeListener (BacktestPhaseEvent::class, $ listener );
140
162
}
141
163
}
142
164
143
- private function displayExecutionTime (SymfonyStyle $ io , StopwatchEvent $ event , bool $ errorOccurred = false ): void
165
+ private function displayExecutionTime (SymfonyStyle $ io , Stopwatch $ stopwatch , bool $ errorOccurred = false ): void
144
166
{
167
+ $ rows = [];
168
+ $ totalDuration = 0 ;
169
+ $ peakMemory = 0 ;
170
+
171
+ $ phases = ['configuration ' , 'initialization ' , 'loop ' , 'statistics ' , 'saving ' ];
172
+
173
+ foreach ($ phases as $ phase ) {
174
+ if ($ stopwatch ->isStarted ($ phase )) {
175
+ $ stopwatch ->stop ($ phase );
176
+ }
177
+
178
+ try {
179
+ $ event = $ stopwatch ->getEvent ($ phase );
180
+ $ duration = $ event ->getDuration ();
181
+ $ memory = $ event ->getMemory ();
182
+ $ totalDuration += $ duration ;
183
+ $ peakMemory = max ($ peakMemory , $ memory );
184
+
185
+ $ rows [] = [ucfirst ($ phase ), sprintf ('%.2f ms ' , $ duration ), sprintf ('%.2f MB ' , $ memory / (1024 ** 2 ))];
186
+ } catch (\LogicException ) {
187
+ // Event was not started/stopped, so we can't display it
188
+ continue ;
189
+ }
190
+ }
191
+
192
+ $ io ->section ('Execution Profile ' );
193
+ $ io ->table (['Phase ' , 'Duration ' , 'Memory ' ], $ rows );
194
+
145
195
$ messagePrefix = $ errorOccurred ? '📊 Backtest ran for ' : '📊 Backtest finished in ' ;
146
196
$ io ->writeln (sprintf (
147
- '%s: <info>%.2f ms</info> / Memory usage: <info>%.2f MB</info> ' ,
197
+ '%s: <info>%.2f ms</info> / Peak Memory usage: <info>%.2f MB</info> ' ,
148
198
$ messagePrefix ,
149
- $ event -> getDuration () ,
150
- $ event -> getMemory () / (1024 ** 2 )
199
+ $ totalDuration ,
200
+ $ peakMemory / (1024 ** 2 )
151
201
));
152
202
}
153
203
0 commit comments