Skip to content

Commit

Permalink
feat: Add multi-leg options (#552)
Browse files Browse the repository at this point in the history
* 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 <hiohiohio@users.noreply.github.com>

* Update alpaca/trading/models.py

Co-authored-by: hiohiohio <hiohiohio@users.noreply.github.com>

* Update alpaca/trading/models.py

Co-authored-by: hiohiohio <hiohiohio@users.noreply.github.com>

* Update alpaca/trading/requests.py

Co-authored-by: hiohiohio <hiohiohio@users.noreply.github.com>

* Update alpaca/trading/enums.py

Co-authored-by: hiohiohio <hiohiohio@users.noreply.github.com>

* Update alpaca/trading/models.py

Co-authored-by: hiohiohio <hiohiohio@users.noreply.github.com>

* 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 <hiohiohio@users.noreply.github.com>
  • Loading branch information
aarjaneiro and hiohiohio authored Jan 20, 2025
1 parent 18e2403 commit e2466ba
Show file tree
Hide file tree
Showing 12 changed files with 1,293 additions and 341 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,4 @@ We have put together some examples in jupyter notebooks so that you can start de
* [Stocks](https://github.com/alpacahq/alpaca-py/blob/master/examples/stocks-trading-basic.ipynb)
* [Options](https://github.com/alpacahq/alpaca-py/blob/master/examples/options-trading-basic.ipynb)
* [Crypto](https://github.com/alpacahq/alpaca-py/blob/master/examples/crypto-trading-basic.ipynb)
* [Multi-Leg Options](https://github.com/alpacahq/alpaca-py/blob/master/examples/options-trading-mleg.ipynb)
4 changes: 3 additions & 1 deletion alpaca/common/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,7 @@ def map_values(val: Any) -> Any:
# {trusted_contact: {}, contact: {}, identity: None, etc}
# so we do a simple list comprehension to filter out None and {}
return {
key: map_values(val) for key, val in d.items() if val and len(str(val)) > 0
key: map_values(val)
for key, val in d.items()
if val is not None and val != {} and len(str(val)) > 0
}
3 changes: 2 additions & 1 deletion alpaca/trading/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,12 @@ class OrderClass(str, Enum):
The order classes supported by Alpaca vary based on the order's security type.
The following provides a comprehensive breakdown of the supported order classes for each category:
- Equity trading: simple (or ""), oco, oto, bracket.
- Options trading: simple (or "").
- Options trading: simple (or ""), mleg (required for multi-leg complex options strategies).
- Crypto trading: simple (or "").
"""

SIMPLE = "simple"
MLEG = "mleg"
BRACKET = "bracket"
OCO = "oco"
OTO = "oto"
Expand Down
40 changes: 24 additions & 16 deletions alpaca/trading/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
TradeConfirmationEmail,
TradeEvent,
)
from pydantic import Field
from pydantic import Field, model_validator


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

client_order_id: str
Expand All @@ -219,17 +220,17 @@ class Order(ModelWithID):
replaced_at: Optional[datetime] = None
replaced_by: Optional[UUID] = None
replaces: Optional[UUID] = None
asset_id: UUID
symbol: str
asset_class: AssetClass
asset_id: Optional[UUID] = None
symbol: Optional[str] = None
asset_class: Optional[AssetClass] = None
notional: Optional[str] = None
qty: Optional[Union[str, float]] = None
filled_qty: Optional[Union[str, float]] = None
filled_avg_price: Optional[Union[str, float]] = None
order_class: OrderClass
order_type: OrderType
type: OrderType
side: OrderSide
order_type: Optional[OrderType] = None
type: Optional[OrderType] = None
side: Optional[OrderSide] = None
time_in_force: TimeInForce
limit_price: Optional[Union[str, float]] = None
stop_price: Optional[Union[str, float]] = None
Expand All @@ -240,11 +241,18 @@ class Order(ModelWithID):
trail_price: Optional[str] = None
hwm: Optional[str] = None
position_intent: Optional[PositionIntent] = None
ratio_qty: Optional[Union[str, float]] = None

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

# mleg responses will give ''s that will need to be converted to None
# to avoid validation errors from pydantic
for k in ["asset_id", "symbol", "asset_class", "side", "type", "order_type"]:
if k in data and data[k] == "":
data[k] = None

super().__init__(**data)


Expand Down Expand Up @@ -500,9 +508,9 @@ class TradeAccount(ModelWithID):
(inclusive of today)
options_buying_power (Optional[str]): Your buying power for options trading
options_approved_level (Optional[int]): The options trading level that was approved for this account.
0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put.
0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.
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.
0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long
0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long, 3=Spreads/Straddles.
"""

account_number: str
Expand Down Expand Up @@ -553,7 +561,7 @@ class AccountConfiguration(BaseModel):
suspend_trade (bool): If true Account becomes unable to submit new orders
trade_confirm_email (TradeConfirmationEmail): Controls whether Trade confirmation emails are sent.
ptp_no_exception_entry (bool): If set to true then Alpaca will accept orders for PTP symbols with no exception. Default is false.
max_options_trading_level (Optional[int]): The desired maximum options trading level. 0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put.
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.
"""

dtbp_check: DTBPCheck
Expand Down
Loading

0 comments on commit e2466ba

Please sign in to comment.