Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add multi-leg options #552

Merged
merged 19 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -498,9 +506,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 @@ -551,7 +559,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
Loading