Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fills report #1289

Merged
merged 5 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions nautilus_trader/analysis/reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -81,6 +82,37 @@ 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:
"""
Expand Down
64 changes: 64 additions & 0 deletions tests/unit_tests/analysis/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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([])
Expand Down Expand Up @@ -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(
Expand Down