ctrader-api-client 0.2.3__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/Justfile +4 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/PKG-INFO +1 -1
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/api/accounts.md +0 -1
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/api/events.md +0 -4
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/api/models.md +3 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/api/trading.md +10 -0
- ctrader_api_client-0.3.0/protos/VERSION +1 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/protos/vendor/OpenApiMessages.proto +5 -38
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/protos/vendor/OpenApiModelMessages.proto +1 -7
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/pyproject.toml +1 -1
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/_internal/messages.py +0 -10
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/_internal/proto/OpenApiMessages.py +1 -51
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/_internal/proto/OpenApiModelMessages.py +1 -7
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/_internal/proto/__init__.py +0 -10
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/api/market_data.py +10 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/api/trading.py +36 -14
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/client.py +0 -2
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/connection/heartbeat.py +3 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/connection/protocol.py +8 -3
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/connection/transport.py +16 -2
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/events/__init__.py +0 -2
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/events/router.py +17 -19
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/events/types.py +0 -21
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/models/__init__.py +2 -1
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/models/position.py +15 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/api/test_trading.py +85 -1
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/events/test_router.py +0 -31
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/events/test_types.py +0 -19
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/uv.lock +1 -1
- ctrader_api_client-0.2.3/protos/VERSION +0 -1
- ctrader_api_client-0.2.3/protos/vendor/.gitkeep +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/.claude/settings.local.json +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/.github/workflows/docs.yml +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/.gitignore +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/.pre-commit-config.yaml +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/.python-version +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/LICENSE +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/README.md +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/api/client.md +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/api/enums.md +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/api/market-data.md +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/api/symbols.md +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/getting-started.md +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/docs/index.md +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/mkdocs.yml +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/protos/SOURCE +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/protos/update.sh +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/protos/vendor/OpenApiCommonMessages.proto +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/protos/vendor/OpenApiCommonModelMessages.proto +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/scripts/fix_proto_imports.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/__init__.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/_internal/__init__.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/_internal/serialization.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/api/__init__.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/api/accounts.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/api/symbols.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/auth/__init__.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/auth/credentials.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/auth/manager.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/config.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/connection/__init__.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/enums.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/events/emitter.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/exceptions.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/models/_base.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/models/account.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/models/deal.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/models/market_data.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/models/order.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/models/requests.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/models/symbol.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/py.typed +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/_internal/test_messages.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/_internal/test_serialization.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/api/conftest.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/api/test_accounts.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/api/test_market_data_api.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/api/test_symbols.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/auth/test_credentials.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/auth/test_manager.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/connection/test_heartbeat.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/connection/test_protocol.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/connection/test_transport.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/events/test_emitter.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/models/test_account.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/models/test_deal.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/models/test_market_data.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/models/test_order.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/models/test_position.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/models/test_requests.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/models/test_symbol.py +0 -0
- {ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/tests/unit/test_client.py +0 -0
|
@@ -108,10 +108,6 @@ async def on_ready(event: ReadyEvent):
|
|
|
108
108
|
options:
|
|
109
109
|
show_source: false
|
|
110
110
|
|
|
111
|
-
::: ctrader_api_client.events.PnLChangeEvent
|
|
112
|
-
options:
|
|
113
|
-
show_source: false
|
|
114
|
-
|
|
115
111
|
::: ctrader_api_client.events.TrailingStopChangedEvent
|
|
116
112
|
options:
|
|
117
113
|
show_source: false
|
|
@@ -10,6 +10,7 @@ Access via `client.trading`.
|
|
|
10
10
|
options:
|
|
11
11
|
show_source: false
|
|
12
12
|
members:
|
|
13
|
+
- get_unrealized_pnl_per_position
|
|
13
14
|
- place_order
|
|
14
15
|
- amend_order
|
|
15
16
|
- cancel_order
|
|
@@ -175,3 +176,12 @@ for deal in deals:
|
|
|
175
176
|
if deal.is_closing_deal and deal.close_detail:
|
|
176
177
|
print(f" Gross P/L: {deal.close_detail.gross_profit}")
|
|
177
178
|
```
|
|
179
|
+
|
|
180
|
+
### Get PnL for all open positions
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
unrealized_pnl = await client.trading.get_unrealized_pnl_per_position(account_id)
|
|
184
|
+
|
|
185
|
+
for p in unrealized_pnl:
|
|
186
|
+
print(f"Position {p.position_id}: Gross Unrealized PnL = {p.gross_unrealized_pnl}, Net Unrealized PnL = {p.net_unrealized_pnl}")
|
|
187
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
main
|
|
@@ -91,8 +91,8 @@ message ProtoOANewOrderReq {
|
|
|
91
91
|
optional double stopPrice = 8; // Stop Price, can be specified for the STOP and the STOP_LIMIT orders only.
|
|
92
92
|
optional ProtoOATimeInForce timeInForce = 9 [default = GOOD_TILL_CANCEL]; // The specific order execution or expiration instruction - GOOD_TILL_DATE, GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL, MARKET_ON_OPEN.
|
|
93
93
|
optional int64 expirationTimestamp = 10; // The Unix time in milliseconds of Order expiration. Should be set for the Good Till Date orders.
|
|
94
|
-
optional double stopLoss = 11; // The absolute Stop Loss price (1.23456 for example). Not supported for
|
|
95
|
-
optional double takeProfit = 12; // The absolute Take Profit price (1.23456 for example). Unsupported for
|
|
94
|
+
optional double stopLoss = 11; // The absolute Stop Loss price (1.23456 for example). Not supported for MARKET orders.
|
|
95
|
+
optional double takeProfit = 12; // The absolute Take Profit price (1.23456 for example). Unsupported for MARKET orders.
|
|
96
96
|
optional string comment = 13; // User-specified comment. MaxLength = 512.
|
|
97
97
|
optional double baseSlippagePrice = 14; // Base price to calculate relative slippage price for MARKET_RANGE order.
|
|
98
98
|
optional int32 slippageInPoints = 15; // Slippage distance for MARKET_RANGE and STOP_LIMIT order.
|
|
@@ -139,8 +139,8 @@ message ProtoOAAmendOrderReq {
|
|
|
139
139
|
optional double limitPrice = 5; // The Limit Price, can be specified for the LIMIT order only.
|
|
140
140
|
optional double stopPrice = 6; // The Stop Price, can be specified for the STOP and the STOP_LIMIT orders.
|
|
141
141
|
optional int64 expirationTimestamp = 7; // The Unix timestamp in milliseconds of Order expiration. Should be set for the Good Till Date orders.
|
|
142
|
-
optional double stopLoss = 8; // The absolute Stop Loss price (e.g. 1.23456). Not supported for
|
|
143
|
-
optional double takeProfit = 9; // The absolute Take Profit price (e.g. 1.23456). Not supported for
|
|
142
|
+
optional double stopLoss = 8; // The absolute Stop Loss price (e.g. 1.23456). Not supported for MARKET orders.
|
|
143
|
+
optional double takeProfit = 9; // The absolute Take Profit price (e.g. 1.23456). Not supported for MARKET orders.
|
|
144
144
|
optional int32 slippageInPoints = 10; // Slippage distance for the MARKET_RANGE and the STOP_LIMIT orders.
|
|
145
145
|
optional int64 relativeStopLoss = 11; // The relative Stop Loss can be specified instead of the absolute one. Specified in 1/100000 of a unit of price. (e.g. 123000 in protocol means 1.23, 53423782 means 534.23782) For BUY stopLoss = entryPrice - relativeStopLoss, for SELL stopLoss = entryPrice + relativeStopLoss.
|
|
146
146
|
optional int64 relativeTakeProfit = 12; // The relative Take Profit can be specified instead of the absolute one. Specified in 1/100000 of a unit of price. (e.g. 123000 in protocol means 1.23, 53423782 means 534.23782) For BUY takeProfit = entryPrice + relativeTakeProfit, for SELL takeProfit = entryPrice - relativeTakeProfit.
|
|
@@ -637,7 +637,7 @@ message ProtoOAAccountLogoutReq {
|
|
|
637
637
|
required int64 ctidTraderAccountId = 2; // The unique identifier of the trader's account in cTrader platform.
|
|
638
638
|
}
|
|
639
639
|
|
|
640
|
-
/** Response to the
|
|
640
|
+
/** Response to the ProtoOAAccountLogoutReq request. Actual logout of trading account will be completed on ProtoOAAccountDisconnectEvent. */
|
|
641
641
|
message ProtoOAAccountLogoutRes {
|
|
642
642
|
optional ProtoOAPayloadType payloadType = 1 [default = PROTO_OA_ACCOUNT_LOGOUT_RES];
|
|
643
643
|
|
|
@@ -793,36 +793,3 @@ message ProtoOAGetPositionUnrealizedPnLRes {
|
|
|
793
793
|
repeated ProtoOAPositionUnrealizedPnL positionUnrealizedPnL = 3; // Information about trader's positions' unrealized PnLs.
|
|
794
794
|
required uint32 moneyDigits = 4; // Specifies the exponent of various monetary values. E.g., moneyDigits = 8 should be interpreted as the value multiplied by 10^8 with the 'real' value equal to 10053099944 / 10^8 = 100.53099944. Affects positionUnrealizedPnL.grossUnrealizedPnL, positionUnrealizedPnL.netUnrealizedPnL.
|
|
795
795
|
}
|
|
796
|
-
|
|
797
|
-
// The event that is sent when the unrealized PnL is changed due to market movement. Requires subscribing to PnL events, see ProtoOAv1PnLChangeSubscribeReq
|
|
798
|
-
message ProtoOAv1PnLChangeEvent {
|
|
799
|
-
optional ProtoOAPayloadType payloadType = 1 [default = PROTO_OA_V1_PNL_CHANGE_EVENT];
|
|
800
|
-
required int64 ctidTraderAccountId = 2; //Unique identifier of the trader's account. Used to match responses to trader's accounts.
|
|
801
|
-
required int64 grossUnrealizedPnL = 3; //The gross unrealized PnL denoted in the account deposit currency
|
|
802
|
-
required int64 netUnrealizedPnL = 4; //The net unrealized PnL denoted in the account deposit currency
|
|
803
|
-
required uint32 moneyDigits = 5; //Specifies the exponent of various monetary values. E.g., moneyDigits = 8 should be interpreted as the value multiplied by 10^8 with the 'real' value equal to 10053099944 / 10^8 = 100.53099944
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
//The request to subscribe to ProtoOAv1PnLChangeEvent
|
|
807
|
-
message ProtoOAv1PnLChangeSubscribeReq {
|
|
808
|
-
optional ProtoOAPayloadType payloadType = 1 [default = PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_REQ];
|
|
809
|
-
required int64 ctidTraderAccountId = 2; //Unique identifier of the trader's account. Used to match responses to trader's accounts.
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
//The response for ProtoOAv1PnLChangeSubscribeReq
|
|
813
|
-
message ProtoOAv1PnLChangeSubscribeRes {
|
|
814
|
-
optional ProtoOAPayloadType payloadType = 1 [default = PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_RES];
|
|
815
|
-
required int64 ctidTraderAccountId = 2; // The unique identifier of the trader's account in cTrader platform.
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
//The request to stop an existing subscription to PnL events. The subscriber who sends this request will stop receiving ProtoOAv1PnLChangeEvent
|
|
819
|
-
message ProtoOAv1PnLChangeUnSubscribeReq {
|
|
820
|
-
optional ProtoOAPayloadType payloadType = 1 [default = PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_REQ];
|
|
821
|
-
required int64 ctidTraderAccountId = 2; //Unique identifier of the trader's account. Used to match responses to trader's accounts.
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
//The response for ProtoOAv1PnLChangeUnSubscribeReq
|
|
825
|
-
message ProtoOAv1PnLChangeUnSubscribeRes {
|
|
826
|
-
optional ProtoOAPayloadType payloadType = 1 [default = PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_RES];
|
|
827
|
-
required int64 ctidTraderAccountId = 2; //Unique identifier of the trader's account. Used to match responses to trader's accounts.
|
|
828
|
-
}
|
{ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/protos/vendor/OpenApiModelMessages.proto
RENAMED
|
@@ -100,11 +100,6 @@ enum ProtoOAPayloadType {
|
|
|
100
100
|
PROTO_OA_DEAL_OFFSET_LIST_RES = 2186;
|
|
101
101
|
PROTO_OA_GET_POSITION_UNREALIZED_PNL_REQ = 2187;
|
|
102
102
|
PROTO_OA_GET_POSITION_UNREALIZED_PNL_RES = 2188;
|
|
103
|
-
PROTO_OA_V1_PNL_CHANGE_EVENT = 2189;
|
|
104
|
-
PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_REQ = 2190;
|
|
105
|
-
PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_RES = 2191;
|
|
106
|
-
PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_REQ = 2192;
|
|
107
|
-
PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_RES = 2193;
|
|
108
103
|
}
|
|
109
104
|
|
|
110
105
|
/** Asset entity. */
|
|
@@ -328,7 +323,7 @@ enum ProtoOAOrderStatus {
|
|
|
328
323
|
ORDER_STATUS_CANCELLED = 5; // Order is cancelled. Might be valid for orders with partially filled volume that were cancelled by LP.
|
|
329
324
|
}
|
|
330
325
|
|
|
331
|
-
/** Stop Order and Stop
|
|
326
|
+
/** Stop Order and Stop Loss triggering method ENUM. */
|
|
332
327
|
enum ProtoOAOrderTriggerMethod {
|
|
333
328
|
TRADE = 1; // Stop Order: buy is triggered by ask, sell by bid; Stop Loss Order: for buy position is triggered by bid and for sell position by ask.
|
|
334
329
|
OPPOSITE = 2; // Stop Order: buy is triggered by bid, sell by ask; Stop Loss Order: for buy position is triggered by ask and for sell position by bid.
|
|
@@ -687,7 +682,6 @@ enum ProtoOAErrorCode {
|
|
|
687
682
|
UNABLE_TO_CANCEL_ORDER = 134; // Unable to cancel order.
|
|
688
683
|
UNABLE_TO_AMEND_ORDER = 135; // Unable to amend order.
|
|
689
684
|
SHORT_SELLING_NOT_ALLOWED = 136; // Short selling is not allowed.
|
|
690
|
-
NOT_SUBSCRIBED_TO_PNL = 137;//This session is not subscribed via ProtoOAv1PnLChangeSubscribeReq
|
|
691
685
|
}
|
|
692
686
|
|
|
693
687
|
enum ProtoOALimitedRiskMarginCalculationStrategy {
|
{ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/_internal/messages.py
RENAMED
|
@@ -100,11 +100,6 @@ from .proto import (
|
|
|
100
100
|
ProtoOAUnsubscribeLiveTrendbarRes,
|
|
101
101
|
ProtoOAUnsubscribeSpotsReq,
|
|
102
102
|
ProtoOAUnsubscribeSpotsRes,
|
|
103
|
-
ProtoOAv1PnLChangeEvent,
|
|
104
|
-
ProtoOAv1PnLChangeSubscribeReq,
|
|
105
|
-
ProtoOAv1PnLChangeSubscribeRes,
|
|
106
|
-
ProtoOAv1PnLChangeUnSubscribeReq,
|
|
107
|
-
ProtoOAv1PnLChangeUnSubscribeRes,
|
|
108
103
|
ProtoOAVersionReq,
|
|
109
104
|
ProtoOAVersionRes,
|
|
110
105
|
ProtoPayloadType,
|
|
@@ -207,11 +202,6 @@ _PAYLOAD_TYPE_TO_CLASS: dict[int, type[betterproto.Message]] = {
|
|
|
207
202
|
ProtoOAPayloadType.PROTO_OA_DEAL_OFFSET_LIST_RES: ProtoOADealOffsetListRes,
|
|
208
203
|
ProtoOAPayloadType.PROTO_OA_GET_POSITION_UNREALIZED_PNL_REQ: ProtoOAGetPositionUnrealizedPnLReq,
|
|
209
204
|
ProtoOAPayloadType.PROTO_OA_GET_POSITION_UNREALIZED_PNL_RES: ProtoOAGetPositionUnrealizedPnLRes,
|
|
210
|
-
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_EVENT: ProtoOAv1PnLChangeEvent,
|
|
211
|
-
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_REQ: ProtoOAv1PnLChangeSubscribeReq,
|
|
212
|
-
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_RES: ProtoOAv1PnLChangeSubscribeRes,
|
|
213
|
-
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_REQ: ProtoOAv1PnLChangeUnSubscribeReq,
|
|
214
|
-
ProtoOAPayloadType.PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_RES: ProtoOAv1PnLChangeUnSubscribeRes,
|
|
215
205
|
}
|
|
216
206
|
|
|
217
207
|
|
|
@@ -850,7 +850,7 @@ class ProtoOAAccountLogoutReq(betterproto.Message):
|
|
|
850
850
|
@dataclass
|
|
851
851
|
class ProtoOAAccountLogoutRes(betterproto.Message):
|
|
852
852
|
"""
|
|
853
|
-
* Response to the
|
|
853
|
+
* Response to the ProtoOAAccountLogoutReq request. Actual logout of trading
|
|
854
854
|
account will be completed on ProtoOAAccountDisconnectEvent.
|
|
855
855
|
"""
|
|
856
856
|
|
|
@@ -1060,53 +1060,3 @@ class ProtoOAGetPositionUnrealizedPnLRes(betterproto.Message):
|
|
|
1060
1060
|
ctid_trader_account_id: int = betterproto.int64_field(2)
|
|
1061
1061
|
position_unrealized_pn_l: list["ProtoOAPositionUnrealizedPnL"] = betterproto.message_field(3)
|
|
1062
1062
|
money_digits: int = betterproto.uint32_field(4)
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
@dataclass
|
|
1066
|
-
class ProtoOAv1PnLChangeEvent(betterproto.Message):
|
|
1067
|
-
"""
|
|
1068
|
-
The event that is sent when the unrealized PnL is changed due to market
|
|
1069
|
-
movement. Requires subscribing to PnL events, see
|
|
1070
|
-
ProtoOAv1PnLChangeSubscribeReq
|
|
1071
|
-
"""
|
|
1072
|
-
|
|
1073
|
-
payload_type: "ProtoOAPayloadType" = betterproto.enum_field(1)
|
|
1074
|
-
ctid_trader_account_id: int = betterproto.int64_field(2)
|
|
1075
|
-
gross_unrealized_pn_l: int = betterproto.int64_field(3)
|
|
1076
|
-
net_unrealized_pn_l: int = betterproto.int64_field(4)
|
|
1077
|
-
money_digits: int = betterproto.uint32_field(5)
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
@dataclass
|
|
1081
|
-
class ProtoOAv1PnLChangeSubscribeReq(betterproto.Message):
|
|
1082
|
-
"""The request to subscribe to ProtoOAv1PnLChangeEvent"""
|
|
1083
|
-
|
|
1084
|
-
payload_type: "ProtoOAPayloadType" = betterproto.enum_field(1)
|
|
1085
|
-
ctid_trader_account_id: int = betterproto.int64_field(2)
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
@dataclass
|
|
1089
|
-
class ProtoOAv1PnLChangeSubscribeRes(betterproto.Message):
|
|
1090
|
-
"""The response for ProtoOAv1PnLChangeSubscribeReq"""
|
|
1091
|
-
|
|
1092
|
-
payload_type: "ProtoOAPayloadType" = betterproto.enum_field(1)
|
|
1093
|
-
ctid_trader_account_id: int = betterproto.int64_field(2)
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
@dataclass
|
|
1097
|
-
class ProtoOAv1PnLChangeUnSubscribeReq(betterproto.Message):
|
|
1098
|
-
"""
|
|
1099
|
-
The request to stop an existing subscription to PnL events. The subscriber
|
|
1100
|
-
who sends this request will stop receiving ProtoOAv1PnLChangeEvent
|
|
1101
|
-
"""
|
|
1102
|
-
|
|
1103
|
-
payload_type: "ProtoOAPayloadType" = betterproto.enum_field(1)
|
|
1104
|
-
ctid_trader_account_id: int = betterproto.int64_field(2)
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
@dataclass
|
|
1108
|
-
class ProtoOAv1PnLChangeUnSubscribeRes(betterproto.Message):
|
|
1109
|
-
"""The response for ProtoOAv1PnLChangeUnSubscribeReq"""
|
|
1110
|
-
|
|
1111
|
-
payload_type: "ProtoOAPayloadType" = betterproto.enum_field(1)
|
|
1112
|
-
ctid_trader_account_id: int = betterproto.int64_field(2)
|
|
@@ -96,11 +96,6 @@ class ProtoOAPayloadType(betterproto.Enum):
|
|
|
96
96
|
PROTO_OA_DEAL_OFFSET_LIST_RES = 2186
|
|
97
97
|
PROTO_OA_GET_POSITION_UNREALIZED_PNL_REQ = 2187
|
|
98
98
|
PROTO_OA_GET_POSITION_UNREALIZED_PNL_RES = 2188
|
|
99
|
-
PROTO_OA_V1_PNL_CHANGE_EVENT = 2189
|
|
100
|
-
PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_REQ = 2190
|
|
101
|
-
PROTO_OA_V1_PNL_CHANGE_SUBSCRIBE_RES = 2191
|
|
102
|
-
PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_REQ = 2192
|
|
103
|
-
PROTO_OA_V1_PNL_CHANGE_UN_SUBSCRIBE_RES = 2193
|
|
104
99
|
|
|
105
100
|
|
|
106
101
|
class ProtoOADayOfWeek(betterproto.Enum):
|
|
@@ -227,7 +222,7 @@ class ProtoOAOrderStatus(betterproto.Enum):
|
|
|
227
222
|
|
|
228
223
|
|
|
229
224
|
class ProtoOAOrderTriggerMethod(betterproto.Enum):
|
|
230
|
-
"""* Stop Order and Stop
|
|
225
|
+
"""* Stop Order and Stop Loss triggering method ENUM."""
|
|
231
226
|
|
|
232
227
|
TRADE = 1
|
|
233
228
|
OPPOSITE = 2
|
|
@@ -406,7 +401,6 @@ class ProtoOAErrorCode(betterproto.Enum):
|
|
|
406
401
|
UNABLE_TO_CANCEL_ORDER = 134
|
|
407
402
|
UNABLE_TO_AMEND_ORDER = 135
|
|
408
403
|
SHORT_SELLING_NOT_ALLOWED = 136
|
|
409
|
-
NOT_SUBSCRIBED_TO_PNL = 137
|
|
410
404
|
|
|
411
405
|
|
|
412
406
|
class ProtoOALimitedRiskMarginCalculationStrategy(betterproto.Enum):
|
|
@@ -97,11 +97,6 @@ from .OpenApiMessages import (
|
|
|
97
97
|
ProtoOAUnsubscribeLiveTrendbarRes,
|
|
98
98
|
ProtoOAUnsubscribeSpotsReq,
|
|
99
99
|
ProtoOAUnsubscribeSpotsRes,
|
|
100
|
-
ProtoOAv1PnLChangeEvent,
|
|
101
|
-
ProtoOAv1PnLChangeSubscribeReq,
|
|
102
|
-
ProtoOAv1PnLChangeSubscribeRes,
|
|
103
|
-
ProtoOAv1PnLChangeUnSubscribeReq,
|
|
104
|
-
ProtoOAv1PnLChangeUnSubscribeRes,
|
|
105
100
|
ProtoOAVersionReq,
|
|
106
101
|
ProtoOAVersionRes,
|
|
107
102
|
)
|
|
@@ -311,10 +306,5 @@ __all__ = [
|
|
|
311
306
|
"ProtoOAUnsubscribeSpotsRes",
|
|
312
307
|
"ProtoOAVersionReq",
|
|
313
308
|
"ProtoOAVersionRes",
|
|
314
|
-
"ProtoOAv1PnLChangeEvent",
|
|
315
|
-
"ProtoOAv1PnLChangeSubscribeReq",
|
|
316
|
-
"ProtoOAv1PnLChangeSubscribeRes",
|
|
317
|
-
"ProtoOAv1PnLChangeUnSubscribeReq",
|
|
318
|
-
"ProtoOAv1PnLChangeUnSubscribeRes",
|
|
319
309
|
"ProtoPayloadType",
|
|
320
310
|
]
|
{ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/api/market_data.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from collections.abc import Sequence
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
@@ -33,6 +34,9 @@ if TYPE_CHECKING:
|
|
|
33
34
|
from ..connection import Protocol
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
36
40
|
# Map TrendbarPeriod enum to proto values
|
|
37
41
|
_PERIOD_TO_PROTO: dict[TrendbarPeriod, int] = {
|
|
38
42
|
TrendbarPeriod.M1: ProtoOATrendbarPeriod.M1,
|
|
@@ -115,6 +119,7 @@ class MarketDataAPI:
|
|
|
115
119
|
APIError: If request fails.
|
|
116
120
|
CTraderConnectionTimeoutError: If request times out.
|
|
117
121
|
"""
|
|
122
|
+
logger.debug("Subscribing to spots: account=%d symbols=%s", account_id, symbol_ids)
|
|
118
123
|
request = ProtoOASubscribeSpotsReq(
|
|
119
124
|
ctid_trader_account_id=account_id,
|
|
120
125
|
symbol_id=symbol_ids,
|
|
@@ -149,6 +154,7 @@ class MarketDataAPI:
|
|
|
149
154
|
APIError: If request fails.
|
|
150
155
|
CTraderConnectionTimeoutError: If request times out.
|
|
151
156
|
"""
|
|
157
|
+
logger.debug("Unsubscribing from spots: account=%d symbols=%s", account_id, symbol_ids)
|
|
152
158
|
request = ProtoOAUnsubscribeSpotsReq(
|
|
153
159
|
ctid_trader_account_id=account_id,
|
|
154
160
|
symbol_id=symbol_ids,
|
|
@@ -193,6 +199,7 @@ class MarketDataAPI:
|
|
|
193
199
|
APIError: If request fails.
|
|
194
200
|
CTraderConnectionTimeoutError: If request times out.
|
|
195
201
|
"""
|
|
202
|
+
logger.debug("Subscribing to trendbars: account=%d symbol=%d period=%s", account_id, symbol_id, period.name)
|
|
196
203
|
request = ProtoOASubscribeLiveTrendbarReq(
|
|
197
204
|
ctid_trader_account_id=account_id,
|
|
198
205
|
symbol_id=symbol_id,
|
|
@@ -229,6 +236,7 @@ class MarketDataAPI:
|
|
|
229
236
|
APIError: If request fails.
|
|
230
237
|
CTraderConnectionTimeoutError: If request times out.
|
|
231
238
|
"""
|
|
239
|
+
logger.debug("Unsubscribing from trendbars: account=%d symbol=%d period=%s", account_id, symbol_id, period.name)
|
|
232
240
|
request = ProtoOAUnsubscribeLiveTrendbarReq(
|
|
233
241
|
ctid_trader_account_id=account_id,
|
|
234
242
|
symbol_id=symbol_id,
|
|
@@ -270,6 +278,7 @@ class MarketDataAPI:
|
|
|
270
278
|
APIError: If request fails.
|
|
271
279
|
CTraderConnectionTimeoutError: If request times out.
|
|
272
280
|
"""
|
|
281
|
+
logger.debug("Subscribing to depth: account=%d symbols=%s", account_id, symbol_ids)
|
|
273
282
|
request = ProtoOASubscribeDepthQuotesReq(
|
|
274
283
|
ctid_trader_account_id=account_id,
|
|
275
284
|
symbol_id=symbol_ids,
|
|
@@ -303,6 +312,7 @@ class MarketDataAPI:
|
|
|
303
312
|
APIError: If request fails.
|
|
304
313
|
CTraderConnectionTimeoutError: If request times out.
|
|
305
314
|
"""
|
|
315
|
+
logger.debug("Unsubscribing from depth: account=%d symbols=%s", account_id, symbol_ids)
|
|
306
316
|
request = ProtoOAUnsubscribeDepthQuotesReq(
|
|
307
317
|
ctid_trader_account_id=account_id,
|
|
308
318
|
symbol_id=symbol_ids,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from datetime import UTC, datetime
|
|
4
5
|
from decimal import Decimal
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
@@ -12,18 +13,18 @@ from .._internal.proto import (
|
|
|
12
13
|
ProtoOADealListRes,
|
|
13
14
|
ProtoOAExecutionEvent,
|
|
14
15
|
ProtoOAExecutionType,
|
|
16
|
+
ProtoOAGetPositionUnrealizedPnLReq,
|
|
17
|
+
ProtoOAGetPositionUnrealizedPnLRes,
|
|
15
18
|
ProtoOAOrderErrorEvent,
|
|
16
19
|
ProtoOAOrderListReq,
|
|
17
20
|
ProtoOAOrderListRes,
|
|
18
21
|
ProtoOAReconcileReq,
|
|
19
22
|
ProtoOAReconcileRes,
|
|
20
|
-
ProtoOAv1PnLChangeSubscribeReq,
|
|
21
|
-
ProtoOAv1PnLChangeSubscribeRes,
|
|
22
23
|
)
|
|
23
24
|
from ..enums import ExecutionType, OrderSide
|
|
24
25
|
from ..events import ExecutionEvent
|
|
25
26
|
from ..exceptions import APIError
|
|
26
|
-
from ..models import Deal, Order, Position
|
|
27
|
+
from ..models import Deal, Order, Position, PositionUnrealizedPnL
|
|
27
28
|
from ..models.requests import (
|
|
28
29
|
AmendOrderRequest,
|
|
29
30
|
AmendPositionRequest,
|
|
@@ -36,6 +37,9 @@ if TYPE_CHECKING:
|
|
|
36
37
|
from ..connection import Protocol
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
39
43
|
def _raise_if_order_error(response: object) -> None:
|
|
40
44
|
"""Raise APIError if response is an order error event.
|
|
41
45
|
|
|
@@ -164,31 +168,38 @@ class TradingAPI:
|
|
|
164
168
|
self._protocol = protocol
|
|
165
169
|
self._default_timeout = default_timeout
|
|
166
170
|
|
|
167
|
-
async def
|
|
168
|
-
"""
|
|
169
|
-
|
|
170
|
-
After subscribing, PnL change data will be delivered via the event system.
|
|
171
|
-
Use `@client.on(PnLChangeEvent)` to handle them.
|
|
172
|
-
|
|
173
|
-
Note:
|
|
174
|
-
This subscription seems to be currently rate-limited by cTrader, so it may not work as expected.
|
|
171
|
+
async def get_unrealized_pnl_per_position(self, account_id: int) -> list[PositionUnrealizedPnL]:
|
|
172
|
+
"""Get unrealized PnL for each open position.
|
|
175
173
|
|
|
176
174
|
Args:
|
|
177
175
|
account_id: The cTID trader account ID.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of PositionUnrealizedPnL, one per open position.
|
|
178
179
|
"""
|
|
179
|
-
request =
|
|
180
|
+
request = ProtoOAGetPositionUnrealizedPnLReq(ctid_trader_account_id=account_id)
|
|
180
181
|
|
|
181
182
|
response = await self._protocol.send_request(
|
|
182
183
|
request,
|
|
183
184
|
timeout=self._default_timeout,
|
|
184
185
|
)
|
|
185
186
|
|
|
186
|
-
if not isinstance(response,
|
|
187
|
+
if not isinstance(response, ProtoOAGetPositionUnrealizedPnLRes):
|
|
187
188
|
raise APIError(
|
|
188
189
|
error_code="UNEXPECTED_RESPONSE",
|
|
189
|
-
description=f"Expected
|
|
190
|
+
description=f"Expected ProtoOAGetPositionUnrealizedPnLRes, got {type(response).__name__}",
|
|
190
191
|
)
|
|
191
192
|
|
|
193
|
+
divisor = 10**response.money_digits
|
|
194
|
+
return [
|
|
195
|
+
PositionUnrealizedPnL(
|
|
196
|
+
position_id=p.position_id,
|
|
197
|
+
gross_unrealized_pnl=p.gross_unrealized_pn_l / divisor,
|
|
198
|
+
net_unrealized_pnl=p.net_unrealized_pn_l / divisor,
|
|
199
|
+
)
|
|
200
|
+
for p in response.position_unrealized_pn_l
|
|
201
|
+
]
|
|
202
|
+
|
|
192
203
|
async def place_order(
|
|
193
204
|
self,
|
|
194
205
|
account_id: int,
|
|
@@ -217,6 +228,13 @@ class TradingAPI:
|
|
|
217
228
|
APIError: If request fails.
|
|
218
229
|
CTraderConnectionTimeoutError: If request times out.
|
|
219
230
|
"""
|
|
231
|
+
logger.debug(
|
|
232
|
+
"Placing order: account=%d symbol=%d type=%s volume=%d",
|
|
233
|
+
account_id,
|
|
234
|
+
request.symbol_id,
|
|
235
|
+
request.order_type.name,
|
|
236
|
+
request.volume,
|
|
237
|
+
)
|
|
220
238
|
proto_request = request.to_proto(account_id)
|
|
221
239
|
|
|
222
240
|
response = await self._protocol.send_request(
|
|
@@ -254,6 +272,7 @@ class TradingAPI:
|
|
|
254
272
|
APIError: If request fails or order not found.
|
|
255
273
|
CTraderConnectionTimeoutError: If request times out.
|
|
256
274
|
"""
|
|
275
|
+
logger.debug("Amending order: account=%d order=%d", account_id, request.order_id)
|
|
257
276
|
proto_request = request.to_proto(account_id)
|
|
258
277
|
|
|
259
278
|
response = await self._protocol.send_request(
|
|
@@ -291,6 +310,7 @@ class TradingAPI:
|
|
|
291
310
|
APIError: If request fails or order not found.
|
|
292
311
|
CTraderConnectionTimeoutError: If request times out.
|
|
293
312
|
"""
|
|
313
|
+
logger.debug("Cancelling order: account=%d order=%d", account_id, order_id)
|
|
294
314
|
request = ProtoOACancelOrderReq(
|
|
295
315
|
ctid_trader_account_id=account_id,
|
|
296
316
|
order_id=order_id,
|
|
@@ -331,6 +351,7 @@ class TradingAPI:
|
|
|
331
351
|
APIError: If request fails or position not found.
|
|
332
352
|
CTraderConnectionTimeoutError: If request times out.
|
|
333
353
|
"""
|
|
354
|
+
logger.debug("Closing position: account=%d position=%d", account_id, request.position_id)
|
|
334
355
|
proto_request = request.to_proto(account_id)
|
|
335
356
|
|
|
336
357
|
response = await self._protocol.send_request(
|
|
@@ -368,6 +389,7 @@ class TradingAPI:
|
|
|
368
389
|
APIError: If request fails or position not found.
|
|
369
390
|
CTraderConnectionTimeoutError: If request times out.
|
|
370
391
|
"""
|
|
392
|
+
logger.debug("Amending position: account=%d position=%d", account_id, request.position_id)
|
|
371
393
|
proto_request = request.to_proto(account_id)
|
|
372
394
|
|
|
373
395
|
response = await self._protocol.send_request(
|
|
@@ -19,7 +19,6 @@ from .events import (
|
|
|
19
19
|
MarginCallTriggerEvent,
|
|
20
20
|
MarginChangeEvent,
|
|
21
21
|
OrderErrorEvent,
|
|
22
|
-
PnLChangeEvent,
|
|
23
22
|
ReadyEvent,
|
|
24
23
|
ReconnectedEvent,
|
|
25
24
|
SpotEvent,
|
|
@@ -50,7 +49,6 @@ T_AccountIdOnly = TypeVar(
|
|
|
50
49
|
SymbolChangedEvent,
|
|
51
50
|
TrailingStopChangedEvent,
|
|
52
51
|
MarginCallTriggerEvent,
|
|
53
|
-
PnLChangeEvent,
|
|
54
52
|
)
|
|
55
53
|
|
|
56
54
|
# Events that support no filters
|
{ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/connection/heartbeat.py
RENAMED
|
@@ -56,6 +56,7 @@ class HeartbeatManager:
|
|
|
56
56
|
self._task_group = anyio.create_task_group()
|
|
57
57
|
await self._task_group.__aenter__()
|
|
58
58
|
self._task_group.start_soon(self._heartbeat_loop)
|
|
59
|
+
logger.info("Heartbeat monitor started (interval=%.1fs, timeout=%.1fs)", self._interval, self._timeout)
|
|
59
60
|
|
|
60
61
|
async def stop(self) -> None:
|
|
61
62
|
"""Stop heartbeat monitoring.
|
|
@@ -66,6 +67,7 @@ class HeartbeatManager:
|
|
|
66
67
|
self._task_scope.cancel()
|
|
67
68
|
|
|
68
69
|
if self._task_group is not None:
|
|
70
|
+
logger.info("Heartbeat monitor stopped")
|
|
69
71
|
self._task_group.cancel_scope.cancel()
|
|
70
72
|
try:
|
|
71
73
|
await self._task_group.__aexit__(None, None, None)
|
|
@@ -85,6 +87,7 @@ class HeartbeatManager:
|
|
|
85
87
|
self._last_received = time.monotonic()
|
|
86
88
|
if self._task_group is not None:
|
|
87
89
|
self._task_group.start_soon(self._heartbeat_loop)
|
|
90
|
+
logger.info("Heartbeat monitor restarted")
|
|
88
91
|
|
|
89
92
|
async def _record_activity(self, _message: betterproto.Message) -> None:
|
|
90
93
|
"""Reset the inactivity timer on any received server message."""
|
{ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/connection/protocol.py
RENAMED
|
@@ -235,11 +235,16 @@ class Protocol:
|
|
|
235
235
|
proto_msg = deserialize_proto_message(raw)
|
|
236
236
|
inner = unwrap_message(proto_msg)
|
|
237
237
|
await self._dispatch_message(proto_msg, inner)
|
|
238
|
-
except
|
|
239
|
-
|
|
238
|
+
except FramingError as e:
|
|
239
|
+
logger.error("Protocol framing error (possible data corruption): %s", e)
|
|
240
240
|
if self._running:
|
|
241
241
|
await self.handle_disconnect()
|
|
242
242
|
break
|
|
243
|
+
except (anyio.ClosedResourceError, anyio.EndOfStream):
|
|
244
|
+
if self._running:
|
|
245
|
+
logger.debug("Connection closed by remote")
|
|
246
|
+
await self.handle_disconnect()
|
|
247
|
+
break
|
|
243
248
|
except anyio.get_cancelled_exc_class():
|
|
244
249
|
break
|
|
245
250
|
except Exception as e:
|
|
@@ -310,7 +315,7 @@ class Protocol:
|
|
|
310
315
|
|
|
311
316
|
async def handle_disconnect(self) -> None:
|
|
312
317
|
"""Handle unexpected disconnection and attempt reconnection."""
|
|
313
|
-
logger.
|
|
318
|
+
logger.warning("Connection lost, attempting to reconnect...")
|
|
314
319
|
|
|
315
320
|
# Cancel the reader loop first to prevent race conditions
|
|
316
321
|
if self._reader_scope is not None:
|
{ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/connection/transport.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import ssl
|
|
4
5
|
|
|
5
6
|
import anyio
|
|
@@ -8,6 +9,9 @@ from anyio.abc import ByteReceiveStream, ByteStream
|
|
|
8
9
|
from ..exceptions import CTraderConnectionClosedError, CTraderConnectionFailedError
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
11
15
|
class Transport:
|
|
12
16
|
"""Low-level TCP/SSL transport.
|
|
13
17
|
|
|
@@ -78,6 +82,11 @@ class Transport:
|
|
|
78
82
|
except OSError as e:
|
|
79
83
|
raise CTraderConnectionFailedError(self._host, self._port, e) from e
|
|
80
84
|
|
|
85
|
+
if self._ssl:
|
|
86
|
+
logger.info("Connected to %s:%d with SSL", self._host, self._port)
|
|
87
|
+
else:
|
|
88
|
+
logger.warning("Connected to %s:%d without SSL (plaintext)", self._host, self._port)
|
|
89
|
+
|
|
81
90
|
async def close(self) -> None:
|
|
82
91
|
"""Close the connection gracefully.
|
|
83
92
|
|
|
@@ -88,9 +97,14 @@ class Transport:
|
|
|
88
97
|
stream = self._stream
|
|
89
98
|
self._stream = None # Clear reference first to prevent re-entry
|
|
90
99
|
try:
|
|
91
|
-
|
|
100
|
+
# move_on_after guards against aclose() hanging when the network
|
|
101
|
+
# route has changed (e.g. VPN toggle) but the OS hasn't reset the
|
|
102
|
+
# TCP socket — graceful TLS shutdown would wait forever for an ACK.
|
|
103
|
+
with anyio.move_on_after(5) as close_scope:
|
|
104
|
+
await stream.aclose()
|
|
105
|
+
if close_scope.cancelled_caught:
|
|
106
|
+
logger.debug("Graceful TLS shutdown timed out, forcing close")
|
|
92
107
|
except (OSError, anyio.ClosedResourceError):
|
|
93
|
-
# Stream already closed or in bad state - ignore
|
|
94
108
|
pass
|
|
95
109
|
|
|
96
110
|
async def send(self, data: bytes) -> None:
|
{ctrader_api_client-0.2.3 → ctrader_api_client-0.3.0}/src/ctrader_api_client/events/__init__.py
RENAMED
|
@@ -31,7 +31,6 @@ from .types import (
|
|
|
31
31
|
MarginCallTriggerEvent,
|
|
32
32
|
MarginChangeEvent,
|
|
33
33
|
OrderErrorEvent,
|
|
34
|
-
PnLChangeEvent,
|
|
35
34
|
ReadyEvent,
|
|
36
35
|
ReconnectedEvent,
|
|
37
36
|
SpotEvent,
|
|
@@ -54,7 +53,6 @@ __all__ = [
|
|
|
54
53
|
"MarginCallTriggerEvent",
|
|
55
54
|
"MarginChangeEvent",
|
|
56
55
|
"OrderErrorEvent",
|
|
57
|
-
"PnLChangeEvent",
|
|
58
56
|
"ReadyEvent",
|
|
59
57
|
"ReconnectedEvent",
|
|
60
58
|
"SpotEvent",
|