22
33namespace Stochastix \Command ;
44
5+ use Psr \EventDispatcher \EventDispatcherInterface ;
56use Stochastix \Domain \Backtesting \Dto \BacktestConfiguration ;
7+ use Stochastix \Domain \Backtesting \Event \BacktestPhaseEvent ;
68use Stochastix \Domain \Backtesting \Repository \BacktestResultRepositoryInterface ;
79use Stochastix \Domain \Backtesting \Service \Backtester ;
810use Stochastix \Domain \Backtesting \Service \BacktestResultSaver ;
1618use Symfony \Component \Console \Output \OutputInterface ;
1719use Symfony \Component \Console \Style \SymfonyStyle ;
1820use Symfony \Component \Stopwatch \Stopwatch ;
19- use Symfony \Component \Stopwatch \StopwatchEvent ;
2021
2122#[AsCommand(
2223 name: 'stochastix:backtesting ' ,
@@ -34,7 +35,8 @@ public function __construct(
3435 private readonly Backtester $ backtester ,
3536 private readonly ConfigurationResolver $ configResolver ,
3637 private readonly BacktestResultRepositoryInterface $ resultRepository ,
37- private readonly BacktestResultSaver $ resultSaver
38+ private readonly BacktestResultSaver $ resultSaver ,
39+ private readonly EventDispatcherInterface $ eventDispatcher ,
3840 ) {
3941 parent ::__construct ();
4042 }
@@ -61,21 +63,38 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6163 $ strategyAlias = $ input ->getArgument ('strategy-alias ' );
6264
6365 $ stopwatch = new Stopwatch (true );
64- $ stopwatch -> start ( ' backtest_execute ' ) ;
66+ $ runId = null ;
6567
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 );
6783
6884 try {
85+ $ io ->title (sprintf ('🚀 Stochastix Backtester Initializing: %s 🚀 ' , $ strategyAlias ));
86+
87+ $ stopwatch ->start ('configuration ' );
6988 $ io ->text ('Resolving configuration... ' );
7089 $ config = $ this ->configResolver ->resolve ($ input );
7190 $ io ->text ('Configuration resolved. ' );
7291 $ io ->newLine ();
92+ $ stopwatch ->stop ('configuration ' );
7393
7494 if ($ savePath = $ input ->getOption ('save-config ' )) {
7595 $ this ->saveConfigToJson ($ config , $ savePath );
7696 $ 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 );
7998
8099 return Command::SUCCESS ;
81100 }
@@ -104,27 +123,28 @@ protected function execute(InputInterface $input, OutputInterface $output): int
104123 $ io ->definitionList (...$ definitions );
105124
106125 $ io ->section ('Starting Backtest Run... ' );
107- $ results = $ this ->backtester ->run ($ config );
108126 $ runId = $ this ->resultRepository ->generateRunId ($ config ->strategyAlias );
109127 $ io ->note ("Generated Run ID: {$ runId }" );
110128
129+ $ results = $ this ->backtester ->run ($ config , $ runId );
130+
131+ $ stopwatch ->start ('saving ' );
111132 $ this ->resultSaver ->save ($ runId , $ results );
133+ $ stopwatch ->stop ('saving ' );
112134
113135 $ io ->section ('Backtest Performance Summary ' );
114136 $ this ->displaySummaryStats ($ io , $ results );
115137 $ this ->displayTradesLog ($ io , $ results ['closedTrades ' ]);
116138 $ this ->displayOpenPositionsLog ($ io , $ results ['openPositions ' ] ?? []); // NEW
117139
118140 $ io ->newLine ();
119- $ event = $ stopwatch ->stop ('backtest_execute ' );
120- $ this ->displayExecutionTime ($ io , $ event );
141+ $ this ->displayExecutionTime ($ io , $ stopwatch );
121142 $ io ->newLine ();
122143 $ io ->success (sprintf ('Backtest for "%s" finished successfully. ' , $ strategyAlias ));
123144
124145 return Command::SUCCESS ;
125146 } catch (\Exception $ e ) {
126- $ event = $ stopwatch ->stop ('backtest_execute ' );
127- $ this ->displayExecutionTime ($ io , $ event , true );
147+ $ this ->displayExecutionTime ($ io , $ stopwatch , true );
128148
129149 $ io ->error ([
130150 '💥 An error occurred: ' ,
@@ -137,17 +157,47 @@ protected function execute(InputInterface $input, OutputInterface $output): int
137157 }
138158
139159 return Command::FAILURE ;
160+ } finally {
161+ $ this ->eventDispatcher ->removeListener (BacktestPhaseEvent::class, $ listener );
140162 }
141163 }
142164
143- private function displayExecutionTime (SymfonyStyle $ io , StopwatchEvent $ event , bool $ errorOccurred = false ): void
165+ private function displayExecutionTime (SymfonyStyle $ io , Stopwatch $ stopwatch , bool $ errorOccurred = false ): void
144166 {
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+
145195 $ messagePrefix = $ errorOccurred ? '📊 Backtest ran for ' : '📊 Backtest finished in ' ;
146196 $ 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> ' ,
148198 $ messagePrefix ,
149- $ event -> getDuration () ,
150- $ event -> getMemory () / (1024 ** 2 )
199+ $ totalDuration ,
200+ $ peakMemory / (1024 ** 2 )
151201 ));
152202 }
153203
0 commit comments