Skip to content
Open
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
31 changes: 30 additions & 1 deletion stake/asx/product.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional
from typing import Any, List, Optional

from pydantic import BaseModel, ConfigDict

Expand Down Expand Up @@ -41,6 +41,23 @@ class Product(BaseModel):
points_change: Optional[float] = None
percentage_change: Optional[float] = None
out_of_market_price: Optional[float] = None

# Aggregated depth fields
id: Optional[str] = None
ticker: Optional[str] = None
total_buy_count: Optional[int] = None
total_sell_count: Optional[int] = None
total_buy_volume: Optional[int] = None
total_sell_volume: Optional[int] = None
buy_orders: Optional[List[dict[str, Any]]] = None
Copy link
Owner

Choose a reason for hiding this comment

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

please let's avoid using Any. we need to define an exact object.

Copy link
Owner

Choose a reason for hiding this comment

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

let's make this a separate object, maybe ProductAggregatedDepth ?

sell_orders: Optional[List[dict[str, Any]]] = None

# Course of sales fields
total_volume: Optional[int] = None
total_trades: Optional[int] = None
total_value: Optional[float] = None
course_of_sales: Optional[List[dict[str, Any]]] = None
Comment on lines +55 to +59
Copy link
Owner

Choose a reason for hiding this comment

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

let's make this a separated object , ProductCourseOfSales .

course_of_sales should be Optional[List[CourseOfSale]]


model_config = ConfigDict(alias_generator=camelcase)


Expand All @@ -57,6 +74,18 @@ async def get(self, symbol: str) -> Optional[Product]:

return Product(**data)

async def depth(self, symbol: str) -> Product:
data = await self._client.get(
self._client.exchange.aggregated_depth.format(symbol=symbol)
)
return Product(**data)

async def course_of_sales(self, symbol: str) -> Product:
data = await self._client.get(
self._client.exchange.course_of_sales.format(symbol=symbol)
)
return Product(**data)

