@@ -88,6 +88,126 @@ def cancel_all(self) -> str:
8888 )
8989 return f"{ len (response )} orders have been cancelled"
9090
91+ ########################################################
92+ # \\\\\\\\\ Replace Order /////////////////////#
93+ ########################################################
94+ def replace_order (
95+ self ,
96+ order_id : str ,
97+ qty : float | None = None ,
98+ limit_price : float | None = None ,
99+ stop_price : float | None = None ,
100+ trail : float | None = None ,
101+ time_in_force : str | None = None ,
102+ client_order_id : str | None = None ,
103+ ) -> OrderModel :
104+ """Replace an existing order with updated parameters.
105+
106+ Args:
107+ order_id: The ID of the order to replace.
108+ qty: The new quantity for the order.
109+ limit_price: The new limit price for limit orders.
110+ stop_price: The new stop price for stop orders.
111+ trail: The new trail amount for trailing stop orders (percent or price).
112+ time_in_force: The new time in force for the order.
113+ client_order_id: Optional client-assigned ID for the replacement order.
114+
115+ Returns:
116+ OrderModel: The replaced order.
117+
118+ Raises:
119+ ValidationError: If no parameters are provided to update.
120+ APIRequestError: If the API request fails.
121+ """
122+ # At least one parameter must be provided
123+ if not any ([qty , limit_price , stop_price , trail , time_in_force ]):
124+ raise ValidationError (
125+ "At least one parameter must be provided to replace the order"
126+ )
127+
128+ body : dict [str , str | float | None ] = {}
129+ if qty is not None :
130+ body ["qty" ] = qty
131+ if limit_price is not None :
132+ body ["limit_price" ] = limit_price
133+ if stop_price is not None :
134+ body ["stop_price" ] = stop_price
135+ if trail is not None :
136+ body ["trail" ] = trail
137+ if time_in_force is not None :
138+ body ["time_in_force" ] = time_in_force
139+ if client_order_id is not None :
140+ body ["client_order_id" ] = client_order_id
141+
142+ url = f"{ self .base_url } /orders/{ order_id } "
143+
144+ response = json .loads (
145+ Requests ()
146+ .request (method = "PATCH" , url = url , headers = self .headers , json = body )
147+ .text
148+ )
149+ return order_class_from_dict (response )
150+
151+ ########################################################
152+ # \\\\\\\ Get Order By Client ID ////////////////#
153+ ########################################################
154+ def get_by_client_order_id (self , client_order_id : str ) -> OrderModel :
155+ """Retrieves order information by client order ID.
156+
157+ Note: This queries all orders and filters by client_order_id.
158+ The Alpaca API doesn't have a direct endpoint for this.
159+
160+ Args:
161+ client_order_id: The client-assigned ID of the order to retrieve.
162+
163+ Returns:
164+ OrderModel: An object representing the order information.
165+
166+ Raises:
167+ APIRequestError: If the request fails or order not found.
168+ ValidationError: If no order with given client_order_id is found.
169+ """
170+ # Get all orders and filter by client_order_id
171+ params : dict [str , str | bool | float | int ] = {"status" : "all" , "limit" : 500 }
172+ url = f"{ self .base_url } /orders"
173+
174+ response = json .loads (
175+ Requests ()
176+ .request (method = "GET" , url = url , headers = self .headers , params = params )
177+ .text
178+ )
179+
180+ # Find the order with matching client_order_id
181+ for order_data in response :
182+ if order_data .get ("client_order_id" ) == client_order_id :
183+ return order_class_from_dict (order_data )
184+
185+ raise ValidationError (f"No order found with client_order_id: { client_order_id } " )
186+
187+ ########################################################
188+ # \\\\\\ Cancel Order By Client ID ///////////////#
189+ ########################################################
190+ def cancel_by_client_order_id (self , client_order_id : str ) -> str :
191+ """Cancel an order by its client order ID.
192+
193+ Note: This first retrieves the order by client_order_id, then cancels by ID.
194+
195+ Args:
196+ client_order_id: The client-assigned ID of the order to be cancelled.
197+
198+ Returns:
199+ str: A message indicating the status of the cancellation.
200+
201+ Raises:
202+ APIRequestError: If the cancellation request fails.
203+ ValidationError: If no order with given client_order_id is found.
204+ """
205+ # First get the order by client_order_id to get its ID
206+ order = self .get_by_client_order_id (client_order_id )
207+
208+ # Then cancel by the actual order ID
209+ return self .cancel_by_id (order .id )
210+
91211 @staticmethod
92212 def check_for_order_errors (
93213 symbol : str ,
@@ -148,6 +268,8 @@ def market(
148268 side : str = "buy" ,
149269 time_in_force : str = "day" ,
150270 extended_hours : bool = False ,
271+ client_order_id : str | None = None ,
272+ order_class : str | None = None ,
151273 ) -> OrderModel :
152274 """Submits a market order for a specified symbol.
153275
@@ -164,6 +286,8 @@ def market(
164286 (day/gtc/opg/ioc/fok). Defaults to "day".
165287 extended_hours (bool, optional): Whether to trade during extended hours.
166288 Defaults to False.
289+ client_order_id (str, optional): Client-assigned ID for the order. Defaults to None.
290+ order_class (str, optional): Order class (simple/bracket/oco/oto). Defaults to None.
167291
168292 Returns:
169293 OrderModel: An instance of the OrderModel representing the submitted order.
@@ -190,6 +314,8 @@ def market(
190314 entry_type = "market" ,
191315 time_in_force = time_in_force ,
192316 extended_hours = extended_hours ,
317+ client_order_id = client_order_id ,
318+ order_class = order_class ,
193319 )
194320
195321 ########################################################
@@ -206,6 +332,8 @@ def limit(
206332 side : str = "buy" ,
207333 time_in_force : str = "day" ,
208334 extended_hours : bool = False ,
335+ client_order_id : str | None = None ,
336+ order_class : str | None = None ,
209337 ) -> OrderModel :
210338 """Limit order function that submits an order to buy or sell a specified symbol
211339 at a specified limit price.
@@ -226,6 +354,8 @@ def limit(
226354 or "gtc" (good till canceled). Default is "day".
227355 extended_hours (bool, optional): Whether to allow trading during extended
228356 hours. Default is False.
357+ client_order_id (str, optional): Client-assigned ID for the order. Defaults to None.
358+ order_class (str, optional): Order class (simple/bracket/oco/oto). Defaults to None.
229359
230360 Returns:
231361 OrderModel: The submitted order.
@@ -253,6 +383,8 @@ def limit(
253383 entry_type = "limit" ,
254384 time_in_force = time_in_force ,
255385 extended_hours = extended_hours ,
386+ client_order_id = client_order_id ,
387+ order_class = order_class ,
256388 )
257389
258390 ########################################################
@@ -268,6 +400,8 @@ def stop(
268400 stop_loss : float | None = None ,
269401 time_in_force : str = "day" ,
270402 extended_hours : bool = False ,
403+ client_order_id : str | None = None ,
404+ order_class : str | None = None ,
271405 ) -> OrderModel :
272406 """Args:
273407
@@ -283,6 +417,8 @@ def stop(
283417 Defaults to 'day'.
284418 extended_hours: A boolean value indicating whether to place the order during
285419 extended hours. Defaults to False.
420+ client_order_id: Client-assigned ID for the order. Defaults to None.
421+ order_class: Order class (simple/bracket/oco/oto). Defaults to None.
286422
287423 Returns:
288424 An instance of the OrderModel representing the submitted order.
@@ -311,6 +447,8 @@ def stop(
311447 entry_type = "stop" ,
312448 time_in_force = time_in_force ,
313449 extended_hours = extended_hours ,
450+ client_order_id = client_order_id ,
451+ order_class = order_class ,
314452 )
315453
316454 ########################################################
@@ -325,6 +463,8 @@ def stop_limit(
325463 side : str = "buy" ,
326464 time_in_force : str = "day" ,
327465 extended_hours : bool = False ,
466+ client_order_id : str | None = None ,
467+ order_class : str | None = None ,
328468 ) -> OrderModel :
329469 """Submits a stop-limit order for trading.
330470
@@ -339,6 +479,8 @@ def stop_limit(
339479 Defaults to 'day'.
340480 extended_hours (bool, optional): Whether to allow trading during extended hours.
341481 Defaults to False.
482+ client_order_id (str, optional): Client-assigned ID for the order. Defaults to None.
483+ order_class (str, optional): Order class (simple/bracket/oco/oto). Defaults to None.
342484
343485 Returns:
344486 OrderModel: The submitted stop-limit order.
@@ -366,6 +508,8 @@ def stop_limit(
366508 entry_type = "stop_limit" ,
367509 time_in_force = time_in_force ,
368510 extended_hours = extended_hours ,
511+ client_order_id = client_order_id ,
512+ order_class = order_class ,
369513 )
370514
371515 ########################################################
@@ -380,6 +524,8 @@ def trailing_stop(
380524 side : str = "buy" ,
381525 time_in_force : str = "day" ,
382526 extended_hours : bool = False ,
527+ client_order_id : str | None = None ,
528+ order_class : str | None = None ,
383529 ) -> OrderModel :
384530 """Submits a trailing stop order for the specified symbol.
385531
@@ -392,7 +538,10 @@ def trailing_stop(
392538 `trail_percent` or `trail_price` must be provided, not both. Defaults to None.
393539 side (str, optional): The side of the order, either 'buy' or 'sell'. Defaults to 'buy'.
394540 time_in_force (str, optional): The time in force for the order. Defaults to 'day'.
395- extended_hours (bool, optional): Whether to allow trading during extended hours.\n Defaults to False.
541+ extended_hours (bool, optional): Whether to allow trading during extended hours.
542+ Defaults to False.
543+ client_order_id (str, optional): Client-assigned ID for the order. Defaults to None.
544+ order_class (str, optional): Order class (simple/bracket/oco/oto). Defaults to None.
396545
397546 Returns:
398547 OrderModel: The submitted trailing stop order.
@@ -426,6 +575,8 @@ def trailing_stop(
426575 entry_type = "trailing_stop" ,
427576 time_in_force = time_in_force ,
428577 extended_hours = extended_hours ,
578+ client_order_id = client_order_id ,
579+ order_class = order_class ,
429580 )
430581
431582 ########################################################
@@ -446,6 +597,8 @@ def _submit_order(
446597 side : str = "buy" ,
447598 time_in_force : str = "day" ,
448599 extended_hours : bool = False ,
600+ client_order_id : str | None = None ,
601+ order_class : str | None = None ,
449602 ) -> OrderModel :
450603 """Submits an order to the Alpaca API.
451604
@@ -470,14 +623,28 @@ def _submit_order(
470623 side (str, optional): The side of the trade (buy or sell). Defaults to "buy".
471624 time_in_force (str, optional): The time in force for the order.
472625 Defaults to "day".
473- extended_hours (bool, optional): Whether to allow trading during extended hours.\n Defaults to False.
626+ extended_hours (bool, optional): Whether to allow trading during extended hours.
627+ Defaults to False.
628+ client_order_id (str, optional): Client-assigned ID for the order. Defaults to None.
629+ order_class (str, optional): Order class (simple/bracket/oco/oto). Defaults to None.
474630
475631 Returns:
476632 OrderModel: The submitted order.
477633
478634 Raises:
479635 Exception: If the order submission fails.
480636 """
637+ # Determine order class
638+ if order_class :
639+ # Use explicitly provided order class
640+ final_order_class = order_class
641+ elif take_profit or stop_loss :
642+ # Bracket order if take profit or stop loss is specified
643+ final_order_class = "bracket"
644+ else :
645+ # Default to simple
646+ final_order_class = "simple"
647+
481648 payload = {
482649 "symbol" : symbol ,
483650 "qty" : qty if qty else None ,
@@ -486,13 +653,14 @@ def _submit_order(
486653 "limit_price" : limit_price if limit_price else None ,
487654 "trail_percent" : trail_percent if trail_percent else None ,
488655 "trail_price" : trail_price if trail_price else None ,
489- "order_class" : "bracket" if take_profit or stop_loss else "simple" ,
656+ "order_class" : final_order_class ,
490657 "take_profit" : take_profit ,
491658 "stop_loss" : stop_loss ,
492659 "side" : side if side == "buy" else "sell" ,
493660 "type" : entry_type ,
494661 "time_in_force" : time_in_force ,
495662 "extended_hours" : extended_hours ,
663+ "client_order_id" : client_order_id if client_order_id else None ,
496664 }
497665
498666 url = f"{ self .base_url } /orders"
0 commit comments