Skip to content

Commit b5270d5

Browse files
update/lint list_orders func and add list_order_details func
1 parent 613bb9a commit b5270d5

File tree

2 files changed

+136
-17
lines changed

2 files changed

+136
-17
lines changed

pyetrade/order.py

+121-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from datetime import datetime
23
from typing import Union
34

45
import dateutil.parser
@@ -31,16 +32,14 @@ def to_decimal_str(price: float, round_down: bool) -> str:
3132
return spstr
3233

3334

34-
def get_request_result(
35-
req: OAuth1Session.request, empty_json: dict, resp_format: str = "xml"
36-
) -> dict:
35+
def get_request_result(req: OAuth1Session.request, resp_format: str = "xml") -> dict:
3736
LOGGER.debug(req.text)
3837

3938
if resp_format == "json":
4039
if req.text.strip() == "":
4140
# otherwise, when ETrade server return empty string, we got this error:
4241
# simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
43-
req_output = empty_json # empty json object
42+
req_output = {}
4443
else:
4544
req_output = req.json()
4645
else:
@@ -50,8 +49,7 @@ def get_request_result(
5049
raise Exception(
5150
f'Etrade API Error - Code: {req_output["Error"]["code"]}, Msg: {req_output["Error"]["message"]}'
5251
)
53-
else:
54-
return req_output
52+
return req_output
5553

5654

5755
# return Etrade internal option symbol: e.g. "PLTR--220218P00023000" ref:_test_option_symbol()
@@ -96,7 +94,7 @@ class ETradeOrder(object):
9694
:param resource_owner_key: Resource key from :class:`pyetrade.authorization.ETradeOAuth`
9795
:type resource_owner_key: str, required
9896
:param resource_owner_secret: Resource secret from
99-
:class:`pyetrade.authorization.ETradeOAuth`
97+
:class: `pyetrade.authorization.ETradeOAuth`
10098
:type resource_owner_secret: str, required
10199
:param dev: Defines Sandbox (True) or Live (False) ETrade, defaults to True
102100
:type dev: bool, optional
@@ -126,30 +124,135 @@ def __init__(
126124
)
127125

128126
def list_orders(
129-
self, account_id_key: str, resp_format: str = "json", **kwargs
127+
self,
128+
account_id_key: str,
129+
marker: str = None,
130+
count: int = 25,
131+
status: str = None,
132+
from_date: datetime = None,
133+
to_date: datetime = None,
134+
symbols: list[str] = None,
135+
security_type: str = None,
136+
transaction_type: str = None,
137+
market_session: str = "REGULAR",
138+
resp_format: str = "json",
130139
) -> dict:
131140
""":description: Lists orders for a specific account ID Key
132141
133142
:param account_id_key: AccountIDKey from :class:`pyetrade.accounts.ETradeAccounts.list_accounts`
134143
:type account_id_key: str, required
135-
:param resp_format: Desired Response format, defaults to xml
144+
:param marker: Specifies the desired starting point of the set of items to return (defaults to None)
145+
:type marker: str, optional
146+
:param count: Number of transactions to return, defaults to 25 (max 100)
147+
:type count: int, optional
148+
:param status: Order status (defaults to None)
149+
:type status: str, optional
150+
:status values:
151+
* OPEN
152+
* EXECUTED
153+
* CANCELLED
154+
* INDIVIDUAL_FILLS
155+
* CANCEL_REQUESTED
156+
* EXPIRED
157+
* REJECTED
158+
:param from_date: The earliest date to include in the date range (history is available for two years).
159+
Both fromDate and toDate should be used together, toDate should be greater than fromDate.
160+
(defaults to None)
161+
:type from_date: datetime obj, optional
162+
:param to_date: The latest date to include in the date range (history is available for two years).
163+
Both fromDate and toDate should be used together, toDate should be greater than fromDate.
164+
(defaults to None)
165+
:type to_date: datetime obj, optional
166+
:param symbols: The market symbol(s) for the security being bought or sold. (defaults to None, Max 25 symbols)
167+
:type symbols: list[str], optional
168+
:param security_type: The security type (defaults to None - Returns all types)
169+
:type security_type: str, optional
170+
:security_type values:
171+
* EQ
172+
* OPTN
173+
* MF
174+
* MMF
175+
:param transaction_type: Type of transaction (defaults to None - Returns all types)
176+
:type transaction_type: str, optional
177+
:transaction_type values:
178+
* ATNM
179+
* BUY
180+
* SELL
181+
* SELL_SHORT
182+
* BUY_TO_COVER
183+
* MF_EXCHANGE
184+
:param market_session: The market session, defaults to REGULAR
185+
:type market_session: str, optional
186+
:market_session values:
187+
* REGULAR
188+
* EXTENDED
189+
:param resp_format: Desired Response format, defaults to json
136190
:type resp_format: str, optional
137-
:param kwargs: Parameters for api. Refer to EtradeRef for options
138-
:type kwargs: ``**kwargs``, optional
139191
:return: List of orders for an account
140192
:rtype: ``xml`` or ``json`` based on ``resp_format``
141193
:EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html
142194
"""
143195

144-
api_url = f"{self.base_url}/{account_id_key}/orders"
196+
if symbols and len(symbols) >= 26:
197+
LOGGER.warning(
198+
"list_orders asked for %d requests; only first 25 returned"
199+
% len(symbols)
200+
)
145201

146-
if resp_format == "json":
147-
api_url += ".json"
202+
api_url = f"{self.base_url}/{account_id_key}/orders{'.json' if resp_format == 'json' else ''}"
203+
LOGGER.debug(api_url)
204+
205+
if count >= 101:
206+
LOGGER.debug(
207+
f"Count {count} is greater than the max allowable value (100), using 100"
208+
)
209+
count = 100
210+
211+
payload = {
212+
"marker": marker,
213+
"count": count,
214+
"status": status,
215+
"fromDate": from_date.date().strftime("%m%d%Y") if from_date else from_date,
216+
"toDate": to_date.date().strftime("%m%d%Y") if to_date else to_date,
217+
"symbol": ",".join([sym for sym in symbols[:25]]) if symbols else symbols,
218+
"securityType": security_type,
219+
"transactionType": transaction_type,
220+
"marketSession": market_session,
221+
}
222+
223+
req = self.session.get(api_url, params=payload, timeout=self.timeout)
224+
req.raise_for_status()
225+
226+
LOGGER.debug(req.text)
227+
228+
return get_request_result(req, resp_format)
229+
230+
def list_order_details(
231+
self, account_id_key: str, order_id: int, resp_format: str = "json"
232+
):
233+
"""
234+
:description: Lists order details of a specific account ID Key and order ID
235+
236+
:param account_id_key: AccountIDKey from :class:`pyetrade.accounts.ETradeAccounts.list_accounts`
237+
:type account_id_key: str, required
238+
:param order_id: Order ID of placed order, order IDs can be retrieved from calling the list_orders() function
239+
:type account_id_key: int, required
240+
:param resp_format: Desired Response format, defaults to json
241+
:type resp_format: str, optional
242+
:return: List of orders for an account
243+
:rtype: ``xml`` or ``json`` based on ``resp_format``
244+
:EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html
245+
"""
148246

247+
api_url = f"{self.base_url}/{account_id_key}/orders/{order_id}{'.json' if resp_format == 'json' else ''}"
149248
LOGGER.debug(api_url)
150-
req = self.session.get(api_url, params=kwargs, timeout=self.timeout)
151249

152-
return get_request_result(req, {}, resp_format)
250+
req = self.session.get(api_url)
251+
req.raise_for_status()
252+
253+
LOGGER.debug(req.text)
254+
255+
return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json()
153256

154257
def find_option_orders(
155258
self,
@@ -327,7 +430,7 @@ def perform_request(
327430
LOGGER.debug("xml payload: %s", payload)
328431
req = method(api_url, data=payload, headers=headers, timeout=self.timeout)
329432

330-
return get_request_result(req, {}, resp_format)
433+
return get_request_result(req, resp_format)
331434

332435
def preview_equity_order(self, **kwargs) -> dict:
333436
"""API is used to submit an order request for preview before placing it
@@ -496,6 +599,7 @@ def place_equity_order(self, **kwargs) -> dict:
496599
"No previewId given, previewing before placing order "
497600
"because of an Etrade bug as of 1/1/2019"
498601
)
602+
499603
preview = self.preview_equity_order(**kwargs)
500604
kwargs["previewId"] = preview["PreviewOrderResponse"]["PreviewIds"][
501605
"previewId"

tests/test_order.py

+15
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,21 @@ def test_list_orders(self, MockOAuthSession):
5555
self.assertTrue(MockOAuthSession().get().json.called)
5656
self.assertTrue(MockOAuthSession().get.called)
5757

58+
@patch("pyetrade.order.OAuth1Session")
59+
def test_list_order_details(self, MockOAuthSession):
60+
MockOAuthSession().get().json.return_value = {"accountId": "12345"}
61+
MockOAuthSession().get().text = r"<xml> returns </xml>"
62+
63+
orders = order.ETradeOrder(
64+
"abc123", "xyz123", "abctoken", "xyzsecret", dev=False
65+
)
66+
67+
self.assertTrue(
68+
isinstance(orders.list_order_details("12345", 123, "json"), dict)
69+
)
70+
self.assertTrue(MockOAuthSession().get().json.called)
71+
self.assertTrue(MockOAuthSession().get.called)
72+
5873
def test_find_option_orders(self):
5974
orders = order.ETradeOrder(
6075
"abc123", "xyz123", "abctoken", "xyzsecret", dev=False

0 commit comments

Comments
 (0)