Skip to content

Commit bb024b4

Browse files
Updated and fixed for forecaster
1 parent d07e05d commit bb024b4

File tree

1 file changed

+134
-51
lines changed

1 file changed

+134
-51
lines changed

XTBApi/api.py

Lines changed: 134 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
"""
99

1010
import enum
11+
import inspect
1112
import json
13+
import logging
1214
import time
15+
from datetime import datetime
1316

1417
from websocket import create_connection
1518
from websocket._exceptions import WebSocketConnectionClosedException
@@ -92,6 +95,34 @@ def _check_volume(volume):
9295
return volume
9396

9497

98+
def login_class_decorator(cls):
99+
for name, method in inspect.getmembers(cls):
100+
if hasattr(method, "_use_check_login"):
101+
LOGGER.debug(name)
102+
def wrapper(clss, func):
103+
clss = func.__
104+
if clss.status == STATUS.NOT_LOGGED:
105+
raise NotLogged()
106+
try:
107+
return func
108+
except SocketError as e:
109+
LOGGER.info(
110+
"re-logging in due to LOGIN_TIMEOUT gone")
111+
clss.login(clss._login_data[0], clss._login_data[1])
112+
return func
113+
#except Exception as e:
114+
# LOGGER.warning(e)
115+
#finally:
116+
# clss.login(clss._login_data[0], clss._login_data[1])
117+
# return method(clss, *args, **kwargs)
118+
setattr(cls, name, wrapper(cls, method))
119+
return cls
120+
121+
def check_login_dec(view):
122+
view._use_check_login = True
123+
return view
124+
125+
@login_class_decorator
95126
class BaseClient(object):
96127
"""main client class"""
97128

@@ -103,14 +134,28 @@ def __init__(self):
103134
LOGGER.debug("BaseClient inited")
104135
self.LOGGER = logging.getLogger('XTBApi.api.BaseClient')
105136

137+
def _login_decorator(self, func, *args, **kwargs):
138+
if self.status == STATUS.NOT_LOGGED:
139+
raise NotLogged()
140+
try:
141+
return func(*args, **kwargs)
142+
except SocketError as e:
143+
LOGGER.info("re-logging in due to LOGIN_TIMEOUT gone")
144+
self.login(self._login_data[0], self._login_data[1])
145+
return func(*args, **kwargs)
146+
except Exception as e:
147+
LOGGER.warning(e)
148+
self.login(self._login_data[0], self._login_data[1])
149+
return func(*args, **kwargs)
150+
106151
def _send_command(self, dict_data):
107152
"""send command to api"""
108153
time_interval = time.time() - self._time_last_request
109154
self.LOGGER.debug("took {} s.".format(time_interval))
110155
if time_interval < MAX_TIME_INTERVAL:
111156
time.sleep(MAX_TIME_INTERVAL - time_interval)
112-
self.ws.send(json.dumps(dict_data))
113157
try:
158+
self.ws.send(json.dumps(dict_data))
114159
response = self.ws.recv()
115160
except WebSocketConnectionClosedException:
116161
raise SocketError()
@@ -123,17 +168,14 @@ def _send_command(self, dict_data):
123168
self.LOGGER.debug(res['returnData'])
124169
return res['returnData']
125170

126-
def _check_login(self):
127-
if self.status == STATUS.NOT_LOGGED:
128-
raise NotLogged()
129-
elif time.time() - self._time_last_request >= LOGIN_TIMEOUT:
130-
self.LOGGER.info("re-logging in due to LOGIN_TIMEOUT gone")
131-
self.login(self._login_data[0], self._login_data[1])
171+
def _send_command_with_check(self, dict_data):
172+
"""with check login"""
173+
return self._login_decorator(self._send_command, dict_data)
132174

133-
def login(self, user_id, password):
175+
def login(self, user_id, password, mode='demo'):
134176
"""login command"""
135177
data = _get_data("login", userId=user_id, password=password)
136-
self.ws = create_connection("wss://ws.xapi.pro/demo")
178+
self.ws = create_connection(f"wss://ws.xtb.com/{mode}")
137179
response = self._send_command(data)
138180
self._login_data = (user_id, password)
139181
self.status = STATUS.LOGGED
@@ -150,22 +192,19 @@ def logout(self):
150192

151193
def get_all_symbols(self):
152194
"""getAllSymbols command"""
153-
self._check_login()
154195
data = _get_data("getAllSymbols")
155196
self.LOGGER.info("CMD: get all symbols...")
156-
return self._send_command(data)
197+
return self._send_command_with_check(data)
157198

158199
def get_calendar(self):
159200
"""getCalendar command"""
160-
self._check_login()
161201
data = _get_data("getCalendar")
162202
self.LOGGER.info("CMD: get calendar...")
163-
return self._send_command(data)
203+
return self._send_command_with_check(data)
164204

165205
def get_chart_last_request(self, symbol, period, start):
166206
"""getChartLastRequest command"""
167207
_check_period(period)
168-
self._check_login()
169208
args = {
170209
"period": period,
171210
"start": start * 1000,
@@ -175,11 +214,10 @@ def get_chart_last_request(self, symbol, period, start):
175214
self.LOGGER.info(f"CMD: get chart last request for {symbol} of period"
176215
f" {period} from {start}...")
177216

178-
return self._send_command(data)
217+
return self._send_command_with_check(data)
179218

180219
def get_chart_range_request(self, symbol, period, start, end, ticks):
181220
"""getChartRangeRequest command"""
182-
_check_period(period)
183221
if not isinstance(ticks, int):
184222
raise ValueError(f"ticks value {ticks} must be int")
185223
self._check_login()
@@ -194,115 +232,111 @@ def get_chart_range_request(self, symbol, period, start, end, ticks):
194232
self.LOGGER.info(f"CMD: get chart range request for {symbol} of "
195233
f"{period} from {start} to {end} with ticks of "
196234
f"{ticks}...")
197-
return self._send_command(data)
235+
return self._send_command_with_check(data)
198236

199237
def get_commission(self, symbol, volume):
200238
"""getCommissionDef command"""
201239
volume = _check_volume(volume)
202-
self._check_login()
203240
data = _get_data("getCommissionDef", symbol=symbol, volume=volume)
204241
self.LOGGER.info(f"CMD: get commission for {symbol} of {volume}...")
205-
return self._send_command(data)
242+
return self._send_command_with_check(data)
206243

207244
def get_margin_level(self):
208245
"""getMarginLevel command
209246
get margin information"""
210-
self._check_login()
211247
data = _get_data("getMarginLevel")
212248
self.LOGGER.info("CMD: get margin level...")
213-
return self._send_command(data)
249+
return self._send_command_with_check(data)
214250

215251
def get_margin_trade(self, symbol, volume):
216252
"""getMarginTrade command
217253
get expected margin for volumes used symbol"""
218254
volume = _check_volume(volume)
219-
self._check_login()
220255
data = _get_data("getMarginTrade", symbol=symbol, volume=volume)
221256
self.LOGGER.info(f"CMD: get margin trade for {symbol} of {volume}...")
222-
return self._send_command(data)
257+
return self._send_command_with_check(data)
223258

224259
def get_profit_calculation(self, symbol, mode, volume, op_price, cl_price):
225260
"""getProfitCalculation command
226261
get profit calculation for symbol with vol, mode and op, cl prices"""
227262
_check_mode(mode)
228263
volume = _check_volume(volume)
229-
self._check_login()
230264
data = _get_data("getProfitCalculation", closePrice=cl_price,
231265
cmd=mode, openPrice=op_price, symbol=symbol,
232266
volume=volume)
233267
self.LOGGER.info(f"CMD: get profit calculation for {symbol} of "
234268
f"{volume} from {op_price} to {cl_price} in mode "
235269
f"{mode}...")
236-
return self._send_command(data)
270+
return self._send_command_with_check(data)
237271

238272
def get_server_time(self):
239273
"""getServerTime command"""
240-
self._check_login()
241274
data = _get_data("getServerTime")
242275
self.LOGGER.info("CMD: get server time...")
243-
return self._send_command(data)
276+
return self._send_command_with_check(data)
244277

245278
def get_symbol(self, symbol):
246279
"""getSymbol command"""
247-
self._check_login()
248280
data = _get_data("getSymbol", symbol=symbol)
249281
self.LOGGER.info(f"CMD: get symbol {symbol}...")
250-
return self._send_command(data)
282+
return self._send_command_with_check(data)
251283

252284
def get_tick_prices(self, symbols, start, level=0):
253285
"""getTickPrices command"""
254-
self._check_login()
255286
data = _get_data("getTickPrices", level=level, symbols=symbols,
256287
timestamp=start)
257288
self.LOGGER.info(f"CMD: get tick prices of {symbols} from {start} "
258289
f"with level {level}...")
259-
return self._send_command(data)
290+
return self._send_command_with_check(data)
260291

261292
def get_trade_records(self, trade_position_list):
262293
"""getTradeRecords command
263294
takes a list of position id"""
264-
self._check_login()
265295
data = _get_data("getTradeRecords", orders=trade_position_list)
266296
self.LOGGER.info(f"CMD: get trade records of len "
267297
f"{len(trade_position_list)}...")
268-
return self._send_command(data)
298+
return self._send_command_with_check(data)
269299

270300
def get_trades(self, opened_only=True):
271301
"""getTrades command"""
272-
self._check_login()
273302
data = _get_data("getTrades", openedOnly=opened_only)
274303
self.LOGGER.info("CMD: get trades...")
275-
return self._send_command(data)
304+
return self._send_command_with_check(data)
276305

277306
def get_trades_history(self, start, end):
278307
"""getTradesHistory command
279308
can take 0 as actual time"""
280-
self._check_login()
281309
data = _get_data("getTradesHistory", end=end, start=start)
282310
self.LOGGER.info(f"CMD: get trades history from {start} to {end}...")
283-
return self._send_command(data)
311+
return self._send_command_with_check(data)
284312

285313
def get_trading_hours(self, trade_position_list):
286314
"""getTradingHours command"""
287-
self._check_login()
315+
# EDITED IN ALPHA2
288316
data = _get_data("getTradingHours", symbols=trade_position_list)
289317
self.LOGGER.info(f"CMD: get trading hours of len "
290318
f"{len(trade_position_list)}...")
291-
return self._send_command(data)
319+
response = self._send_command_with_check(data)
320+
for symbol in response:
321+
for day in symbol['trading']:
322+
day['fromT'] = int(day['fromT'] / 1000)
323+
day['toT'] = int(day['toT'] / 1000)
324+
for day in symbol['quotes']:
325+
day['fromT'] = int(day['fromT'] / 1000)
326+
day['toT'] = int(day['toT'] / 1000)
327+
return response
292328

293329
def get_version(self):
294330
"""getVersion command"""
295-
self._check_login()
296331
data = _get_data("getVersion")
297332
self.LOGGER.info("CMD: get version...")
298-
return self._send_command(data)
333+
return self._send_command_with_check(data)
299334

300335
def ping(self):
301336
"""ping command"""
302-
self._check_login()
303337
data = _get_data("ping")
304338
self.LOGGER.info("CMD: get ping...")
305-
return self._send_command(data)
339+
self._send_command_with_check(data)
306340

307341
def trade_transaction(self, symbol, mode, trans_type, volume, **kwargs):
308342
"""tradeTransaction command"""
@@ -315,7 +349,6 @@ def trade_transaction(self, symbol, mode, trans_type, volume, **kwargs):
315349
assert all([val in accepted_values for val in kwargs.keys()])
316350
_check_mode(mode) # check if mode is acceptable
317351
volume = _check_volume(volume) # check if volume is valid
318-
self._check_login() # check if logged in
319352
info = {
320353
'cmd': mode,
321354
'symbol': symbol,
@@ -330,31 +363,31 @@ def trade_transaction(self, symbol, mode, trans_type, volume, **kwargs):
330363
self.LOGGER.info(f"CMD: trade transaction of {symbol} of mode "
331364
f"{name_of_mode} with type {name_of_type} of "
332365
f"{volume}...")
333-
return self._send_command(data)
366+
return self._send_command_with_check(data)
334367

335368
def trade_transaction_status(self, order_id):
336369
"""tradeTransactionStatus command"""
337-
self._check_login()
338370
data = _get_data("tradeTransactionStatus", order=order_id)
339371
self.LOGGER.info(f"CMD: trade transaction status for {order_id}...")
340-
return self._send_command(data)
372+
return self._send_command_with_check(data)
341373

342374
def get_user_data(self):
343375
"""getCurrentUserData command"""
344-
self._check_login()
345376
data = _get_data("getCurrentUserData")
346377
self.LOGGER.info("CMD: get user data...")
347-
return self._send_command(data)
378+
return self._send_command_with_check(data)
348379

349380

350381
class Transaction(object):
351382
def __init__(self, trans_dict):
352383
self._trans_dict = trans_dict
384+
self.mode = {0: 'buy', 1: 'sell'}[trans_dict['cmd']]
353385
self.order_id = trans_dict['order']
354386
self.symbol = trans_dict['symbol']
355387
self.volume = trans_dict['volume']
356388
self.price = trans_dict['close_price']
357389
self.actual_profit = trans_dict['profit']
390+
self.timestamp = trans_dict['open_time'] / 1000
358391
LOGGER.debug(f"Transaction {self.order_id} inited")
359392

360393

@@ -366,13 +399,63 @@ def __init__(self):
366399
self.LOGGER = logging.getLogger('XTBApi.api.Client')
367400
self.LOGGER.info("Client inited")
368401

402+
def check_if_market_open(self, list_of_symbols):
403+
"""check if market is open for symbol in symbols"""
404+
_td = datetime.today()
405+
actual_tmsp = _td.hour * 3600 + _td.minute * 60 + _td.second
406+
response = self.get_trading_hours(list_of_symbols)
407+
market_values = {}
408+
for symbol in response:
409+
today_values = [day for day in symbol['trading'] if day['day'] ==
410+
_td.isoweekday()][0]
411+
if today_values['fromT'] <= actual_tmsp <= today_values['toT']:
412+
market_values[symbol['symbol']] = True
413+
else:
414+
market_values[symbol['symbol']] = False
415+
return market_values
416+
417+
def get_lastn_candle_history(self, symbol, timeframe_in_seconds, number):
418+
"""get last n candles of timeframe"""
419+
acc_tmf = [60, 300, 900, 1800, 3600, 14400, 86400, 604800, 2592000]
420+
if timeframe_in_seconds not in acc_tmf:
421+
raise ValueError(f"timeframe not accepted, not in "
422+
f"{', '.join([str(x) for x in acc_tmf])}")
423+
sec_prior = timeframe_in_seconds * number
424+
LOGGER.debug(f"sym: {symbol}, tmf: {timeframe_in_seconds},"
425+
f" {time.time() - sec_prior}")
426+
res = {'rateInfos': []}
427+
while len(res['rateInfos']) < number:
428+
res = self.get_chart_last_request(symbol,
429+
timeframe_in_seconds // 60, time.time() - sec_prior)
430+
LOGGER.debug(res)
431+
res['rateInfos'] = res['rateInfos'][-number:]
432+
sec_prior *= 3
433+
candle_history = []
434+
for candle in res['rateInfos']:
435+
_pr = candle['open']
436+
op_pr = _pr / 10 ** res['digits']
437+
cl_pr = (_pr + candle['close']) / 10 ** res['digits']
438+
hg_pr = (_pr + candle['high']) / 10 ** res['digits']
439+
lw_pr = (_pr + candle['low']) / 10 ** res['digits']
440+
new_candle_entry = {'timestamp': candle['ctm'] / 1000, 'open':
441+
op_pr, 'close': cl_pr, 'high': hg_pr, 'low': lw_pr,
442+
'volume': candle['vol']}
443+
candle_history.append(new_candle_entry)
444+
LOGGER.debug(candle_history)
445+
return candle_history
446+
369447
def update_trades(self):
370448
"""update trade list"""
371449
trades = self.get_trades()
372-
data = trades
373-
for trade in data:
450+
self.trade_rec.clear()
451+
for trade in trades:
374452
obj_trans = Transaction(trade)
375453
self.trade_rec[obj_trans.order_id] = obj_trans
454+
#values_to_del = [key for key, trad_not_listed in
455+
# self.trade_rec.items() if trad_not_listed.order_id
456+
# not in [x['order'] for x in trades]]
457+
#for key in values_to_del:
458+
# del self.trade_rec[key]
376459
self.LOGGER.info(f"updated {len(self.trade_rec)} trades")
377460
return self.trade_rec
378461

0 commit comments

Comments
 (0)