Skip to content

Commit 185f010

Browse files
committed
feat: Extend Order API with commission_type
1 parent b880c26 commit 185f010

File tree

7 files changed

+219
-3
lines changed

7 files changed

+219
-3
lines changed

alpaca/broker/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ def __init__(
122122
base_url = (
123123
url_override
124124
if url_override is not None
125-
else BaseURL.BROKER_SANDBOX.value if sandbox else BaseURL.BROKER_PRODUCTION
125+
else BaseURL.BROKER_SANDBOX.value
126+
if sandbox
127+
else BaseURL.BROKER_PRODUCTION
126128
)
127129

128130
super().__init__(

alpaca/broker/enums.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,14 @@ class JournalStatus(str, Enum):
426426
REFUSED = "refused"
427427
CORRECT = "correct"
428428
DELETED = "deleted"
429+
430+
431+
class CommissionType(str, Enum):
432+
"""
433+
Represents the available ways of charging commission. This determines how
434+
the value in the commission field is interpreted.
435+
"""
436+
437+
NOTIONAL = "notional"
438+
BPS = "bps"
439+
QTY = "qty"

alpaca/broker/requests.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
VisaType,
3333
JournalEntryType,
3434
JournalStatus,
35+
CommissionType,
3536
)
3637
from alpaca.common.enums import Sort, SupportedCurrencies
3738
from alpaca.trading.enums import ActivityType, AccountStatus, OrderType, AssetClass
@@ -662,6 +663,7 @@ class OrderRequest(BaseOrderRequest):
662663
take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
663664
stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
664665
commission (Optional[float]): The dollar value commission you want to charge the end user.
666+
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
665667
"""
666668

667669
commission: Optional[float] = None
@@ -705,9 +707,11 @@ class MarketOrderRequest(BaseMarketOrderRequest):
705707
take_profit (Optional[TakeProfitRequest]): For orders with multiple legs, an order to exit a profitable trade.
706708
stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
707709
commission (Optional[float]): The dollar value commission you want to charge the end user.
710+
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
708711
"""
709712

710713
commission: Optional[float] = None
714+
commission_type: Optional[CommissionType] = None
711715

712716

713717
class LimitOrderRequest(BaseLimitOrderRequest):
@@ -729,9 +733,11 @@ class LimitOrderRequest(BaseLimitOrderRequest):
729733
stop_loss (Optional[StopLossRequest]): For orders with multiple legs, an order to exit a losing trade.
730734
limit_price (float): The worst fill price for a limit or stop limit order.
731735
commission (Optional[float]): The dollar value commission you want to charge the end user.
736+
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
732737
"""
733738

734739
commission: Optional[float] = None
740+
commission_type: Optional[CommissionType] = None
735741

736742

737743
class StopOrderRequest(BaseStopOrderRequest):
@@ -754,9 +760,11 @@ class StopOrderRequest(BaseStopOrderRequest):
754760
stop_price (float): The price at which the stop order is converted to a market order or a stop limit
755761
order is converted to a limit order.
756762
commission (Optional[float]): The dollar value commission you want to charge the end user.
763+
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
757764
"""
758765

759766
commission: Optional[float] = None
767+
commission_type: Optional[CommissionType] = None
760768

761769

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

785794
commission: Optional[float] = None
795+
commission_type: Optional[CommissionType] = None
786796

787797

788798
class TrailingStopOrderRequest(BaseTrailingStopOrderRequest):
@@ -805,9 +815,11 @@ class TrailingStopOrderRequest(BaseTrailingStopOrderRequest):
805815
trail_price (Optional[float]): The absolute price difference by which the trailing stop will trail.
806816
trail_percent (Optional[float]): The percent price difference by which the trailing stop will trail.
807817
commission (Optional[float]): The dollar value commission you want to charge the end user.
818+
commission_type (Optional[CommissionType]): An enum to select how to interpret the value provided in the commission field: notional, bps, qty.
808819
"""
809820

810821
commission: Optional[float] = None
822+
commission_type: Optional[CommissionType] = None
811823

812824

813825
class CancelOrderResponse(BaseCancelOrderResponse):

alpaca/trading/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ def __init__(
7777
base_url=(
7878
url_override
7979
if url_override
80-
else BaseURL.TRADING_PAPER if paper else BaseURL.TRADING_LIVE
80+
else BaseURL.TRADING_PAPER
81+
if paper
82+
else BaseURL.TRADING_LIVE
8183
),
8284
sandbox=paper,
8385
raw_data=raw_data,

alpaca/trading/stream.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ def __init__(
3838
self._endpoint = (
3939
url_override
4040
if url_override
41-
else BaseURL.TRADING_STREAM_PAPER if paper else BaseURL.TRADING_STREAM_LIVE
41+
else BaseURL.TRADING_STREAM_PAPER
42+
if paper
43+
else BaseURL.TRADING_STREAM_LIVE
4244
)
4345
self._ws = None
4446
self._running = False

docs/api_reference/broker/enums.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,8 @@ JournalStatus
136136
-------------
137137

138138
.. autoenum:: alpaca.broker.enums.JournalStatus
139+
140+
CommissionType
141+
----------------------
142+
143+
.. autoenum:: alpaca.broker.enums.CommissionType
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
from alpaca.broker.client import BrokerClient
2+
from alpaca.common.enums import BaseURL
3+
from alpaca.broker.enums import CommissionType
4+
from alpaca.trading.enums import OrderSide, OrderStatus, TimeInForce
5+
from alpaca.broker.requests import (
6+
MarketOrderRequest,
7+
)
8+
9+
10+
def test_order_commission_type(reqmock, client: BrokerClient):
11+
account_id = "0d969814-40d6-4b2b-99ac-2e37427f1ad2"
12+
13+
# 1. commission_type notional per order
14+
reqmock.post(
15+
f"{BaseURL.BROKER_SANDBOX.value}/v1/trading/accounts/{account_id}/orders",
16+
text="""
17+
{
18+
"id": "61e69015-8549-4bfd-b9c3-01e75843f47d",
19+
"client_order_id": "eb9e2aaa-f71a-4f51-b5b4-52a6c565dad4",
20+
"created_at": "2021-03-16T18:38:01.942282Z",
21+
"updated_at": "2021-03-16T18:38:01.942282Z",
22+
"submitted_at": "2021-03-16T18:38:01.937734Z",
23+
"filled_at": null,
24+
"expired_at": null,
25+
"canceled_at": null,
26+
"failed_at": null,
27+
"replaced_at": null,
28+
"replaced_by": null,
29+
"replaces": null,
30+
"asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
31+
"symbol": "SPY`",
32+
"asset_class": "us_equity",
33+
"notional": null,
34+
"qty": 1,
35+
"filled_qty": "0",
36+
"filled_avg_price": null,
37+
"order_class": "simple",
38+
"order_type": "market",
39+
"type": "market",
40+
"side": "buy",
41+
"time_in_force": "day",
42+
"limit_price": null,
43+
"stop_price": null,
44+
"status": "accepted",
45+
"extended_hours": false,
46+
"legs": null,
47+
"trail_percent": null,
48+
"trail_price": null,
49+
"hwm": null,
50+
"commission": 1.25,
51+
"commission_type": "notional"
52+
}
53+
""",
54+
)
55+
56+
mo = MarketOrderRequest(
57+
symbol="SPY",
58+
side=OrderSide.BUY,
59+
time_in_force=TimeInForce.DAY,
60+
qty=1,
61+
commission_type=CommissionType.NOTIONAL,
62+
)
63+
64+
assert mo.commission_type == CommissionType.NOTIONAL
65+
66+
mo_response = client.submit_order_for_account(account_id, mo)
67+
68+
assert mo_response.status == OrderStatus.ACCEPTED
69+
70+
# 2. commission_type bps
71+
reqmock.post(
72+
f"{BaseURL.BROKER_SANDBOX.value}/v1/trading/accounts/{account_id}/orders",
73+
text="""
74+
{
75+
"id": "61e69015-8549-4bfd-b9c3-01e75843f47d",
76+
"client_order_id": "eb9e2aaa-f71a-4f51-b5b4-52a6c565dad4",
77+
"created_at": "2021-03-16T18:38:01.942282Z",
78+
"updated_at": "2021-03-16T18:38:01.942282Z",
79+
"submitted_at": "2021-03-16T18:38:01.937734Z",
80+
"filled_at": null,
81+
"expired_at": null,
82+
"canceled_at": null,
83+
"failed_at": null,
84+
"replaced_at": null,
85+
"replaced_by": null,
86+
"replaces": null,
87+
"asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
88+
"symbol": "SPY`",
89+
"asset_class": "us_equity",
90+
"notional": null,
91+
"qty": 1,
92+
"filled_qty": "0",
93+
"filled_avg_price": null,
94+
"order_class": "simple",
95+
"order_type": "market",
96+
"type": "market",
97+
"side": "buy",
98+
"time_in_force": "day",
99+
"limit_price": null,
100+
"stop_price": null,
101+
"status": "accepted",
102+
"extended_hours": false,
103+
"legs": null,
104+
"trail_percent": null,
105+
"trail_price": null,
106+
"hwm": null,
107+
"commission": 1.25,
108+
"commission_type": "bps"
109+
}
110+
""",
111+
)
112+
113+
mo = MarketOrderRequest(
114+
symbol="SPY",
115+
side=OrderSide.BUY,
116+
time_in_force=TimeInForce.DAY,
117+
qty=1,
118+
commission_type=CommissionType.BPS,
119+
)
120+
121+
assert mo.commission_type == CommissionType.BPS
122+
123+
mo_response = client.submit_order_for_account(account_id, mo)
124+
125+
assert mo_response.status == OrderStatus.ACCEPTED
126+
127+
# 3. commission_type per qty
128+
reqmock.post(
129+
f"{BaseURL.BROKER_SANDBOX.value}/v1/trading/accounts/{account_id}/orders",
130+
text="""
131+
{
132+
"id": "61e69015-8549-4bfd-b9c3-01e75843f47d",
133+
"client_order_id": "eb9e2aaa-f71a-4f51-b5b4-52a6c565dad4",
134+
"created_at": "2021-03-16T18:38:01.942282Z",
135+
"updated_at": "2021-03-16T18:38:01.942282Z",
136+
"submitted_at": "2021-03-16T18:38:01.937734Z",
137+
"filled_at": null,
138+
"expired_at": null,
139+
"canceled_at": null,
140+
"failed_at": null,
141+
"replaced_at": null,
142+
"replaced_by": null,
143+
"replaces": null,
144+
"asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
145+
"symbol": "SPY`",
146+
"asset_class": "us_equity",
147+
"notional": null,
148+
"qty": 3,
149+
"filled_qty": "0",
150+
"filled_avg_price": null,
151+
"order_class": "simple",
152+
"order_type": "market",
153+
"type": "market",
154+
"side": "buy",
155+
"time_in_force": "day",
156+
"limit_price": null,
157+
"stop_price": null,
158+
"status": "accepted",
159+
"extended_hours": false,
160+
"legs": null,
161+
"trail_percent": null,
162+
"trail_price": null,
163+
"hwm": null,
164+
"commission": 1.25,
165+
"commission_type": "bps"
166+
}
167+
""",
168+
)
169+
170+
mo = MarketOrderRequest(
171+
symbol="SPY",
172+
side=OrderSide.BUY,
173+
time_in_force=TimeInForce.DAY,
174+
qty=1,
175+
commission_type=CommissionType.QTY,
176+
)
177+
178+
assert mo.commission_type == CommissionType.QTY
179+
180+
mo_response = client.submit_order_for_account(account_id, mo)
181+
182+
assert mo_response.status == OrderStatus.ACCEPTED

0 commit comments

Comments
 (0)