-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdxtrade_api.py
231 lines (207 loc) · 9.12 KB
/
dxtrade_api.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
'''
Implement DxTrade API
If using a different broker than FTMO you must modify self.base_url below
You can run this module standalone to execute the test routines in main()
but you must first enter your demo account credentials in line:
ftmo_conn = DXT("accountnumber", "password")
'''
import requests
import json
import uuid
import time # for time.sleep() used only for testing ( main() )
import logging
class DXT:
def __init__(self, username, password):
self.token = None
self.account_id = None
self.username = username
self.password = password
self.server = "ftmo"
self.cookies = {}
self.s = requests.Session()
self.base_url = "https://dxtrade.ftmo.com/dxsca-web/"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[ logging.FileHandler("TradeCopier-O2D.log"), logging.StreamHandler() ]
)
def login(self):
url = self.base_url + "login"
payload = json.dumps({
"username": self.username,
"domain": "default",
"password": self.password
})
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = self.s.request("POST", url, headers=headers, data=payload)
if response.status_code == 200:
self.token = response.json()["sessionToken"]
return True
else:
logging.critical("Login failed with status code: %s", response.status_code)
return False;
def ping(self):
url = self.base_url + "ping"
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'DXAPI ' + self.token
}
response = self.s.request("POST", url, headers=headers)
if response.status_code == 200:
#logging.info("Ping successful")
return True
else:
logging.error("Ping failed with status code: %s", response.status_code)
return False
def account_balance(self):
url = self.base_url + "accounts/default:" + self.username + "/metrics?include-positions=false"
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'DXAPI ' + self.token
}
response = self.s.request("GET", url, headers=headers)
if response.status_code == 200:
# returns a dict named 'metrics' containing a list of one item, a dict
return response.json()['metrics'][0]['balance']
else:
logging.error("account balance query failed with status code: %s", response.status_code)
return None
def get_quote(self, currpair):
url = self.base_url + "marketdata"
print(f"URL:{url}")
payload = json.dumps({
"account": "default:" + self.username,
"symbols": [ currpair.replace("_","") ],
"eventTypes": [ {
"type": "Quote",
"format": "COMPACT"
} ]
})
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'DXAPI ' + self.token
}
response = self.s.request("POST", url, headers=headers, data=payload)
if response.status_code == 200:
# returns a dict named 'metrics' containing a list of one item, a dict
# return bid and ask prices
return response.json()['events'][0]['bid'], response.json()['events'][0]['ask']
else:
logging.error("get-quote query failed with status code: %s", response.status_code)
return None
'''
placeOrder is called to open a new order or close an existing order
when opening:
positionCode is not given, it is generated by this function and returned
when closing:
positionCode of the original order is required, buy/sell must be reversed
'''
def placeOrder(self, currpair, lotsize, buy_or_sell, open_or_close, positionCode):
url = self.base_url + "accounts/default:" + self.username + "/orders"
orderCode = str(uuid.uuid4()).replace("-","")
payload = json.dumps({
"account": "default:" + self.username,
"orderCode": orderCode,
"positionCode": positionCode,
"type": "MARKET",
"instrument": currpair.replace("_",""),
"quantity": lotsize,
"positionEffect": "OPEN" if open_or_close else "CLOSE",
"side": "BUY" if buy_or_sell == 1 else "SELL",
"tif": "GTC"
})
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'DXAPI ' + self.token
}
response = self.s.request("POST", url, headers=headers, data=payload)
if response.status_code == 200:
# clean this up
positionCode = response.json()["orderId"]
logging.info("Order successful, order code: %s, position code: %s", orderCode, positionCode)
return orderCode, str(positionCode)
else:
logging.error("Order failed with status code: %s", response.status_code)
return None, None
def openOrder(self, currpair, lotsize, buy_or_sell):
orderCode, positionCode = self.placeOrder(currpair=currpair, lotsize=lotsize, buy_or_sell=buy_or_sell, open_or_close=1, positionCode="")
return positionCode
def closeOrder(self, currpair, lotsize, buy_or_sell, positionCode):
# note buy_or_sell is inverted
orderCode, positionCode = self.placeOrder(currpair=currpair, lotsize=lotsize, buy_or_sell=not buy_or_sell, open_or_close=0, positionCode=positionCode)
return False if orderCode is None else True
def get_positions(self):
url = self.base_url + "accounts/default:" + self.username + "/positions"
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'DXAPI ' + self.token
}
response = self.s.request("GET", url, headers=headers)
if response.status_code == 200:
# returns a dict named 'positions' containing a list of positions, each position is a dict
order_list = response.json()['positions']
# create a simplified list-of-dicts to return the results
positions = []
for order_item in order_list:
positions.append({
"symbol": order_item["symbol"],
"positionCode": order_item["positionCode"],
"side": 1 if order_item["side"] == "BUY" else 0,
"quantity": order_item["quantity"]
})
return positions
else:
logging.error("query failed with status code: %s", response.status_code)
return None
def close_all(self):
for position in self.get_positions():
logging.info("close_all closing order for symbol %s position %s side %s", position['symbol'], position['positionCode'], position['side'])
self.closeOrder(
currpair=position['symbol'],
lotsize=position['quantity'],
buy_or_sell=position['side'],
positionCode=position['positionCode']
)
if __name__ == "__main__":
ftmo_conn = DXT("accountnumber", "password")
ftmo_conn.login()
ftmo_conn.ping()
print(f"FTMO balance:{ftmo_conn.account_balance()}")
# get a quote
bid,ask = ftmo_conn.get_quote("EURUSD")
print(f"Quote for EURUSD - Bid:{bid}, Ask:{ask}")
# create these 3 orders
positions = {}
for order_param in [ ("EURCAD", 5000, 0), ("EURUSD", 6000, 1), ("EURGBP", 7000, 1) ]:
symbol, lotsize, side = order_param
positionCode = ftmo_conn.openOrder(symbol, lotsize, side)
if positionCode is not None:
positions[symbol] = (lotsize, side, positionCode)
time.sleep(10)
curr_positions = ftmo_conn.get_positions()
print(f"After opening orders: {len(curr_positions)} open positions:")
for list_item in curr_positions:
print(list_item)
# close 1 order
if "EURCAD" in positions:
(lotsize, side, positionCode) = positions["EURCAD"]
ftmo_conn.closeOrder("EURCAD", lotsize, side, positionCode)
time.sleep(10)
curr_positions = ftmo_conn.get_positions()
print(f"After closing 1 order: {len(curr_positions)} open positions:")
for list_item in curr_positions:
print(list_item)
ftmo_conn.close_all()
time.sleep(10)
curr_positions = ftmo_conn.get_positions()
print(f"After closing all orders: {len(curr_positions)} open positions:")
for list_item in curr_positions:
print(list_item)