Skip to content

Commit

Permalink
feat: Support position intent in order requests
Browse files Browse the repository at this point in the history
  • Loading branch information
matebudai committed Mar 22, 2024
1 parent 7b521ee commit 96ead7b
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 1 deletion.
11 changes: 11 additions & 0 deletions alpaca/trading/enums.py
Original file line number Diff line number Diff line change
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"
3 changes: 3 additions & 0 deletions alpaca/trading/requests.py
Original file line number Diff line number Diff line change
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
5 changes: 5 additions & 0 deletions docs/api_reference/trading/enums.rst
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 96ead7b

Please sign in to comment.