1- from datetime import datetime
2- from typing import Dict , List , Optional , cast
1+ from typing import Dict , List , Optional
32
43import pandas as pd
54
@@ -24,7 +23,7 @@ class Broker:
2423 Typical usage:
2524 broker = Broker(start_capital=100000, fees=0.001)
2625 broker.place_order(Order("AAPL", 100, OrderType.OPEN))
27- broker.update(next_bar, next_datetime )
26+ broker.update(next_bar, next_timestamp )
2827 """
2928
3029 def __init__ (
@@ -64,9 +63,9 @@ def __init__(
6463 self ._open_orders : List [Order ] = []
6564
6665 self ._current_bar : pd .DataFrame = pd .DataFrame ()
67- self ._current_datetime : Optional [datetime ] = None
66+ self ._current_timestamp : Optional [pd . Timestamp ] = None
6867 self ._previous_bar : pd .DataFrame = pd .DataFrame ()
69- self ._previous_datetime : Optional [datetime ] = None
68+ self ._previous_timestamp : Optional [pd . Timestamp ] = None
7069
7170 self ._long_only = long_only
7271 self ._short_fee_rate = short_fee_rate
@@ -75,8 +74,8 @@ def __init__(
7574 self ._benchmark = benchmark
7675 self ._benchmark_size = 0.0
7776
78- self ._history : Dict [str , List [float ]] = {
79- "date " : [],
77+ self ._history : Dict [str , List [float | int | pd . Timestamp ]] = {
78+ "timestamp " : [],
8079 "cash" : [],
8180 "long_position_value" : [],
8281 "short_position_value" : [],
@@ -90,7 +89,7 @@ def _update_history(self):
9089 """
9190 Updates the history dictionary with the current portfolio state.
9291 """
93- self ._history ["date " ].append (self ._current_datetime )
92+ self ._history ["timestamp " ].append (self ._current_timestamp )
9493 self ._history ["cash" ].append (self ._cash )
9594 self ._history ["long_position_value" ].append (self .long_position_value )
9695 self ._history ["short_position_value" ].append (self .short_position_value )
@@ -164,7 +163,7 @@ def _get_price_for_order(self, order: Order, bar: pd.DataFrame) -> float | None:
164163 raise ValueError (f"Unknown order type { order .order_type } " )
165164
166165 def _update_closed_positions (
167- self , ticker : str , size : float , price : float , datetime : datetime
166+ self , ticker : str , size : float , price : float , timestamp : pd . Timestamp
168167 ):
169168 """
170169 Updates the list of closed positions for a given trade.
@@ -178,7 +177,7 @@ def _update_closed_positions(
178177 ticker (str): The ticker symbol of the position
179178 size (float): Position size (positive for long, negative for short)
180179 price (float): The current closing/reduction price
181- datetime (datetime ): Timestamp of the closing/reduction
180+ timestamp (timestamp ): Timestamp of the closing/reduction
182181 """
183182 if (
184183 ticker in self ._open_positions
@@ -191,9 +190,9 @@ def _update_closed_positions(
191190 self ._open_positions [ticker ].ticker ,
192191 min (self ._open_positions [ticker ].size , abs (size )),
193192 self ._open_positions [ticker ].price ,
194- self ._open_positions [ticker ].datetime ,
193+ self ._open_positions [ticker ].timestamp ,
195194 price ,
196- datetime ,
195+ timestamp ,
197196 ),
198197 )
199198 # if short position is closed/reduced
@@ -203,59 +202,60 @@ def _update_closed_positions(
203202 self ._open_positions [ticker ].ticker ,
204203 max (self ._open_positions [ticker ].size , - size ),
205204 price ,
206- datetime ,
205+ timestamp ,
207206 self ._open_positions [ticker ].price ,
208- self ._open_positions [ticker ].datetime ,
207+ self ._open_positions [ticker ].timestamp ,
209208 ),
210209 )
211210
212211 def _update_open_positions (
213- self , ticker : str , size : float , price : float , datetime : datetime
212+ self , ticker : str , size : float , price : float , timestamp : pd . Timestamp
214213 ):
215214 """
216215 Updates the open positions for a given ticker.
217216
218217 If the ticker already exists in the open positions, it updates the size, price,
219- and datetime based on the new transaction. If the size of the position becomes
218+ and timestamp based on the new transaction. If the size of the position becomes
220219 zero, the position is removed. If the ticker does not exist, a new open position
221220 is created.
222221
223222 Args:
224223 ticker (str): The ticker symbol of the asset.
225224 size (float): The size of the position.
226225 price (float): The price at which the position was opened or updated.
227- datetime (datetime): The datetime when the position was opened or updated.
226+ timestamp (Timestamp): The timestamp when the position was opened or
227+ updated.
228228 """
229229 if ticker in self ._open_positions :
230230 if size + self ._open_positions [ticker ].size == 0.0 :
231231 self ._open_positions .pop (ticker )
232232 else :
233233 open_position_size = self ._open_positions [ticker ].size + size
234234 open_position_price = price
235- open_position_datetime = datetime
235+ open_position_timestamp = timestamp
236236
237237 if size * self ._open_positions [ticker ].size > 0.0 :
238238 open_position_price = (
239239 self ._open_positions [ticker ].size
240240 * self ._open_positions [ticker ].price
241241 + size * price
242242 ) / (self ._open_positions [ticker ].size + size )
243- open_position_datetime = self ._open_positions [ticker ].datetime
243+ open_position_timestamp = self ._open_positions [ticker ].timestamp
244244 elif abs (self ._open_positions [ticker ].size ) > abs (size ):
245- open_position_datetime = self ._open_positions [ticker ].datetime
245+ open_position_timestamp = self ._open_positions [ticker ].timestamp
246246 open_position_price = self ._open_positions [ticker ].price
247247 self ._open_positions [ticker ] = OpenPosition (
248248 ticker ,
249249 open_position_size ,
250250 open_position_price ,
251- open_position_datetime ,
251+ open_position_timestamp ,
252252 )
253253 else :
254254 self ._open_positions [ticker ] = OpenPosition (
255255 ticker ,
256256 size ,
257257 price ,
258- datetime ,
258+ timestamp ,
259259 )
260260
261261 def _update_cash (self , order : Order , price : float ):
@@ -272,29 +272,29 @@ def _update_cash(self, order: Order, price: float):
272272 else :
273273 self ._cash -= order .size * price * (1.0 - self ._fees )
274274
275- def _check_long_only_condition (self , order : Order , datetime : datetime ):
275+ def _check_long_only_condition (self , order : Order , timestamp : pd . Timestamp ):
276276 size = order .size
277277 if order .ticker in self ._open_positions :
278278 size += self ._open_positions [order .ticker ].size
279279
280280 if size < 0.0 :
281281 raise ValueError (
282- f"Short selling is not allowed for { order .ticker } on { datetime } ."
282+ f"Short selling is not allowed for { order .ticker } on { timestamp } ."
283283 )
284284
285285 def _execute_order (
286286 self ,
287287 order : Order ,
288288 bar : pd .DataFrame ,
289- datetime : datetime ,
289+ timestamp : pd . Timestamp ,
290290 ) -> bool :
291291 """
292- Executes an order based on the provided bar data and datetime .
292+ Executes an order based on the provided bar data and timestamp .
293293
294294 Args:
295295 order (Order): The order to be executed.
296296 bar (pd.DataFrame): The bar data containing price information.
297- datetime (datetime ): The datetime at which the order is executed.
297+ timestamp (Timestamp ): The timestamp at which the order is executed.
298298
299299 Returns:
300300 bool: True if the order was successfully executed, False otherwise.
@@ -308,7 +308,7 @@ def _execute_order(
308308 return False
309309
310310 if self ._long_only :
311- self ._check_long_only_condition (order , datetime )
311+ self ._check_long_only_condition (order , timestamp )
312312
313313 price = self ._get_price_for_order (order , bar )
314314
@@ -319,16 +319,16 @@ def _execute_order(
319319 # update cash for long and short positions
320320 self ._update_cash (order , price )
321321
322- self ._update_closed_positions (ticker , order .size , price , datetime )
322+ self ._update_closed_positions (ticker , order .size , price , timestamp )
323323
324- self ._update_open_positions (ticker , order .size , price , datetime )
324+ self ._update_open_positions (ticker , order .size , price , timestamp )
325325
326326 return True
327327
328328 def update (
329329 self ,
330330 next_bar : pd .DataFrame ,
331- next_datetime : pd .Timestamp ,
331+ next_timestamp : pd .Timestamp ,
332332 ):
333333 """
334334 Updates the broker's state with the next trading bar and executes pending
@@ -344,7 +344,7 @@ def update(
344344 Args:
345345 next_bar (pd.DataFrame): The next trading bar data containing at minimum
346346 'close' prices for assets
347- next_datetime (pd.Timestamp): The timestamp for the next trading bar
347+ next_timestamp (pd.Timestamp): The timestamp for the next trading bar
348348
349349 Notes:
350350 - Short fees are calculated using the current bar's closing price
@@ -354,9 +354,9 @@ def update(
354354 - Good-till-cancel orders that aren't filled are retained for the next bar
355355 """
356356 self ._previous_bar = self ._current_bar
357- self ._previous_datetime = self ._current_datetime
357+ self ._previous_timestamp = self ._current_timestamp
358358 self ._current_bar = next_bar
359- self ._current_datetime = next_datetime
359+ self ._current_timestamp = next_timestamp
360360
361361 # consider short fees
362362 if not self ._long_only :
@@ -384,7 +384,7 @@ def update(
384384 self ._execute_order (
385385 Order (ticker , - self ._open_positions [ticker ].size , OrderType .CLOSE ),
386386 self ._previous_bar ,
387- cast ( datetime , self ._previous_datetime ) ,
387+ self ._previous_timestamp ,
388388 )
389389
390390 # buy and sell assets
@@ -396,18 +396,18 @@ def update(
396396 if open_order .ticker in ticker_not_available :
397397 if open_order .size > 0 :
398398 print (
399- f"{ open_order .ticker } could not be bought on { self ._current_datetime } ." # noqa: E501
399+ f"{ open_order .ticker } could not be bought on { self ._current_timestamp } ." # noqa: E501
400400 )
401401 else :
402402 print (
403- f"{ open_order .ticker } could not be sold on { self ._current_datetime } ." # noqa: E501
403+ f"{ open_order .ticker } could not be sold on { self ._current_timestamp } ." # noqa: E501
404404 )
405405 continue
406406 if (
407407 not self ._execute_order (
408408 open_order ,
409409 self ._current_bar ,
410- cast ( datetime , self ._current_datetime ) ,
410+ self ._current_timestamp ,
411411 )
412412 and open_order .good_till_cancel
413413 ):
@@ -439,7 +439,7 @@ def liquidate_positions(self):
439439 self ._execute_order (
440440 Order (ticker , - self ._open_positions [ticker ].size , OrderType .CLOSE ),
441441 self ._current_bar ,
442- self ._current_datetime ,
442+ self ._current_timestamp ,
443443 )
444444
445445 def place_order (self , order : Order ):
@@ -524,7 +524,7 @@ def open_positions(self) -> Dict[str, OpenPosition]:
524524 - ticker: Financial instrument identifier
525525 - size: Position size (positive=long, negative=short)
526526 - price: Average entry price
527- - datetime : Position opening timestamp
527+ - timestamp : Position opening timestamp
528528
529529 Returns:
530530 Dict[str, OpenPosition]: Dictionary mapping ticker symbols to positions.
0 commit comments