Skip to content

Commit

Permalink
feat: Extend Order API with commission_type
Browse files Browse the repository at this point in the history
  • Loading branch information
matebudai committed May 27, 2024
1 parent b880c26 commit b554eb8
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 25 deletions.
11 changes: 11 additions & 0 deletions alpaca/broker/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,14 @@ class JournalStatus(str, Enum):
REFUSED = "refused"
CORRECT = "correct"
DELETED = "deleted"


class CommissionType(str, Enum):
"""
Represents the available ways of charging commission. This determines how
the value in the commission field is interpreted.
"""

NOTIONAL = "notional"
BPS = "bps"
QTY = "qty"
12 changes: 12 additions & 0 deletions alpaca/broker/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
VisaType,
JournalEntryType,
JournalStatus,
CommissionType,
)
from alpaca.common.enums import Sort, SupportedCurrencies
from alpaca.trading.enums import ActivityType, AccountStatus, OrderType, AssetClass
Expand Down Expand Up @@ -662,6 +663,7 @@ class OrderRequest(BaseOrderRequest):
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.
commission (Optional[float]): The dollar value commission you want to charge the end user.
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
"""

commission: Optional[float] = None
Expand Down Expand Up @@ -705,9 +707,11 @@ class MarketOrderRequest(BaseMarketOrderRequest):
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.
commission (Optional[float]): The dollar value commission you want to charge the end user.
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
"""

commission: Optional[float] = None
commission_type: Optional[CommissionType] = None


class LimitOrderRequest(BaseLimitOrderRequest):
Expand All @@ -729,9 +733,11 @@ class LimitOrderRequest(BaseLimitOrderRequest):
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.
commission (Optional[float]): The dollar value commission you want to charge the end user.
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
"""

commission: Optional[float] = None
commission_type: Optional[CommissionType] = None


class StopOrderRequest(BaseStopOrderRequest):
Expand All @@ -754,9 +760,11 @@ class StopOrderRequest(BaseStopOrderRequest):
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.
commission (Optional[float]): The dollar value commission you want to charge the end user.
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
"""

commission: Optional[float] = None
commission_type: Optional[CommissionType] = None


class StopLimitOrderRequest(BaseStopLimitOrderRequest):
Expand All @@ -780,9 +788,11 @@ class StopLimitOrderRequest(BaseStopLimitOrderRequest):
order is converted to a limit order.
limit_price (float): The worst fill price for a limit or stop limit order.
commission (Optional[float]): The dollar value commission you want to charge the end user
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
"""

commission: Optional[float] = None
commission_type: Optional[CommissionType] = None


class TrailingStopOrderRequest(BaseTrailingStopOrderRequest):
Expand All @@ -805,9 +815,11 @@ class TrailingStopOrderRequest(BaseTrailingStopOrderRequest):
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.
commission (Optional[float]): The dollar value commission you want to charge the end user.
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
"""

commission: Optional[float] = None
commission_type: Optional[CommissionType] = None


class CancelOrderResponse(BaseCancelOrderResponse):
Expand Down
5 changes: 5 additions & 0 deletions docs/api_reference/broker/enums.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,8 @@ JournalStatus
-------------

.. autoenum:: alpaca.broker.enums.JournalStatus

CommissionType
----------------------

.. autoenum:: alpaca.broker.enums.CommissionType
51 changes: 26 additions & 25 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

182 changes: 182 additions & 0 deletions tests/broker/broker_client/test_order_commission_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
from alpaca.broker.client import BrokerClient
from alpaca.common.enums import BaseURL
from alpaca.broker.enums import CommissionType
from alpaca.trading.enums import OrderSide, OrderStatus, TimeInForce
from alpaca.broker.requests import (
MarketOrderRequest,
)


def test_order_commission_type(reqmock, client: BrokerClient):
account_id = "0d969814-40d6-4b2b-99ac-2e37427f1ad2"

# 1. commission_type notional per order
reqmock.post(
f"{BaseURL.BROKER_SANDBOX.value}/v1/trading/accounts/{account_id}/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,
"commission_type": "notional"
}
""",
)

mo = MarketOrderRequest(
symbol="SPY",
side=OrderSide.BUY,
time_in_force=TimeInForce.DAY,
qty=1,
commission_type=CommissionType.NOTIONAL,
)

assert mo.commission_type == CommissionType.NOTIONAL

mo_response = client.submit_order_for_account(account_id, mo)

assert mo_response.status == OrderStatus.ACCEPTED

# 2. commission_type bps
reqmock.post(
f"{BaseURL.BROKER_SANDBOX.value}/v1/trading/accounts/{account_id}/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,
"commission_type": "bps"
}
""",
)

mo = MarketOrderRequest(
symbol="SPY",
side=OrderSide.BUY,
time_in_force=TimeInForce.DAY,
qty=1,
commission_type=CommissionType.BPS,
)

assert mo.commission_type == CommissionType.BPS

mo_response = client.submit_order_for_account(account_id, mo)

assert mo_response.status == OrderStatus.ACCEPTED

# 3. commission_type per qty
reqmock.post(
f"{BaseURL.BROKER_SANDBOX.value}/v1/trading/accounts/{account_id}/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": 3,
"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,
"commission_type": "bps"
}
""",
)

mo = MarketOrderRequest(
symbol="SPY",
side=OrderSide.BUY,
time_in_force=TimeInForce.DAY,
qty=1,
commission_type=CommissionType.QTY,
)

assert mo.commission_type == CommissionType.QTY

mo_response = client.submit_order_for_account(account_id, mo)

assert mo_response.status == OrderStatus.ACCEPTED

0 comments on commit b554eb8

Please sign in to comment.