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

return order_id;unit test added #27

Merged
merged 2 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
17 changes: 13 additions & 4 deletions cschwabpy/SchwabAsyncClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import base64
import json

HEADER_ORDER_ID_PATTERN = re.compile(r"orders/(\d+)")


class SchwabAsyncClient(object):
def __init__(
Expand Down Expand Up @@ -213,7 +215,8 @@ async def get_instruments_async(

async def place_order_async(
self, account_number_hash: AccountNumberWithHashID, order: Order
) -> bool:
) -> int:
"""Place an order (Equity or Option) for a specific account, returns order id (int)."""
await self._ensure_valid_access_token()
target_url = f"{SCHWAB_TRADER_API_BASE_URL}/accounts/{account_number_hash.hashValue}/orders"
client = httpx.AsyncClient() if self.__client is None else self.__client
Expand All @@ -226,9 +229,15 @@ async def place_order_async(
headers=_header,
)
if response.status_code == 201:
return True
else:
raise Exception("Failed to place order. Status: ", response.status_code)
location_url = response.headers.get("Location")
if location_url is not None:
needle = re.search(HEADER_ORDER_ID_PATTERN, location_url)
if needle:
return int(needle.group(1))
else:
raise Exception("Failed to locate order ID in response")

raise Exception("Failed to place order. Status: ", response.status_code)
finally:
if not self.__keep_client_alive:
await client.aclose()
Expand Down
16 changes: 12 additions & 4 deletions cschwabpy/SchwabClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import base64
import json

HEADER_ORDER_ID_PATTERN = re.compile(r"orders/(\d+)")


class SchwabClient(object):
"""This is regular sync client. For async client, use SchwabClientAsync."""
Expand Down Expand Up @@ -215,7 +217,7 @@ def get_instruments(

def place_order(
self, account_number_hash: AccountNumberWithHashID, order: Order
) -> bool:
) -> int:
self._ensure_valid_access_token()
target_url = f"{SCHWAB_TRADER_API_BASE_URL}/accounts/{account_number_hash.hashValue}/orders"
client = httpx.Client() if self.__client is None else self.__client
Expand All @@ -229,9 +231,15 @@ def place_order(
headers=_header,
)
if response.status_code == 201:
return True
else:
raise Exception("Failed to place order. Status: ", response.status_code)
location_url = response.headers.get("Location")
if location_url is not None:
needle = re.search(HEADER_ORDER_ID_PATTERN, location_url)
if needle:
return int(needle.group(1))
else:
raise Exception("Failed to locate order ID in response")

raise Exception("Failed to place order. Status: ", response.status_code)
finally:
if not self.__keep_client_alive:
client.close()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "cschwabpy"
version = "0.1.1"
version = "0.1.2"
description = ""
authors = ["Tony Wang <[email protected]>"]
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name="CSchwabPy",
version="0.1.1",
version="0.1.2",
description="Charles Schwab Stock & Option Trade API Client for Python.",
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down
100 changes: 96 additions & 4 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,26 @@
OptionContractStrategy,
)
from cschwabpy.models.trade_models import (
SecuritiesAccount,
AccountNumberWithHashID,
MarginAccount,
CashAccount,
AccountType,
Order,
ExecutionLeg,
ExecutionType,
OrderLeg,
PositionEffect,
OrderLegInstruction,
AssetType,
OrderLegCollection,
AccountInstrument,
OrderActivity,
ComplexOrderStrategyType,
Session,
Duration,
OrderType,
OrderStatus,
OrderStrategyType,
InstrumentProjection,
AccountType,
SecuritiesAccount,
)
from cschwabpy.models.token import Tokens, LocalTokenStore
from cschwabpy.SchwabAsyncClient import SchwabAsyncClient
Expand Down Expand Up @@ -142,6 +155,85 @@ async def test_get_order(httpx_mock: HTTPXMock):
assert retrieved_orders2[0].cancelable == False


@pytest.mark.asyncio
async def test_place_order(httpx_mock: HTTPXMock):
mocked_token = mock_tokens()
token_store = LocalTokenStore()
if os.path.exists(Path(token_store.token_output_path)):
os.remove(token_store.token_output_path) # clean up before test

opt_order1 = Order(
session=Session.NORMAL,
duration=Duration.GOOD_TILL_CANCEL,
orderType=OrderType.NET_DEBIT,
complexOrderStrategyType=ComplexOrderStrategyType.BUTTERFLY,
price=0.9,
orderLegCollection=[
OrderLegCollection(
orderLegType=AssetType.OPTION,
instrument=AccountInstrument(
assetType=AssetType.OPTION, symbol="SPXW 240701C05530000"
),
instruction=OrderLegInstruction.BUY_TO_OPEN,
positionEffect=PositionEffect.OPENING,
quantity=1,
),
OrderLegCollection(
orderLegType=AssetType.OPTION,
instrument=AccountInstrument(
assetType=AssetType.OPTION, symbol="SPXW 240701C05540000"
),
instruction=OrderLegInstruction.SELL_TO_OPEN,
positionEffect=PositionEffect.OPENING,
quantity=2,
),
OrderLegCollection(
orderLegType=AssetType.OPTION,
instrument=AccountInstrument(
assetType=AssetType.OPTION, symbol="SPXW 240701C05550000"
),
instruction=OrderLegInstruction.BUY_TO_OPEN,
positionEffect=PositionEffect.OPENING,
quantity=1,
),
],
)

order_id = 1000847830245
token_store.save_tokens(mocked_token)
location_url = (
f"https://api.schwabapi.com/trader/v1/accounts/HASHHERE/orders/{order_id}"
)
headers = {"Location": location_url}
httpx_mock.add_response(headers=headers, status_code=201)
async with httpx.AsyncClient() as client:
cschwab_client = SchwabAsyncClient(
app_client_id="fake_id",
app_secret="fake_secret",
token_store=token_store,
tokens=mocked_token,
http_client=client,
)
new_order_id = await cschwab_client.place_order_async(
account_number_hash=mock_account(), order=opt_order1
)
assert new_order_id == order_id

with httpx.Client() as client2:
cschwab_client = SchwabClient(
app_client_id="fake_id",
app_secret="fake_secret",
token_store=token_store,
http_client=client2,
)

new_order_id2 = cschwab_client.place_order(
account_number_hash=mock_account(),
order=opt_order1,
)
assert new_order_id2 == order_id


@pytest.mark.asyncio
async def test_get_order_by_id(httpx_mock: HTTPXMock):
json_mock = get_mock_response()["filled_order"]
Expand Down