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