Skip to content

Commit

Permalink
feat: Support position intent in order requests (#428)
Browse files Browse the repository at this point in the history
  • Loading branch information
matebudai committed Mar 27, 2024
1 parent c1e90f7 commit 390b043
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 2 deletions.
11 changes: 11 additions & 0 deletions alpaca/trading/enums.py
Expand Up @@ -379,3 +379,14 @@ class ActivityCategory(str, Enum):

TRADE_ACTIVITY = "trade_activity"
NON_TRADE_ACTIVITY = "non_trade_activity"


class PositionIntent(str, Enum):
"""
Represents what side this order was executed on.
"""

BUY_TO_OPEN = "buy_to_open"
BUY_TO_CLOSE = "buy_to_close"
SELL_TO_OPEN = "sell_to_open"
SELL_TO_CLOSE = "sell_to_close"
9 changes: 8 additions & 1 deletion alpaca/trading/requests.py
Expand Up @@ -20,6 +20,7 @@
CorporateActionType,
CorporateActionDateType,
QueryOrderStatus,
PositionIntent,
)


Expand Down Expand Up @@ -238,6 +239,7 @@ class OrderRequest(NonEmptyRequest):
order_class (Optional[OrderClass]): The class of the order. Simple orders have no other legs.
take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
position_intent (Optional[PositionIntent]): An enum to indicate the desired position strategy: BTO, BTC, STO, STC.
"""

symbol: str
Expand All @@ -251,6 +253,7 @@ class OrderRequest(NonEmptyRequest):
client_order_id: Optional[str] = None
take_profit: Optional[TakeProfitRequest] = None
stop_loss: Optional[StopLossRequest] = None
position_intent: Optional[PositionIntent] = None

@model_validator(mode="before")
def root_validator(cls, values: dict) -> dict:
Expand Down Expand Up @@ -282,7 +285,7 @@ class MarketOrderRequest(OrderRequest):
order_class (Optional[OrderClass]): The class of the order. Simple orders have no other legs.
take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
position_intent (Optional[PositionIntent]): An enum to indicate the desired position strategy: BTO, BTC, STO, STC.
"""

def __init__(self, **data: Any) -> None:
Expand Down Expand Up @@ -310,6 +313,7 @@ class StopOrderRequest(OrderRequest):
stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
stop_price (float): The price at which the stop order is converted to a market order or a stop limit
order is converted to a limit order.
position_intent (Optional[PositionIntent]): An enum to indicate the desired position strategy: BTO, BTC, STO, STC.
"""

stop_price: float
Expand Down Expand Up @@ -338,6 +342,7 @@ class LimitOrderRequest(OrderRequest):
take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
limit_price (float): The worst fill price for a limit or stop limit order.
position_intent (Optional[PositionIntent]): An enum to indicate the desired position strategy: BTO, BTC, STO, STC.
"""

limit_price: float
Expand Down Expand Up @@ -368,6 +373,7 @@ class StopLimitOrderRequest(OrderRequest):
stop_price (float): The price at which the stop order is converted to a market order or a stop limit
order is converted to a limit order.
limit_price (float): The worst fill price for a limit or stop limit order.
position_intent (Optional[PositionIntent]): An enum to indicate the desired position strategy: BTO, BTC, STO, STC.
"""

stop_price: float
Expand Down Expand Up @@ -398,6 +404,7 @@ class TrailingStopOrderRequest(OrderRequest):
stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
trail_price (Optional[float]): The absolute price difference by which the trailing stop will trail.
trail_percent (Optional[float]): The percent price difference by which the trailing stop will trail.
position_intent (Optional[PositionIntent]): An enum to indicate the desired position strategy: BTO, BTC, STO, STC.
"""

trail_price: Optional[float] = None
Expand Down
5 changes: 5 additions & 0 deletions docs/api_reference/trading/enums.rst
Expand Up @@ -115,3 +115,8 @@ TradeConfirmationEmail
----------------------

.. autoenum:: alpaca.trading.enums.TradeConfirmationEmail

PositionIntent
----------------------

