Repository navigation
Rebuild against the published DXtrade spec: SL/TP actually placed, correct endpoints, typed errors, packaging - #2
Merged
Merged
Conversation
The README said official docs were not accessible, so every request shape
was inferred. They are public (demo.dx.trade/developers, OpenAPI at
/dxsca-web/swagger/openapi.json). Checked against them, most calls were
still wrong after the 2026 fixes:
- SL/TP were silently never placed: they waited for a positionCode in the
order response, which only has orderId/updateOrderId. Now one atomic
IF-THEN order group, so entry and protections succeed or fail together.
- get_balance read equity/margin from /portfolio (they live in /metrics)
and returned Balance(0,0,0,0) on any parse problem. Now /metrics, strict.
- Positions/orders parsed invented names (id, entryPrice, stopLoss) from a
bare list; the API wraps {"positions": [...]} with positionCode, symbol,
openPrice, stopLossPrice, legs[0].price... Parsers are now strict.
- History hit /accounts/{a}/history; it is /accounts/{a}/orders/history.
- modify_order PATCHed /orders/{id} with price/qty; the spec is PUT
/accounts/{a}/orders with the complete order and If-Match. cancel_order
lacked If-Match (403 per spec). Both now read the ETag and retry on 412.
- close_position / modify_position_sl_tp called /positions/{id}, which does
not exist. Now a linked CLOSE market order, and protection orders
modified via PUT or placed via POST. New close_all() (bulk close).
- ping was GET (spec: POST); a renewed sessionToken is adopted.
- Push API used made-up 'subscribe' messages, Atmosphere headers and the
token in the URL (logged at INFO). Now the spec envelope (type/session/
requestId), PingRequest->Ping replies, /md channel for quotes, portfolio
and metrics subscriptions, reconnect that really replays subscriptions.
Also: one _request() path with typed exceptions (status, errorCode,
description), a single re-login on 401, account discovery via /users when
none is configured (never invented), URL-encoded account/order codes,
OrderPlacementError.ambiguous for timeouts after sending, no logging
config in the library, credentials never logged. Module moved into a
package (dxtrade_wrapper/); `from dxtrade_wrapper import ...` is unchanged.
The old file is in git history at dxtrade_wrapper.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pip install . (or git+https://...) now works and ships type hints. Version lives in dxtrade_wrapper.__version__. Not published to PyPI; that stays the owner's call. Dev requirements gain jsonschema/websockets/ruff/mypy for the new conformance and live-socket tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_spec_conformance.py (opt-in: DXTRADE_SPEC_TESTS=1, downloads the public spec rather than vendoring it) drives every REST method and asserts each request hits a documented path/method/query param, sends only declared fields and validates against the schema, and that every fixture matches its response schema. Mutation-checked: renaming quantity->qty or reverting the history path makes it fail. test_push_live_socket.py runs the Push channels against a fake DXtrade server over real sockets: PingRequest/Ping on both channels, quote subscription on /md, and resubscription after the TCP connection drops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README: status is now 'spec-conformant, not live-verified' with links to the public docs. The quick start runs with no broker account (verified from a fresh clone), plus an API table mapping each method to its endpoint, and sections on errors, sessions and security. - Drops the 'Use this instead: pip install dxtrade-sdk' advice: no such package exists on PyPI (404). - example.py: read-only tour of a real account; orders only with --demo-order. examples/offline_demo.py prints every request it sends. - CI: ruff + pytest on 3.9/3.11/3.13/3.14, mypy + spec-conformance job, wheel build and install. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- get_positions(include_pnl=True) fills Position.pnl from the per-position fpl in /metrics?include-positions=true (one extra request). - A GET that gets 429 waits out Retry-After (up to 5s, 1s if absent) and retries once; the spec's default read limit is 10/s, easy to hit from a dashboard poll loop. Orders are never retried on 429. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ambiguous Independent review against the prose REST spec (demo.dx.trade/developers): - modify_order on the entry of an IF-THEN group sent a single-order PUT. Modify Order example 1 shows that converts the group to a single order, so the stop loss and take profit disappear. Members of a working group are now modified as the whole group (example 2b); OCO members and groups with a missing child are refused with nothing sent. - The Open Orders listing gives OPEN legs positionCode == orderId; the replace request copied it, which the spec says must be omitted unless positionEffect is CLOSE. The fixture now has it, as in the spec. - The 412 retry resent the body built from the stale read, silently undoing the change that caused the 412. It is rebuilt from the re-read. - 5xx on placement, and a group acknowledged with fewer OrderResponses than orders sent, are now OrderPlacementError(ambiguous=True). - Concurrent 401s on one stale token log in once; _relogin no longer flips is_authenticated off while logging in. - Conformance test also checks the prose rules (positionCode only on CLOSE, no type in PUT) and a group modify. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
license = "MIT" (PEP 639) fails validation on setuptools 76 and older
("project.license must be ... {text: ...}"), so the declared >=69 floor
did not build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…H again Both were in main's .env.example; the rewritten example silently ignored them, so an existing .env for a broker with non-default paths would hit the wrong URLs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilds the client against the published DXtrade REST/Push spec (https://demo.dx.trade/developers/, OpenAPI at https://demo.dx.trade/dxsca-web/swagger/openapi.json). The README said no official documentation was available; it is public.
Why (trading-safety bugs on
main)positionCode, and per the spec it never does. Callers got "success" for an unprotected position.get_balance()read equity and margin from/portfolio(they live in/metrics), and returned a fake zero balance on parse errors.If-Match;/positions/{id};except Exceptioneverywhere.What changed
If-Match; on a 412 the body is rebuilt from fresh data.If-Match.close_all().PingRequest/Ping, a separate/mdmarket-data socket, and subscriptions replayed after a reconnect. The token never appears in URLs, logs, reprs or exceptions.POST /pingkeepalive, adopting any renewed token, and one automatic re-login on 401. Order retries keep the sameorderCode; 5xx and timeouts after sending are flaggedambiguous.AuthenticationError,NotFoundError,ConflictError,PreconditionFailedError,RateLimitError,ServerError,OrderPlacementError)./users/{login}.pyproject.toml, 0.2.0,py.typed) with an offline demo and CI.from dxtrade_wrapper import …still works for every public name onmain.Verification
Do not trade real money with it until it has been run on a demo account. Check in particular:
quantity;See the review notes in the README.