diff --git a/stake/asx/product.py b/stake/asx/product.py index 80806ea..e8456c4 100644 --- a/stake/asx/product.py +++ b/stake/asx/product.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Any, List, Optional from pydantic import BaseModel, ConfigDict @@ -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 + 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 + model_config = ConfigDict(alias_generator=camelcase) @@ -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) diff --git a/stake/constant.py b/stake/constant.py index 6ab0f13..540f8f2 100644 --- a/stake/constant.py +++ b/stake/constant.py @@ -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) diff --git a/tests/cassettes/test_product/test_asx_product_course_of_sales.yaml b/tests/cassettes/test_product/test_asx_product_course_of_sales.yaml new file mode 100644 index 0000000..9633aff --- /dev/null +++ b/tests/cassettes/test_product/test_asx_product_course_of_sales.yaml @@ -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 diff --git a/tests/cassettes/test_product/test_asx_product_depth.yaml b/tests/cassettes/test_product/test_asx_product_depth.yaml new file mode 100644 index 0000000..af00520 --- /dev/null +++ b/tests/cassettes/test_product/test_asx_product_depth.yaml @@ -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 diff --git a/tests/test_product.py b/tests/test_product.py index bff3702..ae3419b 100644 --- a/tests/test_product.py +++ b/tests/test_product.py @@ -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 + + +@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 \ No newline at end of file