ctrader-api-client 0.1.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.1.0/.github/workflows/docs.yml +18 -0
- ctrader_api_client-0.1.0/.gitignore +14 -0
- ctrader_api_client-0.1.0/.pre-commit-config.yaml +19 -0
- ctrader_api_client-0.1.0/.python-version +1 -0
- ctrader_api_client-0.1.0/Justfile +51 -0
- ctrader_api_client-0.1.0/LICENSE +21 -0
- ctrader_api_client-0.1.0/PKG-INFO +252 -0
- ctrader_api_client-0.1.0/README.md +233 -0
- ctrader_api_client-0.1.0/docs/api/accounts.md +44 -0
- ctrader_api_client-0.1.0/docs/api/client.md +52 -0
- ctrader_api_client-0.1.0/docs/api/enums.md +102 -0
- ctrader_api_client-0.1.0/docs/api/events.md +143 -0
- ctrader_api_client-0.1.0/docs/api/market-data.md +117 -0
- ctrader_api_client-0.1.0/docs/api/models.md +161 -0
- ctrader_api_client-0.1.0/docs/api/symbols.md +91 -0
- ctrader_api_client-0.1.0/docs/api/trading.md +123 -0
- ctrader_api_client-0.1.0/docs/getting-started.md +221 -0
- ctrader_api_client-0.1.0/docs/index.md +77 -0
- ctrader_api_client-0.1.0/mkdocs.yml +67 -0
- ctrader_api_client-0.1.0/protos/SOURCE +1 -0
- ctrader_api_client-0.1.0/protos/VERSION +1 -0
- ctrader_api_client-0.1.0/protos/update.sh +31 -0
- ctrader_api_client-0.1.0/protos/vendor/.gitkeep +0 -0
- ctrader_api_client-0.1.0/protos/vendor/OpenApiCommonMessages.proto +31 -0
- ctrader_api_client-0.1.0/protos/vendor/OpenApiCommonModelMessages.proto +33 -0
- ctrader_api_client-0.1.0/protos/vendor/OpenApiMessages.proto +828 -0
- ctrader_api_client-0.1.0/protos/vendor/OpenApiModelMessages.proto +729 -0
- ctrader_api_client-0.1.0/pyproject.toml +75 -0
- ctrader_api_client-0.1.0/scripts/fix_proto_imports.py +147 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/__init__.py +64 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/_internal/__init__.py +26 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/_internal/messages.py +348 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/_internal/proto/OpenApiCommonMessages.py +42 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/_internal/proto/OpenApiCommonModelMessages.py +30 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/_internal/proto/OpenApiMessages.py +1112 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/_internal/proto/OpenApiModelMessages.py +802 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/_internal/proto/__init__.py +320 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/_internal/serialization.py +84 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/api/__init__.py +21 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/api/accounts.py +71 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/api/market_data.py +424 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/api/symbols.py +171 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/api/trading.py +506 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/auth/__init__.py +14 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/auth/credentials.py +72 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/auth/manager.py +511 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/client.py +475 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/config.py +56 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/connection/__init__.py +16 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/connection/heartbeat.py +120 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/connection/protocol.py +366 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/connection/transport.py +123 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/enums.py +138 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/events/__init__.py +65 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/events/emitter.py +254 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/events/router.py +400 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/events/types.py +340 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/exceptions.py +231 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/models/__init__.py +50 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/models/_base.py +19 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/models/account.py +177 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/models/deal.py +242 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/models/market_data.py +192 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/models/order.py +262 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/models/position.py +209 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/models/requests.py +299 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/models/symbol.py +194 -0
- ctrader_api_client-0.1.0/src/ctrader_api_client/py.typed +0 -0
- ctrader_api_client-0.1.0/tests/unit/_internal/test_messages.py +270 -0
- ctrader_api_client-0.1.0/tests/unit/_internal/test_serialization.py +172 -0
- ctrader_api_client-0.1.0/tests/unit/api/conftest.py +13 -0
- ctrader_api_client-0.1.0/tests/unit/api/test_accounts.py +117 -0
- ctrader_api_client-0.1.0/tests/unit/api/test_market_data_api.py +219 -0
- ctrader_api_client-0.1.0/tests/unit/api/test_symbols.py +281 -0
- ctrader_api_client-0.1.0/tests/unit/api/test_trading.py +350 -0
- ctrader_api_client-0.1.0/tests/unit/auth/test_credentials.py +177 -0
- ctrader_api_client-0.1.0/tests/unit/auth/test_manager.py +775 -0
- ctrader_api_client-0.1.0/tests/unit/connection/test_heartbeat.py +193 -0
- ctrader_api_client-0.1.0/tests/unit/connection/test_protocol.py +396 -0
- ctrader_api_client-0.1.0/tests/unit/connection/test_transport.py +244 -0
- ctrader_api_client-0.1.0/tests/unit/events/test_emitter.py +343 -0
- ctrader_api_client-0.1.0/tests/unit/events/test_router.py +608 -0
- ctrader_api_client-0.1.0/tests/unit/events/test_types.py +335 -0
- ctrader_api_client-0.1.0/tests/unit/models/test_account.py +243 -0
- ctrader_api_client-0.1.0/tests/unit/models/test_deal.py +381 -0
- ctrader_api_client-0.1.0/tests/unit/models/test_market_data.py +265 -0
- ctrader_api_client-0.1.0/tests/unit/models/test_order.py +438 -0
- ctrader_api_client-0.1.0/tests/unit/models/test_position.py +334 -0
- ctrader_api_client-0.1.0/tests/unit/models/test_requests.py +378 -0
- ctrader_api_client-0.1.0/tests/unit/models/test_symbol.py +263 -0
- ctrader_api_client-0.1.0/tests/unit/test_client.py +491 -0
- ctrader_api_client-0.1.0/tests/unit/test_config.py +149 -0
- ctrader_api_client-0.1.0/uv.lock +1067 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Deploy Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
deploy:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
- run: uv sync --group dev
|
|
18
|
+
- run: uv run mkdocs gh-deploy --force
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
# Ruff for linting & formatting
|
|
3
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
4
|
+
rev: v0.12.9
|
|
5
|
+
hooks:
|
|
6
|
+
- id: ruff-check
|
|
7
|
+
args: [--fix]
|
|
8
|
+
language_version: python3
|
|
9
|
+
- id: ruff-format
|
|
10
|
+
language_version: python3
|
|
11
|
+
|
|
12
|
+
# Basic pre-commit housekeeping hooks
|
|
13
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
14
|
+
rev: v6.0.0
|
|
15
|
+
hooks:
|
|
16
|
+
- id: end-of-file-fixer
|
|
17
|
+
- id: trailing-whitespace
|
|
18
|
+
- id: check-added-large-files
|
|
19
|
+
- id: check-shebang-scripts-are-executable
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env just --justfile
|
|
2
|
+
|
|
3
|
+
SEPARATOR := `printf '%0.s-' {1..60}`
|
|
4
|
+
PROTO_PATH := "protos/vendor"
|
|
5
|
+
PYTHON_OUT_PATH := "src/ctrader_api_client/_internal/proto"
|
|
6
|
+
|
|
7
|
+
default: help
|
|
8
|
+
|
|
9
|
+
# Print this help message
|
|
10
|
+
help:
|
|
11
|
+
just --list
|
|
12
|
+
|
|
13
|
+
# Run all CI steps: linting, formatting, type checking
|
|
14
|
+
ci directory='':
|
|
15
|
+
@just lint {{directory}}
|
|
16
|
+
@just fmt {{directory}}
|
|
17
|
+
@just type-check {{directory}}
|
|
18
|
+
|
|
19
|
+
# Lint the codebase using ruff, optionally specifying a directory to lint.
|
|
20
|
+
lint directory='':
|
|
21
|
+
uv run ruff check --fix {{directory}}
|
|
22
|
+
|
|
23
|
+
# Format the codebase using ruff, optionally specifying a directory to format.
|
|
24
|
+
fmt directory='':
|
|
25
|
+
uv run ruff format {{directory}}
|
|
26
|
+
|
|
27
|
+
# Run type checking using ty, optionally specifying a directory to check.
|
|
28
|
+
type-check directory='':
|
|
29
|
+
uv run ty check {{directory}}
|
|
30
|
+
|
|
31
|
+
# Run tests using pytest, optionally specifying a directory to test.
|
|
32
|
+
test directory='':
|
|
33
|
+
uv run pytest {{directory}}
|
|
34
|
+
|
|
35
|
+
# Update .proto files to a specific version. Defaults to 'main' if no version is provided.
|
|
36
|
+
update-proto version='':
|
|
37
|
+
./protos/update.sh {{version}}
|
|
38
|
+
|
|
39
|
+
# Generate Python code from .proto files. This should be run after updating the .proto files.
|
|
40
|
+
@compile-proto:
|
|
41
|
+
echo {{SEPARATOR}}
|
|
42
|
+
echo "Compiling .proto files from {{PROTO_PATH}}/ to Python code under {{PYTHON_OUT_PATH}}/"
|
|
43
|
+
uv run protoc -I={{PROTO_PATH}} \
|
|
44
|
+
--python_betterproto_out={{PYTHON_OUT_PATH}} \
|
|
45
|
+
{{PROTO_PATH}}/*.proto
|
|
46
|
+
echo {{SEPARATOR}}
|
|
47
|
+
echo "Fixing cross-module imports..."
|
|
48
|
+
uv run python scripts/fix_proto_imports.py
|
|
49
|
+
echo "Formatting generated code with ruff..."
|
|
50
|
+
@uv run ruff check {{PYTHON_OUT_PATH}} --fix
|
|
51
|
+
@uv run ruff format {{PYTHON_OUT_PATH}}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Elio Anthony Chukri
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctrader-api-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: API Client to interact with the cTrader Open API spec
|
|
5
|
+
Author-email: Elio <elioachukri@pm.me>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Natural Language :: English
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Requires-Dist: anyio>=4.13.0
|
|
15
|
+
Requires-Dist: betterproto[compiler]>=1.2.5
|
|
16
|
+
Requires-Dist: pydantic>=2.12.5
|
|
17
|
+
Requires-Dist: tenacity>=9.1.4
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# cTrader API Client
|
|
21
|
+
|
|
22
|
+
A Python client for the cTrader Open API. Provides a high-level async interface for trading operations, market data subscriptions, and account management.
|
|
23
|
+
|
|
24
|
+
Documentation:
|
|
25
|
+
- [Library Docs](https://elioachukri.github.io/ctrader-api-client/)
|
|
26
|
+
- [cTrader Open API Docs](https://help.ctrader.com/open-api/)
|
|
27
|
+
|
|
28
|
+
> Note that this library is in early development. The API may change, and some features may be incomplete. Contributions and feedback are welcome!
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Python 3.12+
|
|
33
|
+
- An activated cTrader Open API application with client ID and secret
|
|
34
|
+
- OAuth tokens for account authentication (see below)
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
**Using uv (recommended):**
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv add ctrader-api-client
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Using pip:**
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install ctrader-api-client
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import asyncio
|
|
54
|
+
from ctrader_api_client import CTraderClient, ClientConfig
|
|
55
|
+
from ctrader_api_client.events import ReadyEvent, SpotEvent
|
|
56
|
+
|
|
57
|
+
config = ClientConfig(
|
|
58
|
+
client_id="your_client_id",
|
|
59
|
+
client_secret="your_client_secret",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
client = CTraderClient(config)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@client.on(SpotEvent, symbol_id=270) # US500.cash
|
|
66
|
+
async def on_price(event: SpotEvent):
|
|
67
|
+
print(f"Price update: {event.bid}/{event.ask}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@client.on(ReadyEvent)
|
|
71
|
+
async def on_ready(event: ReadyEvent):
|
|
72
|
+
"""Called when account is authenticated and ready."""
|
|
73
|
+
await client.market_data.subscribe_spots(event.account_id, [270])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def main():
|
|
77
|
+
async with client:
|
|
78
|
+
await client.auth.authenticate_app()
|
|
79
|
+
await client.auth.authenticate_by_trader_login(
|
|
80
|
+
trader_login=12345678,
|
|
81
|
+
access_token="your_access_token",
|
|
82
|
+
refresh_token="your_refresh_token",
|
|
83
|
+
expires_at=1778617423,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Keep running to receive events
|
|
87
|
+
await asyncio.Event().wait()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
asyncio.run(main())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## OAuth Token Generation
|
|
95
|
+
|
|
96
|
+
This library requires OAuth tokens from cTrader. For simple use cases, you can use [ctrader-oauth-fetcher](https://github.com/ElioaChukri/ctrader-oauth-fetcher) to generate tokens:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
uvx ctrader-oauth-fetcher --client-id [ID] --client-secret [SECRET]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This opens a browser for authorization and returns your access token, refresh token, and expiry time.
|
|
103
|
+
|
|
104
|
+
For production applications, implement the OAuth flow according to the [cTrader Open API documentation](https://help.ctrader.com/open-api/).
|
|
105
|
+
|
|
106
|
+
## Features
|
|
107
|
+
|
|
108
|
+
### Authentication
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Authenticate the application
|
|
112
|
+
await client.auth.authenticate_app()
|
|
113
|
+
|
|
114
|
+
# Authenticate a trading account
|
|
115
|
+
creds = await client.auth.authenticate_by_trader_login(
|
|
116
|
+
trader_login=12345678,
|
|
117
|
+
access_token="...",
|
|
118
|
+
refresh_token="...",
|
|
119
|
+
expires_at=1778617423,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Tokens are automatically refreshed before expiry
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Market Data
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
# Subscribe to spot prices
|
|
129
|
+
await client.market_data.subscribe_spots(account_id, [symbol_id])
|
|
130
|
+
|
|
131
|
+
# Subscribe to candles
|
|
132
|
+
await client.market_data.subscribe_trendbars(account_id, symbol_id, TrendbarPeriod.M1)
|
|
133
|
+
|
|
134
|
+
# Get historical data
|
|
135
|
+
bars = await client.market_data.get_trendbars(
|
|
136
|
+
account_id, symbol_id, TrendbarPeriod.H1, from_ts, to_ts
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Trading
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from ctrader_api_client.models import NewOrderRequest, ClosePositionRequest
|
|
144
|
+
from ctrader_api_client.enums import OrderType, OrderSide
|
|
145
|
+
|
|
146
|
+
# Place a market order
|
|
147
|
+
request = NewOrderRequest(
|
|
148
|
+
symbol_id=symbol_id,
|
|
149
|
+
order_type=OrderType.MARKET,
|
|
150
|
+
side=OrderSide.BUY,
|
|
151
|
+
volume=100000, # 1 lot in cents
|
|
152
|
+
)
|
|
153
|
+
result = await client.trading.create_order(account_id, request)
|
|
154
|
+
|
|
155
|
+
# Get open positions
|
|
156
|
+
positions = await client.trading.get_positions(account_id)
|
|
157
|
+
|
|
158
|
+
# Close a position
|
|
159
|
+
close_position = ClosePositionRequest(
|
|
160
|
+
position_id=position_id,
|
|
161
|
+
volume=100000, # Close full volume
|
|
162
|
+
)
|
|
163
|
+
await client.trading.close_position(account_id, close_position)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Event Handling
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from ctrader_api_client.events import (
|
|
170
|
+
SpotEvent,
|
|
171
|
+
ExecutionEvent,
|
|
172
|
+
ReadyEvent,
|
|
173
|
+
ReconnectedEvent,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Price updates
|
|
177
|
+
@client.on(SpotEvent, symbol_id=270)
|
|
178
|
+
async def on_spot(event: SpotEvent):
|
|
179
|
+
print(f"{event.bid}/{event.ask}")
|
|
180
|
+
|
|
181
|
+
# Order executions
|
|
182
|
+
@client.on(ExecutionEvent, account_id=account_id)
|
|
183
|
+
async def on_execution(event: ExecutionEvent):
|
|
184
|
+
print(f"Order {event.order_id}: {event.execution_type}")
|
|
185
|
+
|
|
186
|
+
# Account ready (fires on initial auth and after reconnection)
|
|
187
|
+
@client.on(ReadyEvent)
|
|
188
|
+
async def on_ready(event: ReadyEvent):
|
|
189
|
+
# Set up subscriptions here
|
|
190
|
+
await client.market_data.subscribe_spots(event.account_id, symbols)
|
|
191
|
+
|
|
192
|
+
# Connection restored
|
|
193
|
+
@client.on(ReconnectedEvent)
|
|
194
|
+
async def on_reconnected(event: ReconnectedEvent):
|
|
195
|
+
print(f"Reconnected, restored accounts: {event.restored_accounts}")
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Symbols
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
# List all symbols
|
|
202
|
+
symbols = await client.symbols.list_all(account_id)
|
|
203
|
+
|
|
204
|
+
# Search by name
|
|
205
|
+
results = await client.symbols.search(account_id, "EUR")
|
|
206
|
+
|
|
207
|
+
# Get specific symbol
|
|
208
|
+
symbol = await client.symbols.get_by_id(account_id, symbol_id)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Account Information
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
# Get account details
|
|
215
|
+
account = await client.accounts.get_trader(account_id)
|
|
216
|
+
print(f"Balance: {account.balance}")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Automatic Reconnection
|
|
220
|
+
|
|
221
|
+
The client automatically handles connection drops:
|
|
222
|
+
|
|
223
|
+
1. Reconnects with exponential backoff
|
|
224
|
+
2. Re-authenticates the app and all accounts
|
|
225
|
+
3. Emits `ReadyEvent` for each restored account (for resubscribing to market data)
|
|
226
|
+
4. Emits `ReconnectedEvent` with summary of restored/failed accounts
|
|
227
|
+
|
|
228
|
+
Use `ReadyEvent` to set up subscriptions that persist across reconnections.
|
|
229
|
+
|
|
230
|
+
## Configuration
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
config = ClientConfig(
|
|
234
|
+
client_id="your_client_id",
|
|
235
|
+
client_secret="your_client_secret",
|
|
236
|
+
|
|
237
|
+
# Connection settings
|
|
238
|
+
host="live.ctraderapi.com", # or "demo.ctraderapi.com"
|
|
239
|
+
port=5035,
|
|
240
|
+
use_ssl=True,
|
|
241
|
+
|
|
242
|
+
# Timeouts
|
|
243
|
+
heartbeat_interval=10.0,
|
|
244
|
+
heartbeat_timeout=30.0, # Or 0 to disable server heartbeat checks
|
|
245
|
+
request_timeout=30.0,
|
|
246
|
+
|
|
247
|
+
# Reconnection
|
|
248
|
+
reconnect_attempts=5,
|
|
249
|
+
reconnect_min_wait=1.0,
|
|
250
|
+
reconnect_max_wait=60.0,
|
|
251
|
+
)
|
|
252
|
+
```
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# cTrader API Client
|
|
2
|
+
|
|
3
|
+
A Python client for the cTrader Open API. Provides a high-level async interface for trading operations, market data subscriptions, and account management.
|
|
4
|
+
|
|
5
|
+
Documentation:
|
|
6
|
+
- [Library Docs](https://elioachukri.github.io/ctrader-api-client/)
|
|
7
|
+
- [cTrader Open API Docs](https://help.ctrader.com/open-api/)
|
|
8
|
+
|
|
9
|
+
> Note that this library is in early development. The API may change, and some features may be incomplete. Contributions and feedback are welcome!
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Python 3.12+
|
|
14
|
+
- An activated cTrader Open API application with client ID and secret
|
|
15
|
+
- OAuth tokens for account authentication (see below)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
**Using uv (recommended):**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uv add ctrader-api-client
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Using pip:**
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install ctrader-api-client
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import asyncio
|
|
35
|
+
from ctrader_api_client import CTraderClient, ClientConfig
|
|
36
|
+
from ctrader_api_client.events import ReadyEvent, SpotEvent
|
|
37
|
+
|
|
38
|
+
config = ClientConfig(
|
|
39
|
+
client_id="your_client_id",
|
|
40
|
+
client_secret="your_client_secret",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
client = CTraderClient(config)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@client.on(SpotEvent, symbol_id=270) # US500.cash
|
|
47
|
+
async def on_price(event: SpotEvent):
|
|
48
|
+
print(f"Price update: {event.bid}/{event.ask}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@client.on(ReadyEvent)
|
|
52
|
+
async def on_ready(event: ReadyEvent):
|
|
53
|
+
"""Called when account is authenticated and ready."""
|
|
54
|
+
await client.market_data.subscribe_spots(event.account_id, [270])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def main():
|
|
58
|
+
async with client:
|
|
59
|
+
await client.auth.authenticate_app()
|
|
60
|
+
await client.auth.authenticate_by_trader_login(
|
|
61
|
+
trader_login=12345678,
|
|
62
|
+
access_token="your_access_token",
|
|
63
|
+
refresh_token="your_refresh_token",
|
|
64
|
+
expires_at=1778617423,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Keep running to receive events
|
|
68
|
+
await asyncio.Event().wait()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
asyncio.run(main())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## OAuth Token Generation
|
|
76
|
+
|
|
77
|
+
This library requires OAuth tokens from cTrader. For simple use cases, you can use [ctrader-oauth-fetcher](https://github.com/ElioaChukri/ctrader-oauth-fetcher) to generate tokens:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uvx ctrader-oauth-fetcher --client-id [ID] --client-secret [SECRET]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This opens a browser for authorization and returns your access token, refresh token, and expiry time.
|
|
84
|
+
|
|
85
|
+
For production applications, implement the OAuth flow according to the [cTrader Open API documentation](https://help.ctrader.com/open-api/).
|
|
86
|
+
|
|
87
|
+
## Features
|
|
88
|
+
|
|
89
|
+
### Authentication
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# Authenticate the application
|
|
93
|
+
await client.auth.authenticate_app()
|
|
94
|
+
|
|
95
|
+
# Authenticate a trading account
|
|
96
|
+
creds = await client.auth.authenticate_by_trader_login(
|
|
97
|
+
trader_login=12345678,
|
|
98
|
+
access_token="...",
|
|
99
|
+
refresh_token="...",
|
|
100
|
+
expires_at=1778617423,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Tokens are automatically refreshed before expiry
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Market Data
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# Subscribe to spot prices
|
|
110
|
+
await client.market_data.subscribe_spots(account_id, [symbol_id])
|
|
111
|
+
|
|
112
|
+
# Subscribe to candles
|
|
113
|
+
await client.market_data.subscribe_trendbars(account_id, symbol_id, TrendbarPeriod.M1)
|
|
114
|
+
|
|
115
|
+
# Get historical data
|
|
116
|
+
bars = await client.market_data.get_trendbars(
|
|
117
|
+
account_id, symbol_id, TrendbarPeriod.H1, from_ts, to_ts
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Trading
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from ctrader_api_client.models import NewOrderRequest, ClosePositionRequest
|
|
125
|
+
from ctrader_api_client.enums import OrderType, OrderSide
|
|
126
|
+
|
|
127
|
+
# Place a market order
|
|
128
|
+
request = NewOrderRequest(
|
|
129
|
+
symbol_id=symbol_id,
|
|
130
|
+
order_type=OrderType.MARKET,
|
|
131
|
+
side=OrderSide.BUY,
|
|
132
|
+
volume=100000, # 1 lot in cents
|
|
133
|
+
)
|
|
134
|
+
result = await client.trading.create_order(account_id, request)
|
|
135
|
+
|
|
136
|
+
# Get open positions
|
|
137
|
+
positions = await client.trading.get_positions(account_id)
|
|
138
|
+
|
|
139
|
+
# Close a position
|
|
140
|
+
close_position = ClosePositionRequest(
|
|
141
|
+
position_id=position_id,
|
|
142
|
+
volume=100000, # Close full volume
|
|
143
|
+
)
|
|
144
|
+
await client.trading.close_position(account_id, close_position)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Event Handling
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from ctrader_api_client.events import (
|
|
151
|
+
SpotEvent,
|
|
152
|
+
ExecutionEvent,
|
|
153
|
+
ReadyEvent,
|
|
154
|
+
ReconnectedEvent,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Price updates
|
|
158
|
+
@client.on(SpotEvent, symbol_id=270)
|
|
159
|
+
async def on_spot(event: SpotEvent):
|
|
160
|
+
print(f"{event.bid}/{event.ask}")
|
|
161
|
+
|
|
162
|
+
# Order executions
|
|
163
|
+
@client.on(ExecutionEvent, account_id=account_id)
|
|
164
|
+
async def on_execution(event: ExecutionEvent):
|
|
165
|
+
print(f"Order {event.order_id}: {event.execution_type}")
|
|
166
|
+
|
|
167
|
+
# Account ready (fires on initial auth and after reconnection)
|
|
168
|
+
@client.on(ReadyEvent)
|
|
169
|
+
async def on_ready(event: ReadyEvent):
|
|
170
|
+
# Set up subscriptions here
|
|
171
|
+
await client.market_data.subscribe_spots(event.account_id, symbols)
|
|
172
|
+
|
|
173
|
+
# Connection restored
|
|
174
|
+
@client.on(ReconnectedEvent)
|
|
175
|
+
async def on_reconnected(event: ReconnectedEvent):
|
|
176
|
+
print(f"Reconnected, restored accounts: {event.restored_accounts}")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Symbols
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
# List all symbols
|
|
183
|
+
symbols = await client.symbols.list_all(account_id)
|
|
184
|
+
|
|
185
|
+
# Search by name
|
|
186
|
+
results = await client.symbols.search(account_id, "EUR")
|
|
187
|
+
|
|
188
|
+
# Get specific symbol
|
|
189
|
+
symbol = await client.symbols.get_by_id(account_id, symbol_id)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Account Information
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
# Get account details
|
|
196
|
+
account = await client.accounts.get_trader(account_id)
|
|
197
|
+
print(f"Balance: {account.balance}")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Automatic Reconnection
|
|
201
|
+
|
|
202
|
+
The client automatically handles connection drops:
|
|
203
|
+
|
|
204
|
+
1. Reconnects with exponential backoff
|
|
205
|
+
2. Re-authenticates the app and all accounts
|
|
206
|
+
3. Emits `ReadyEvent` for each restored account (for resubscribing to market data)
|
|
207
|
+
4. Emits `ReconnectedEvent` with summary of restored/failed accounts
|
|
208
|
+
|
|
209
|
+
Use `ReadyEvent` to set up subscriptions that persist across reconnections.
|
|
210
|
+
|
|
211
|
+
## Configuration
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
config = ClientConfig(
|
|
215
|
+
client_id="your_client_id",
|
|
216
|
+
client_secret="your_client_secret",
|
|
217
|
+
|
|
218
|
+
# Connection settings
|
|
219
|
+
host="live.ctraderapi.com", # or "demo.ctraderapi.com"
|
|
220
|
+
port=5035,
|
|
221
|
+
use_ssl=True,
|
|
222
|
+
|
|
223
|
+
# Timeouts
|
|
224
|
+
heartbeat_interval=10.0,
|
|
225
|
+
heartbeat_timeout=30.0, # Or 0 to disable server heartbeat checks
|
|
226
|
+
request_timeout=30.0,
|
|
227
|
+
|
|
228
|
+
# Reconnection
|
|
229
|
+
reconnect_attempts=5,
|
|
230
|
+
reconnect_min_wait=1.0,
|
|
231
|
+
reconnect_max_wait=60.0,
|
|
232
|
+
)
|
|
233
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Accounts API
|
|
2
|
+
|
|
3
|
+
Account information retrieval operations.
|
|
4
|
+
|
|
5
|
+
Access via `client.accounts`.
|
|
6
|
+
|
|
7
|
+
## AccountsAPI
|
|
8
|
+
|
|
9
|
+
::: ctrader_api_client.api.AccountsAPI
|
|
10
|
+
options:
|
|
11
|
+
show_source: false
|
|
12
|
+
members:
|
|
13
|
+
- get_trader
|
|
14
|
+
|
|
15
|
+
## Usage Examples
|
|
16
|
+
|
|
17
|
+
### Get Account Details
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
account = await client.accounts.get_trader(account_id)
|
|
21
|
+
|
|
22
|
+
print(f"Balance: {account.get_balance()}")
|
|
23
|
+
print(f"Leverage: {account.get_leverage()}")
|
|
24
|
+
print(f"Account type: {account.account_type}")
|
|
25
|
+
print(f"Broker name: {account.broker_name}")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Account Discovery
|
|
29
|
+
|
|
30
|
+
To discover available accounts for an access token, use the auth manager:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
# Get all accounts associated with a token
|
|
34
|
+
accounts = await client.auth.get_accounts(access_token)
|
|
35
|
+
|
|
36
|
+
for acc in accounts:
|
|
37
|
+
print(f"Login: {acc.trader_login}, Account ID: {acc.account_id}")
|
|
38
|
+
print(f" Live: {acc.is_live}, Broker: {acc.broker_name}")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Related
|
|
42
|
+
|
|
43
|
+
- [Authentication](client.md#authentication) - Authenticating accounts
|
|
44
|
+
- [Models - Account](models.md#account) - Account model reference
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Client
|
|
2
|
+
|
|
3
|
+
The main entry point for interacting with the cTrader API.
|
|
4
|
+
|
|
5
|
+
## CTraderClient
|
|
6
|
+
|
|
7
|
+
::: ctrader_api_client.CTraderClient
|
|
8
|
+
options:
|
|
9
|
+
show_source: false
|
|
10
|
+
members:
|
|
11
|
+
- __init__
|
|
12
|
+
- connect
|
|
13
|
+
- close
|
|
14
|
+
- "on"
|
|
15
|
+
- "off"
|
|
16
|
+
- auth
|
|
17
|
+
- accounts
|
|
18
|
+
- symbols
|
|
19
|
+
- trading
|
|
20
|
+
- market_data
|
|
21
|
+
- is_connected
|
|
22
|
+
- protocol
|
|
23
|
+
|
|
24
|
+
## ClientConfig
|
|
25
|
+
|
|
26
|
+
::: ctrader_api_client.ClientConfig
|
|
27
|
+
options:
|
|
28
|
+
show_source: false
|
|
29
|
+
|
|
30
|
+
## Authentication
|
|
31
|
+
|
|
32
|
+
The `client.auth` property provides access to authentication operations.
|
|
33
|
+
|
|
34
|
+
::: ctrader_api_client.auth.AuthManager
|
|
35
|
+
options:
|
|
36
|
+
show_source: false
|
|
37
|
+
members:
|
|
38
|
+
- authenticate_app
|
|
39
|
+
- authenticate_account
|
|
40
|
+
- authenticate_by_trader_login
|
|
41
|
+
- get_accounts
|
|
42
|
+
- resolve_account_id
|
|
43
|
+
- get_credentials
|
|
44
|
+
- remove_account
|
|
45
|
+
- is_app_authenticated
|
|
46
|
+
- authenticated_accounts
|
|
47
|
+
|
|
48
|
+
## AccountCredentials
|
|
49
|
+
|
|
50
|
+
::: ctrader_api_client.auth.AccountCredentials
|
|
51
|
+
options:
|
|
52
|
+
show_source: false
|