.. autoenum:: alpaca.trading.enums.PositionIntent
117 changes: 116 additions & 1 deletion tests/trading/trading_client/test_order_routes.py
Expand Up @@ -9,7 +9,7 @@
)
from alpaca.trading.models import Order
from alpaca.trading.client import TradingClient
from alpaca.trading.enums import OrderSide, OrderStatus, TimeInForce
from alpaca.trading.enums import OrderSide, OrderStatus, TimeInForce, PositionIntent
from alpaca.common.enums import BaseURL

import pytest
Expand Down Expand Up @@ -391,3 +391,118 @@ def test_limit_order(reqmock, trading_client):
lo_response = trading_client.submit_order(lo)

assert lo_response.status == OrderStatus.ACCEPTED


def test_order_position_intent(reqmock, trading_client: TradingClient):
reqmock.post(
f"{BaseURL.TRADING_PAPER.value}/v2/orders",
text="""
{
"id": "61e69015-8549-4bfd-b9c3-01e75843f47d",
"client_order_id": "eb9e2aaa-f71a-4f51-b5b4-52a6c565dad4",
"created_at": "2021-03-16T18:38:01.942282Z",
"updated_at": "2021-03-16T18:38:01.942282Z",
"submitted_at": "2021-03-16T18:38:01.937734Z",
"filled_at": null,
"expired_at": null,
"canceled_at": null,
"failed_at": null,
"replaced_at": null,
"replaced_by": null,
"replaces": null,
"asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
"symbol": "SPY`",
"asset_class": "us_equity",
"notional": null,
"qty": 1,
"filled_qty": "0",
"filled_avg_price": null,
"order_class": "simple",
"order_type": "market",
"type": "market",
"side": "buy",
"time_in_force": "day",
"limit_price": null,
"stop_price": null,
"status": "accepted",
"extended_hours": false,
"legs": null,
"trail_percent": null,
"trail_price": null,
"hwm": null,
"commission": 1.25,
"position_intent": "buy_to_open"
}
""",
)

# Market Order
mo = MarketOrderRequest(
symbol="SPY",
side=OrderSide.BUY,
time_in_force=TimeInForce.DAY,
qty=1,
position_intent=PositionIntent.BUY_TO_OPEN,
)

assert mo.position_intent == PositionIntent.BUY_TO_OPEN

mo_response = trading_client.submit_order(mo)

assert mo_response.status == OrderStatus.ACCEPTED

reqmock.post(
f"{BaseURL.TRADING_PAPER.value}/v2/orders",
text="""
{
"id": "61e69015-8549-4bfd-b9c3-01e75843f47d",
"client_order_id": "eb9e2aaa-f71a-4f51-b5b4-52a6c565dad4",
"created_at": "2021-03-16T18:38:01.942282Z",
"updated_at": "2021-03-16T18:38:01.942282Z",
"submitted_at": "2021-03-16T18:38:01.937734Z",
"filled_at": null,
"expired_at": null,
"canceled_at": null,
"failed_at": null,
"replaced_at": null,
"replaced_by": null,
"replaces": null,
"asset_id": "904837e3-3b76-47ec-b432-046db621571b",
"symbol": "AAPL`",
"asset_class": "us_equity",
"notional": null,
"qty": 1,
"filled_qty": "0",
"filled_avg_price": null,
"order_class": "simple",
"order_type": "limit",
"type": "limit",
"side": "sell",
"time_in_force": "day",
"limit_price": 300,
"stop_price": null,
"status": "accepted",
"extended_hours": false,
"legs": null,
"trail_percent": null,
"trail_price": null,
"hwm": null,
"commission": 1.25
}
""",
)

lo = LimitOrderRequest(
symbol="SPY",
side=OrderSide.BUY,
time_in_force=TimeInForce.DAY,
limit_price=300,
qty=1,
position_intent=PositionIntent.SELL_TO_OPEN,
)

assert lo.position_intent == PositionIntent.SELL_TO_OPEN

lo_response = trading_client.submit_order(lo)

assert lo_response.status == OrderStatus.ACCEPTED

0 comments on commit 390b043

Please sign in to comment.