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: Extend Order API with commission_type #462

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
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.
hiohiohio marked this conversation as resolved.
Show resolved Hide resolved
"""

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

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(
hiohiohio marked this conversation as resolved.
Show resolved Hide resolved
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,
hiohiohio marked this conversation as resolved.
Show resolved Hide resolved
)

assert mo.commission_type == CommissionType.NOTIONAL

mo_response = client.submit_order_for_account(account_id, mo)

assert mo_response.status == OrderStatus.ACCEPTED
hiohiohio marked this conversation as resolved.
Show resolved Hide resolved

# 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,
hiohiohio marked this conversation as resolved.
Show resolved Hide resolved
"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"
hiohiohio marked this conversation as resolved.
Show resolved Hide resolved
}
""",
)

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
Loading