From cd74e132ff4eaf1dff24fcd0ea511d6901ceb69a Mon Sep 17 00:00:00 2001 From: r3k4mn14r <8102483+r3k4mn14r@users.noreply.github.com> Date: Fri, 20 Oct 2023 00:07:11 +0000 Subject: [PATCH 1/2] feat: added a fills report --- nautilus_trader/analysis/reporter.py | 30 +++++++++++ tests/unit_tests/analysis/test_reports.py | 64 +++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/nautilus_trader/analysis/reporter.py b/nautilus_trader/analysis/reporter.py index b8ff2fd7ba10..0a36a9a98b7d 100644 --- a/nautilus_trader/analysis/reporter.py +++ b/nautilus_trader/analysis/reporter.py @@ -22,6 +22,7 @@ from nautilus_trader.core.datetime import unix_nanos_to_dt from nautilus_trader.model.enums import OrderStatus from nautilus_trader.model.events import AccountState +from nautilus_trader.model.events import OrderFilled from nautilus_trader.model.orders import Order from nautilus_trader.model.position import Position @@ -81,6 +82,35 @@ def generate_order_fills_report(orders: list[Order]) -> pd.DataFrame: return report + @staticmethod + def generate_fills_report(orders: list[Order]) -> pd.DataFrame: + """ + Generate a fills report. + + Parameters + ---------- + orders : list[Order] + The orders for the report. + + Returns + ------- + pd.DataFrame + + """ + if not orders: + return pd.DataFrame() + + fills = [OrderFilled.to_dict(e) for o in orders for e in o.events if isinstance(e, OrderFilled)] + if not fills: + return pd.DataFrame() + + report = pd.DataFrame(data=fills).set_index("client_order_id").sort_index() + report["ts_event"] = [unix_nanos_to_dt(ts_last or 0) for ts_last in report["ts_event"]] + report["ts_init"] = [unix_nanos_to_dt(ts_init) for ts_init in report["ts_init"]] + del report["type"] + + return report + @staticmethod def generate_positions_report(positions: list[Position]) -> pd.DataFrame: """ diff --git a/tests/unit_tests/analysis/test_reports.py b/tests/unit_tests/analysis/test_reports.py index f63714928474..5bee524f138f 100644 --- a/tests/unit_tests/analysis/test_reports.py +++ b/tests/unit_tests/analysis/test_reports.py @@ -27,6 +27,7 @@ from nautilus_trader.model.identifiers import AccountId from nautilus_trader.model.identifiers import PositionId from nautilus_trader.model.identifiers import StrategyId +from nautilus_trader.model.identifiers import TradeId from nautilus_trader.model.identifiers import TraderId from nautilus_trader.model.identifiers import Venue from nautilus_trader.model.objects import AccountBalance @@ -98,6 +99,13 @@ def test_generate_orders_fills_report_with_no_order_returns_emtpy_dataframe(self # Assert assert report.empty + def test_generate_fills_report_with_no_fills_returns_emtpy_dataframe(self): + # Arrange, Act + report = ReportProvider.generate_fills_report([]) + + # Assert + assert report.empty + def test_generate_positions_report_with_no_positions_returns_emtpy_dataframe(self): # Arrange, Act report = ReportProvider.generate_positions_report([]) @@ -201,6 +209,62 @@ def test_generate_order_fills_report(self): assert report.iloc[0]["avg_px"] == "0.80011" assert report.iloc[0]["slippage"] == "9.99999999995449e-06" + def test_generate_fills_report(self): + # Arrange + order1 = self.order_factory.limit( + AUDUSD_SIM.id, + OrderSide.BUY, + Quantity.from_int(1_500_000), + Price.from_str("0.80010"), + ) + + order1.apply(TestEventStubs.order_submitted(order1)) + order1.apply(TestEventStubs.order_accepted(order1)) + + partially_filled1 = TestEventStubs.order_filled( + order1, + trade_id=TradeId("E-19700101-0000-000-001-1"), + instrument=AUDUSD_SIM, + position_id=PositionId("P-1"), + strategy_id=StrategyId("S-1"), + last_qty=Quantity.from_int(1_000_000), + last_px=Price.from_str("0.80011"), + ) + + partially_filled2 = TestEventStubs.order_filled( + order1, + trade_id=TradeId("E-19700101-0000-000-001-2"), + instrument=AUDUSD_SIM, + position_id=PositionId("P-1"), + strategy_id=StrategyId("S-1"), + last_qty=Quantity.from_int(500_000), + last_px=Price.from_str("0.80011"), + ) + + order1.apply(partially_filled1) + order1.apply(partially_filled2) + + orders = [order1] + + # Act + report = ReportProvider.generate_fills_report(orders) + + # Assert + assert len(report) == 2 + assert report.index.name == "client_order_id" + assert report.index[0] == order1.client_order_id.value + assert report.iloc[0]["instrument_id"] == "AUD/USD.SIM" + assert report.iloc[0]["order_side"] == "BUY" + assert report.iloc[0]["order_type"] == "LIMIT" + assert report.iloc[0]["last_qty"] == "1000000" + assert report.iloc[0]["last_px"] == "0.80011" + assert report.index[1] == order1.client_order_id.value + assert report.iloc[1]["instrument_id"] == "AUD/USD.SIM" + assert report.iloc[1]["order_side"] == "BUY" + assert report.iloc[1]["order_type"] == "LIMIT" + assert report.iloc[1]["last_qty"] == "500000" + assert report.iloc[1]["last_px"] == "0.80011" + def test_generate_positions_report(self): # Arrange order1 = self.order_factory.market( From 6a7ce3c457b81d5aaeecad8f6739611069462a1d Mon Sep 17 00:00:00 2001 From: r3k4mn14r <8102483+r3k4mn14r@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:59:11 +0000 Subject: [PATCH 2/2] fix: ran pre-commit --- nautilus_trader/analysis/reporter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nautilus_trader/analysis/reporter.py b/nautilus_trader/analysis/reporter.py index 0a36a9a98b7d..9805c8ac93b0 100644 --- a/nautilus_trader/analysis/reporter.py +++ b/nautilus_trader/analysis/reporter.py @@ -100,7 +100,9 @@ def generate_fills_report(orders: list[Order]) -> pd.DataFrame: if not orders: return pd.DataFrame() - fills = [OrderFilled.to_dict(e) for o in orders for e in o.events if isinstance(e, OrderFilled)] + fills = [ + OrderFilled.to_dict(e) for o in orders for e in o.events if isinstance(e, OrderFilled) + ] if not fills: return pd.DataFrame()