Skip to content

Commit e2466ba

Browse files
feat: Add multi-leg options (#552)
* create enum for mleg + adds request * feat: ipynb fully works with fixes feat: examples feat: update model for mleg feat: add missing qty test feat: trailing stop limit validator separated feat: emit warning with info upon validation test failing feat: limit + mleg validation feat: adds validation + tests for symbol repetition feat: adds validation + tests feat: add legs as parameter to order-type requests * fix: docgen refactor: cleanup fix: to_request_fields empty detection refactor: typing refactor: prettiness * fix: minor docstring typo * Update alpaca/trading/models.py Co-authored-by: hiohiohio <[email protected]> * Update alpaca/trading/models.py Co-authored-by: hiohiohio <[email protected]> * Update alpaca/trading/models.py Co-authored-by: hiohiohio <[email protected]> * Update alpaca/trading/requests.py Co-authored-by: hiohiohio <[email protected]> * Update alpaca/trading/enums.py Co-authored-by: hiohiohio <[email protected]> * Update alpaca/trading/models.py Co-authored-by: hiohiohio <[email protected]> * feat: adds validation test for one of side or position intent in OptionLegRequest * reformat feat: typing for `leg` parameters * fixes re: @hiohiohio fix: prefer !python3 -m pip install alpaca-py refactor: ratio_qty docstring added fix: implement suggestion re: validating api returns * refactor: docs refer to options-trading-mleg.ipynb * fix: implement fixes suggested by @matebudai * fix: implement fixes suggested by @matebudai * refactor: refmt --------- Co-authored-by: hiohiohio <[email protected]>
1 parent 18e2403 commit e2466ba

12 files changed

+1293
-341
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,4 @@ We have put together some examples in jupyter notebooks so that you can start de
271271
* [Stocks](https://github.com/alpacahq/alpaca-py/blob/master/examples/stocks-trading-basic.ipynb)
272272
* [Options](https://github.com/alpacahq/alpaca-py/blob/master/examples/options-trading-basic.ipynb)
273273
* [Crypto](https://github.com/alpacahq/alpaca-py/blob/master/examples/crypto-trading-basic.ipynb)
274+
* [Multi-Leg Options](https://github.com/alpacahq/alpaca-py/blob/master/examples/options-trading-mleg.ipynb)

alpaca/common/requests.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,7 @@ def map_values(val: Any) -> Any:
7676
# {trusted_contact: {}, contact: {}, identity: None, etc}
7777
# so we do a simple list comprehension to filter out None and {}
7878
return {
79-
key: map_values(val) for key, val in d.items() if val and len(str(val)) > 0
79+
key: map_values(val)
80+
for key, val in d.items()
81+
if val is not None and val != {} and len(str(val)) > 0
8082
}

alpaca/trading/enums.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,12 @@ class OrderClass(str, Enum):
107107
The order classes supported by Alpaca vary based on the order's security type.
108108
The following provides a comprehensive breakdown of the supported order classes for each category:
109109
- Equity trading: simple (or ""), oco, oto, bracket.
110-
- Options trading: simple (or "").
110+
- Options trading: simple (or ""), mleg (required for multi-leg complex options strategies).
111111
- Crypto trading: simple (or "").
112112
"""
113113

114114
SIMPLE = "simple"
115+
MLEG = "mleg"
115116
BRACKET = "bracket"
116117
OCO = "oco"
117118
OTO = "oto"

alpaca/trading/models.py

+24-16
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
TradeConfirmationEmail,
2727
TradeEvent,
2828
)
29-
from pydantic import Field
29+
from pydantic import Field, model_validator
3030

3131

3232
class Asset(ModelWithID):
@@ -182,19 +182,19 @@ class Order(ModelWithID):
182182
replaced_at (Optional[datetime]): Timestamp when the order was replaced by a new order.
183183
replaced_by (Optional[UUID]): ID of order that replaces this order.
184184
replaces (Optional[UUID]): ID of order which this order replaces.
185-
asset_id (UUID): ID of the asset.
186-
symbol (str): Symbol of the asset.
187-
asset_class (AssetClass): Asset class of the asset.
185+
asset_id (Optional[UUID]): ID of the asset. Omitted from top-level of response if the order is of mleg class.
186+
symbol (Optional[str]): Symbol of the asset. Omitted from top-level of response if the order is of mleg class.
187+
asset_class (Optional[AssetClass]): Asset class of the asset. Omitted from top-level of response if the order is of mleg class.
188188
notional (Optional[str]): Ordered notional amount. If entered, qty will be null. Can take up to 9 decimal
189189
points.
190190
qty (Optional[str]): Ordered quantity. If entered, notional will be null. Can take up to 9 decimal points.
191191
filled_qty (Optional[str]): Filled quantity.
192192
filled_avg_price (Optional[str]): Filled average price. Can be 0 until order is processed in case order is
193193
passed outside of market hours.
194194
order_class (OrderClass): Valid values: simple, bracket, oco or oto.
195-
order_type (OrderType): Deprecated with just type field below.
196-
type (OrderType): Valid values: market, limit, stop, stop_limit, trailing_stop.
197-
side (OrderSide): Valid values: buy and sell.
195+
order_type (Optional[OrderType]): Deprecated with just type field below. Omitted from legs of mleg orders.
196+
type (Optional[OrderType]): Valid values: market, limit, stop, stop_limit, trailing_stop. Omitted from legs of mleg orders.
197+
side (Optional[OrderSide]): Valid values: buy and sell. Omitted from top-level of response if the order is of mleg class.
198198
time_in_force (TimeInForce): Length of time the order is in force.
199199
limit_price (Optional[str]): Limit price of the order.
200200
stop_price (Optional[str]): Stop price of the order.
@@ -206,6 +206,7 @@ class Order(ModelWithID):
206206
trail_price (Optional[str]): The dollar value away from the high water mark for trailing stop orders.
207207
hwm (Optional[str]): The highest (lowest) market price seen since the trailing stop order was submitted.
208208
position_intent (Optional[PositionIntent]): Represents the desired position strategy.
209+
ratio_qty (Optional[str]): The proportional quantity of this leg in relation to the overall multi-leg order quantity.
209210
"""
210211

211212
client_order_id: str
@@ -219,17 +220,17 @@ class Order(ModelWithID):
219220
replaced_at: Optional[datetime] = None
220221
replaced_by: Optional[UUID] = None
221222
replaces: Optional[UUID] = None
222-
asset_id: UUID
223-
symbol: str
224-
asset_class: AssetClass
223+
asset_id: Optional[UUID] = None
224+
symbol: Optional[str] = None
225+
asset_class: Optional[AssetClass] = None
225226
notional: Optional[str] = None
226227
qty: Optional[Union[str, float]] = None
227228
filled_qty: Optional[Union[str, float]] = None
228229
filled_avg_price: Optional[Union[str, float]] = None
229230
order_class: OrderClass
230-
order_type: OrderType
231-
type: OrderType
232-
side: OrderSide
231+
order_type: Optional[OrderType] = None
232+
type: Optional[OrderType] = None
233+
side: Optional[OrderSide] = None
233234
time_in_force: TimeInForce
234235
limit_price: Optional[Union[str, float]] = None
235236
stop_price: Optional[Union[str, float]] = None
@@ -240,11 +241,18 @@ class Order(ModelWithID):
240241
trail_price: Optional[str] = None
241242
hwm: Optional[str] = None
242243
position_intent: Optional[PositionIntent] = None
244+
ratio_qty: Optional[Union[str, float]] = None
243245

244246
def __init__(self, **data: Any) -> None:
245247
if "order_class" not in data or data["order_class"] == "":
246248
data["order_class"] = OrderClass.SIMPLE
247249

250+
# mleg responses will give ''s that will need to be converted to None
251+
# to avoid validation errors from pydantic
252+
for k in ["asset_id", "symbol", "asset_class", "side", "type", "order_type"]:
253+
if k in data and data[k] == "":
254+
data[k] = None
255+
248256
super().__init__(**data)
249257

250258

@@ -500,9 +508,9 @@ class TradeAccount(ModelWithID):
500508
(inclusive of today)
501509
options_buying_power (Optional[str]): Your buying power for options trading
502510
options_approved_level (Optional[int]): The options trading level that was approved for this account.
503-
0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put.
511+
0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.
504512
options_trading_level (Optional[int]): The effective options trading level of the account. This is the minimum between account options_approved_level and account configurations max_options_trading_level.
505-
0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long
513+
0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long, 3=Spreads/Straddles.
506514
"""
507515

508516
account_number: str
@@ -553,7 +561,7 @@ class AccountConfiguration(BaseModel):
553561
suspend_trade (bool): If true Account becomes unable to submit new orders
554562
trade_confirm_email (TradeConfirmationEmail): Controls whether Trade confirmation emails are sent.
555563
ptp_no_exception_entry (bool): If set to true then Alpaca will accept orders for PTP symbols with no exception. Default is false.
556-
max_options_trading_level (Optional[int]): The desired maximum options trading level. 0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put.
564+
max_options_trading_level (Optional[int]): The desired maximum options trading level. 0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.
557565
"""
558566

559567
dtbp_check: DTBPCheck

0 commit comments

Comments
 (0)