From 0a8cfb79e245462cbb05e9a452302930cb62f035 Mon Sep 17 00:00:00 2001 From: Vinayak Tiwari <84216407+VinayakTiwari1103@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:37:53 +0530 Subject: [PATCH] [backtests]: Optimize SimulatedExecutionEngine and enhance ExecutionEngine structure Enhanced ExecutionEngine with ABC refactor, optimized order sorting, added docstrings/type hints, exception handling, and PEP8 cleanups for better performance and clarity. --- gs_quant/backtests/execution_engine.py | 62 +++++++++++++++++++++----- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/gs_quant/backtests/execution_engine.py b/gs_quant/backtests/execution_engine.py index c0e3242c..0469e83d 100644 --- a/gs_quant/backtests/execution_engine.py +++ b/gs_quant/backtests/execution_engine.py @@ -14,35 +14,73 @@ under the License. """ -from gs_quant.backtests.data_handler import DataHandler -from gs_quant.backtests.event import * import datetime as dt +import bisect +from typing import List + +from gs_quant.backtests.data_handler import DataHandler +from gs_quant.backtests.event import OrderEvent, OrderBase, FillEvent +from abc import ABC -class ExecutionEngine(object): + +class ExecutionEngine(ABC): + """Abstract base class for execution engines.""" pass class SimulatedExecutionEngine(ExecutionEngine): + """ + Simulates order execution based on market data. + + Attributes: + data_handler (DataHandler): Provides market data for execution. + orders (List[OrderEvent]): Sorted list of submitted orders by execution end time. + """ + def __init__(self, data_handler: DataHandler): self.data_handler = data_handler - self.orders = [] + self.orders: List[OrderEvent] = [] def submit_order(self, order: OrderEvent): - self.orders.append(order) - self.orders.sort(key=lambda e: e.order.execution_end_time()) + """ + Submit an order and maintain sorted execution order. - def ping(self, state: dt.datetime): + Args: + order (OrderEvent): The order to be submitted. + """ + bisect.insort(self.orders, order, key=lambda e: e.order.execution_end_time()) + + def ping(self, state: dt.datetime) -> List[FillEvent]: + """ + Process and fill orders whose execution time has passed. + + Args: + state (datetime): Current simulation time. + + Returns: + List[FillEvent]: List of filled order events. + """ fill_events = [] while self.orders: - order: OrderBase = self.orders[0].order + order_event = self.orders[0] + order: OrderBase = order_event.order end_time = order.execution_end_time() + if end_time > state: break - else: - fill = FillEvent(order=order, - filled_price=order.execution_price(self.data_handler), - filled_units=order.execution_quantity()) + + try: + fill = FillEvent( + order=order, + filled_price=order.execution_price(self.data_handler), + filled_units=order.execution_quantity() + ) fill_events.append(fill) + except Exception as e: + # Log or handle execution failure gracefully + print(f"Error processing order {order}: {e}") + finally: self.orders.pop(0) + return fill_events