-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecution.py
More file actions
118 lines (98 loc) · 3.84 KB
/
Copy pathexecution.py
File metadata and controls
118 lines (98 loc) · 3.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
"""
Execution handler module for simulating order execution.
"""
from abc import ABC, abstractmethod
from queue import Queue
import logging
from event import OrderEvent, FillEvent
from data import DataHandler
# Configure console logging
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)
class ExecutionHandler(ABC):
"""
Abstract base class for execution handlers.
Provides an interface for executing orders and generating fill events.
"""
@abstractmethod
def executeOrder(self, event: OrderEvent) -> None:
"""
Takes an OrderEvent and executes it, producing a FillEvent
that gets placed onto the events queue.
"""
pass
class SimulatedExecutionHandler(ExecutionHandler):
"""
Simulated execution handler that converts all order events into
fill events with simulated slippage and commission.
"""
def __init__(
self,
eventsQueue: Queue,
dataHandler: DataHandler,
fixed_commission: float = 0.001,
slippage_pct: float = 0.0005,
):
"""
Initialises the handler, saving the events queue and data handler.
Args:
eventsQueue: The shared event queue.
dataHandler: Supplies the latest bar used as the execution price.
fixed_commission: Flat commission charged per fill.
slippage_pct: Fraction the fill price moves against the order, e.g.
0.0005 for 5 bps. LONG fills pay more and SHORT fills receive
less; EXIT fills are modelled without slippage.
"""
self.eventsQueue = eventsQueue
self.dataHandler = dataHandler
self.fixed_commission = fixed_commission
self.slippage_pct = slippage_pct
def executeOrder(self, event: OrderEvent) -> None:
"""
Converts OrderEvent to FillEvent.
"""
if event.type != "ORDER":
return
latest_bar = self.dataHandler.getLatestBar(event.symbol)
# If no bar data is available, we cannot execute the order in this simulation.
if not latest_bar or "close" not in latest_bar:
logger.warning(
f"No price data available for {event.symbol}. Cannot execute order."
)
return
base_price = float(latest_bar["close"])
direction = event.direction
# Apply the configured slippage to the base price.
# LONG: pay more (+slippage)
# SHORT: receive less (-slippage)
# EXIT: For simplicity, assume worst-case execution if we don't know the exact side
# In a real system, EXIT would check current position to determine if it's a buy or sell.
slippage_pct = self.slippage_pct
if direction == "LONG":
fill_price = base_price * (1 + slippage_pct)
elif direction == "SHORT":
fill_price = base_price * (1 - slippage_pct)
elif direction == "EXIT":
fill_price = (
base_price # No slippage for EXIT orders in this simplified model
)
else:
fill_price = base_price
slippage_value = abs(fill_price - base_price)
# Create FillEvent
fill_event = FillEvent(
symbol=event.symbol,
timestamp=event.timestamp,
quantity=event.quantity,
direction=event.direction,
fillPrice=fill_price,
commission=self.fixed_commission,
slippage=slippage_value,
)
# Log the fill
logger.info(
f"FILLED {fill_event.timestamp} {fill_event.direction} {fill_event.quantity} {fill_event.symbol} "
f"@ {fill_event.fillPrice:.4f} (comm: {fill_event.commission}, slippage: {fill_event.slippage:.4f})"
)
# Put the FillEvent onto the queue
self.eventsQueue.put(fill_event)