async def search(self, request: ProductSearchByName) -> List[Instrument]:
products = await self._client.get(
self._client.exchange.products_suggestions.format(keyword=request.keyword)
Expand Down
10 changes: 10 additions & 0 deletions stake/constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ class ASXUrl(BaseModel):
market_status: str = urljoin(
ASX_STAKE_URL, "api/asx/instrument/quoteTwo/ASX", allow_fragments=True
)
aggregated_depth: str = urljoin(
ASX_STAKE_URL,
"api/asx/instrument/aggregatedDepth/{symbol}?type=EQUITY",
allow_fragments=True,
)
course_of_sales: str = urljoin(
ASX_STAKE_URL,
"api/asx/instrument/courseOfSales/{symbol}",
allow_fragments=True,
)

orders: str = urljoin(ASX_STAKE_URL, "api/asx/orders", allow_fragments=True)

Expand Down
34 changes: 34 additions & 0 deletions tests/cassettes/test_product/test_asx_product_course_of_sales.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Content-Type:
- application/json
method: GET
uri: https://api2.prd.hellostake.com/api/user
response:
body:
string: '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2023-10-01", "createdDate": 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": null, "userProfile": {"residentialAddress": null, "postalAddress": null}, "ledgerBalance": 0.0, "fxSpeed": "Regular", "dateOfBirth": null, "upToDateDetails2021": "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": false}'
headers: {}
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Content-Type:
- application/json
method: GET
uri: https://api2.prd.hellostake.com/api/asx/instrument/courseOfSales/ORG
response:
body:
string: '{"ticker":"ORG","totalVolume":4340689,"totalTrades":15192,"totalValue":52386677.84,"courseOfSales":[{"id":"1430712669","instrumentCodeId":"ORG.XAU","exchangeMarket":"ASX","price":12.07,"volume":126,"value":1520.82,"tradeTimeMillis":1771824891233,"cancelledTimeMillis":null,"buyOrderNumber":"8116826296338466744","sellOrderNumber":"8116826296338466744"},{"id":"156728782706","instrumentCodeId":"ORG.XAU","exchangeMarket":"CXA","price":12.07,"volume":4116,"value":49680.12,"tradeTimeMillis":1771824535706,"cancelledTimeMillis":null,"buyOrderNumber":null,"sellOrderNumber":null}]}'
headers: {}
status:
code: 200
message: OK
version: 1
34 changes: 34 additions & 0 deletions tests/cassettes/test_product/test_asx_product_depth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Content-Type:
- application/json
method: GET
uri: https://api2.prd.hellostake.com/api/user
response:
body:
string: '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2023-10-01", "createdDate": 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": null, "userProfile": {"residentialAddress": null, "postalAddress": null}, "ledgerBalance": 0.0, "fxSpeed": "Regular", "dateOfBirth": null, "upToDateDetails2021": "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": false}'
headers: {}
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Content-Type:
- application/json
method: GET
uri: https://api2.prd.hellostake.com/api/asx/instrument/aggregatedDepth/ORG
response:
body:
string: '{"id":"ORG#depth","ticker":"ORG","totalBuyCount":2,"totalSellCount":2,"totalBuyVolume":17227,"totalSellVolume":34912,"buyOrders":[{"id":"buy-12.06","price":12.06,"volume":15461,"numberOfOrders":1,"value":186459.66,"orders":[{"id":"buy-1","exchange":"ASX","volume":15461,"value":186459.66,"undisclosed":false}]},{"id":"buy-12.05","price":12.05,"volume":1766,"numberOfOrders":1,"value":21280.3,"orders":[{"id":"buy-2","exchange":"ASX","volume":1766,"value":21280.3,"undisclosed":false}]}],"sellOrders":[{"id":"sell-12.08","price":12.08,"volume":514,"numberOfOrders":1,"value":6209.12,"orders":[{"id":"sell-1","exchange":"ASX","volume":514,"value":6209.12,"undisclosed":false}]},{"id":"sell-12.10","price":12.1,"volume":34398,"numberOfOrders":3,"value":416215.8,"orders":[{"id":"sell-2","exchange":"ASX","volume":18500,"value":223850.0,"undisclosed":false}]}]}'
headers: {}
status:
code: 200
message: OK
version: 1
24 changes: 24 additions & 0 deletions tests/test_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,27 @@ async def test_search_products(

product = await tracing_client.products.product_from_instrument(search_results[0])
assert product


@pytest.mark.vcr()
@pytest.mark.asyncio
async def test_asx_product_depth(tracing_client: StakeClient):
tracing_client.set_exchange(constant.ASX)

depth = await tracing_client.products.depth("ORG")

assert depth.ticker == "ORG"
assert depth.buy_orders
assert depth.sell_orders
Comment on lines +82 to +89
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Consider asserting more of the depth response shape and numeric fields, not just presence of orders.

This test only checks that ticker is set and that buy_orders/sell_orders are truthy, but not the additional depth fields you introduced (total_buy_count, total_sell_count, total_buy_volume, total_sell_volume, etc.) or the shape of individual order entries. Please extend the assertions to cover some of these numeric fields (e.g., non-None, correct type) and verify the structure of an order item (expected keys like price/volume) so the test better guards against regressions in the response mapping.



@pytest.mark.vcr()
@pytest.mark.asyncio
async def test_asx_product_course_of_sales(tracing_client: StakeClient):
tracing_client.set_exchange(constant.ASX)

sales = await tracing_client.products.course_of_sales("ORG")

assert sales.ticker == "ORG"
assert sales.total_trades
assert sales.course_of_sales
Comment on lines +94 to +101
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Expand the course-of-sales test to validate key numeric fields and the shape of individual sales entries.

Right now this only asserts ticker, total_trades, and that course_of_sales is truthy. Since the Product model now exposes total_volume, total_trades, and total_value, please assert these are populated and of the expected type for this cassette. Also add an assertion that at least one course_of_sales entry has the expected keys/structure to catch future API or deserialization changes.