Skip to content
Closed
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
66 changes: 66 additions & 0 deletions stake/asx/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,60 @@ class Product(BaseModel):
model_config = ConfigDict(alias_generator=camelcase)


class DepthOrder(BaseModel):
id: Optional[str] = None
exchange: Optional[str] = None
volume: Optional[int] = None
value: Optional[float] = None
undisclosed: Optional[bool] = None
model_config = ConfigDict(alias_generator=camelcase)


class DepthLevel(BaseModel):
id: Optional[str] = None
price: Optional[float] = None
volume: Optional[int] = None
number_of_orders: Optional[int] = None
value: Optional[float] = None
orders: Optional[List[DepthOrder]] = None
model_config = ConfigDict(alias_generator=camelcase)


class ProductAggregatedDepth(BaseModel):
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[DepthLevel]] = None
sell_orders: Optional[List[DepthLevel]] = None
model_config = ConfigDict(alias_generator=camelcase)


class CourseOfSale(BaseModel):
id: Optional[str] = None
instrument_code_id: Optional[str] = None
exchange_market: Optional[str] = None
price: Optional[float] = None
volume: Optional[int] = None
value: Optional[float] = None
trade_time_millis: Optional[int] = None
cancelled_time_millis: Optional[int] = None
buy_order_number: Optional[str] = None
sell_order_number: Optional[str] = None
model_config = ConfigDict(alias_generator=camelcase)


class ProductCourseOfSales(BaseModel):
ticker: Optional[str] = None
total_volume: Optional[int] = None
total_trades: Optional[int] = None
total_value: Optional[float] = None
course_of_sales: Optional[List[CourseOfSale]] = None
model_config = ConfigDict(alias_generator=camelcase)


class ProductsClient(BaseClient):
async def get(self, symbol: str) -> Optional[Product]:
"""Given a symbol it will return the matching product.
Expand All @@ -57,6 +111,18 @@ async def get(self, symbol: str) -> Optional[Product]:

return Product(**data)

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

async def course_of_sales(self, symbol: str) -> ProductCourseOfSales:
data = await self._client.get(
self._client.exchange.course_of_sales.format(symbol=symbol)
)
return ProductCourseOfSales(**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?type=EQUITY
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
47 changes: 47 additions & 0 deletions tests/test_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,50 @@ 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 isinstance(depth.total_buy_count, int)
assert isinstance(depth.total_sell_count, int)
assert isinstance(depth.total_buy_volume, int)
assert isinstance(depth.total_sell_volume, int)
assert depth.buy_orders
assert depth.sell_orders

buy_order = depth.buy_orders[0]
assert isinstance(buy_order.price, float)
assert isinstance(buy_order.volume, int)
assert isinstance(buy_order.number_of_orders, int)
assert buy_order.orders
assert isinstance(buy_order.orders[0].exchange, str)
assert isinstance(buy_order.orders[0].undisclosed, bool)


@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 isinstance(sales.total_volume, int)
assert isinstance(sales.total_trades, int)
assert isinstance(sales.total_value, float)
assert sales.course_of_sales

sale = sales.course_of_sales[0]
assert isinstance(sale.id, str)
assert isinstance(sale.instrument_code_id, str)
assert isinstance(sale.exchange_market, str)
assert isinstance(sale.price, float)
assert isinstance(sale.volume, int)
assert isinstance(sale.value, float)
assert isinstance(sale.trade_time_millis, int)
Loading