Add endpoints for ASX market depth and ASX course of sales#175
Add endpoints for ASX market depth and ASX course of sales#175apmarkham wants to merge 1 commit intostabacco:masterfrom
Conversation
Reviewer's GuideAdds ASX-specific market depth and course-of-sales support by extending the ASX Product model with new depth and sales fields, wiring new ASX URLs, and exposing async client methods with VCR-backed tests. Sequence diagram for new ASX market depth and course of sales callssequenceDiagram
actor User
participant StakeClient
participant ProductClient
participant Exchange
participant ASXUrl
participant HttpClient
User->>StakeClient: create_with_login(login_request, exchange ASX)
activate StakeClient
StakeClient->>ProductClient: get_products_client()
activate ProductClient
User->>ProductClient: depth(symbol)
activate ProductClient
ProductClient->>Exchange: get_exchange()
activate Exchange
Exchange->>ASXUrl: access_aggregated_depth_template()
activate ASXUrl
ASXUrl-->>Exchange: aggregated_depth_url_template
deactivate ASXUrl
Exchange-->>ProductClient: aggregated_depth_url_template
deactivate Exchange
ProductClient->>HttpClient: get(formatted_aggregated_depth_url)
activate HttpClient
HttpClient-->>ProductClient: depth_json
deactivate HttpClient
ProductClient-->>User: Product(depth_fields)
deactivate ProductClient
User->>ProductClient: course_of_sales(symbol)
activate ProductClient
ProductClient->>Exchange: get_exchange()
activate Exchange
Exchange->>ASXUrl: access_course_of_sales_template()
activate ASXUrl
ASXUrl-->>Exchange: course_of_sales_url_template
deactivate ASXUrl
Exchange-->>ProductClient: course_of_sales_url_template
deactivate Exchange
ProductClient->>HttpClient: get(formatted_course_of_sales_url)
activate HttpClient
HttpClient-->>ProductClient: course_of_sales_json
deactivate HttpClient
ProductClient-->>User: Product(course_of_sales_fields)
deactivate ProductClient
deactivate StakeClient
Class diagram for updated ASX Product and ASXUrl modelsclassDiagram
class Product {
+str~Optional~ id
+str~Optional~ ticker
+int~Optional~ total_buy_count
+int~Optional~ total_sell_count
+int~Optional~ total_buy_volume
+int~Optional~ total_sell_volume
+List~dict~buy_orders
+List~dict~sell_orders
+int~Optional~ total_volume
+int~Optional~ total_trades
+float~Optional~ total_value
+List~dict~course_of_sales
}
class ASXUrl {
+str market_status
+str aggregated_depth
+str course_of_sales
+str orders
}
class ProductClient {
+Product get(symbol str)
+Product depth(symbol str)
+Product course_of_sales(symbol str)
+List~Instrument~ search(request ProductSearchByName)
}
class HttpClient {
+Any get(url str)
}
class Exchange {
+ASXUrl exchange
}
ProductClient --> Product : returns
ProductClient --> HttpClient : uses
Exchange --> ASXUrl : has
ProductClient --> Exchange : uses
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The
Productmodel is starting to accumulate unrelated concerns (quote data, depth, and course of sales); consider introducing separate Pydantic models (e.g.,Depth,CourseOfSales) instead of adding all fields toProductto keep the domain model cohesive. - The fields
buy_orders,sell_orders, andcourse_of_salesare typed asList[dict[str, Any]]; defining dedicated typed models for these structures would improve type safety and make the API easier to understand and use. - Both
depthandcourse_of_salescurrently returnProduct, even though the underlying payloads are structurally different; returning more specific response types would make the methods’ contracts clearer and avoid partial/unused fields onProduct.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `Product` model is starting to accumulate unrelated concerns (quote data, depth, and course of sales); consider introducing separate Pydantic models (e.g., `Depth`, `CourseOfSales`) instead of adding all fields to `Product` to keep the domain model cohesive.
- The fields `buy_orders`, `sell_orders`, and `course_of_sales` are typed as `List[dict[str, Any]]`; defining dedicated typed models for these structures would improve type safety and make the API easier to understand and use.
- Both `depth` and `course_of_sales` currently return `Product`, even though the underlying payloads are structurally different; returning more specific response types would make the methods’ contracts clearer and avoid partial/unused fields on `Product`.
## Individual Comments
### Comment 1
<location path="tests/test_product.py" line_range="82-89" />
<code_context>
+
+@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
+
+
</code_context>
<issue_to_address>
**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.
</issue_to_address>
### Comment 2
<location path="tests/test_product.py" line_range="94-101" />
<code_context>
+
+@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
</code_context>
<issue_to_address>
**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.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| 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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
|
@apmarkham are you able to address the comments that sourcery has added? |
| 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 |
There was a problem hiding this comment.
please let's avoid using Any. we need to define an exact object.
| 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 |
There was a problem hiding this comment.
let's make this a separate object, maybe ProductAggregatedDepth ?
| # 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 |
There was a problem hiding this comment.
let's make this a separated object , ProductCourseOfSales .
course_of_sales should be Optional[List[CourseOfSale]]
|
Thanks @apmarkham for the contribution, it will be very helpful |
Hi @stabacco,
Thanks for the great package, it's been very useful. Stake Black (their paid monthly subscription) gives access to a few extra endpoints which the package doesn't currently support, see the image below for some of the data it gives access to:
This PR implements just the two ASX endpoints for now, so market depth and course of sales. The data looks a bit like this in the UI:
To take a look at the data the API returns just authenticate and call the methods:
I implemented this in the
Productclass which seems the best approach, given the data is unique to each ticker.Please review when you have some time and let me know if you have any feedback on style and how I've implemented this. If you're happy with these changes I will make time to implement some of the other US-exchange features (financials, analyst ratings, price targets).
Summary by Sourcery
Add support for ASX market depth and course-of-sales data on products.
New Features:
Enhancements:
Tests: