-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathAlorPy.py
1967 lines (1727 loc) · 150 KB
/
AlorPy.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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import logging # Будем вести лог
from typing import Union # Объединение типов
from math import log10 # Кол-во десятичных знаков будем получать из шага цены через десятичный логарифм
from datetime import datetime
from time import time_ns # Текущее время в наносекундах, прошедших с 01.01.1970 UTC
from uuid import uuid4 # Номера подписок должны быть уникальными во времени и пространстве
from json import loads, JSONDecodeError, dumps # Сервер WebSockets работает с JSON сообщениями
from asyncio import get_event_loop, create_task, run, CancelledError # Работа с асинхронными функциями
from threading import Thread # Подписки сервера WebSockets будем получать в отдельном потоке
from pytz import timezone, utc # Работаем с временнОй зоной и UTC
import requests.adapters # Настройки запросов/ответов
from requests import post, get, put, delete, Response # Запросы/ответы от сервера запросов
from jwt import decode
from urllib3.exceptions import MaxRetryError # Соединение с сервером не установлено за максимальное кол-во попыток подключения
from websockets import connect, ConnectionClosed # Работа с сервером WebSockets
from AlorPy import Config # Файл конфигурации
# noinspection PyShadowingBuiltins
class AlorPy:
"""Работа с Alor OpenAPI V2 https://alor.dev/docs из Python"""
requests.adapters.DEFAULT_RETRIES = 10 # Настройка кол-ва попыток
requests.adapters.DEFAULT_POOL_TIMEOUT = 10 # Настройка таймауту запроса в секундах
tz_msk = timezone('Europe/Moscow') # Время UTC будем приводить к московскому времени
jwt_token_ttl = 60 # Время жизни токена JWT в секундах
exchanges = ('MOEX', 'SPBX',) # Биржи
logger = logging.getLogger('AlorPy') # Будем вести лог
def __init__(self, refresh_token=Config.refresh_token, demo=False):
"""Инициализация
:param str refresh_token: Токен
:param bool demo: Режим демо торговли. По умолчанию установлен режим реальной торговли
"""
self.oauth_server = f'https://oauth{"dev" if demo else ""}.alor.ru' # Сервер аутентификации
self.api_server = f'https://api{"dev" if demo else ""}.alor.ru' # Сервер запросов
self.cws_server = f'wss://api{"dev" if demo else ""}.alor.ru/cws' # Сервис работы с заявками WebSocket
self.cws_socket = None # Подключение к серверу WebSocket
self.ws_server = f'wss://api{"dev" if demo else ""}.alor.ru/ws' # Сервис подписок и событий WebSocket
self.ws_socket = None # Подключение к серверу WebSocket
self.ws_task = None # Задача управления подписками WebSocket
self.ws_ready = False # WebSocket готов принимать запросы
# События Alor OpenAPI V2
self.on_change_order_book = self.default_handler # Биржевой стакан
self.on_new_bar = self.default_handler # Новый бар
self.on_new_quotes = self.default_handler # Котировки
self.on_all_trades = self.default_handler # Все сделки
self.on_position = self.default_handler # Позиции по ценным бумагам и деньгам
self.on_summary = self.default_handler # Сводная информация по портфелю
self.on_risk = self.default_handler # Портфельные риски
self.on_spectra_risk = self.default_handler # Риски срочного рынка (FORTS)
self.on_trade = self.default_handler # Сделки
self.on_stop_order = self.default_handler # Стоп заявки
self.on_stop_order_v2 = self.default_handler # Стоп заявки v2
self.on_order = self.default_handler # Заявки
self.on_symbol = self.default_handler # Информация о финансовых инструментах
# События WebSocket Thread/Task
self.on_entering = self.default_handler # Начало входа (Thread)
self.on_enter = self.default_handler # Вход (Thread)
self.on_connect = self.default_handler # Подключение к серверу (Task)
self.on_resubscribe = self.default_handler # Возобновление подписок (Task)
self.on_ready = self.default_handler # Готовность к работе (Task)
self.on_disconnect = self.default_handler # Отключение от сервера (Task)
self.on_timeout = self.default_handler # Таймаут/максимальное кол-во попыток подключения (Task)
self.on_error = self.default_handler # Ошибка (Task)
self.on_cancel = self.default_handler # Отмена (Task)
self.on_exit = self.default_handler # Выход (Thread)
self.refresh_token = refresh_token # Токен
self.jwt_token = None # Токен JWT
self.jwt_token_decoded = dict() # Информация по портфелям
self.jwt_token_issued = 0 # UNIX время в секундах выдачи токена JWT
self.accounts = list() # Счета (портфели по договорам)
self.get_jwt_token() # Получаем токен JWT
if self.jwt_token_decoded:
all_agreements = self.jwt_token_decoded['agreements'].split(' ') # Все договоры
all_portfolios = self.jwt_token_decoded['portfolios'].split(' ') # Все портфели. К каждому договору привязаны 3 портфеля
account_id = portfolio_id = 0 # Начальная позиция договоров и портфелей
for agreement in all_agreements: # Пробегаемся по всем договорам
for portfolio in all_portfolios[portfolio_id:portfolio_id + 3]: # Пробегаемся по 3-м портфелям каждого договора
if portfolio.startswith('D'): # Портфель счета фондового рынка начинается с D и имеет формат D12345
type = 'securities' # Тип счета
exchanges = self.exchanges # Все биржи
boards = ('TQRD', 'TQOY', 'TQIF', 'TQBR', 'MTQR', 'TQOB', 'TQIR', 'EQRP_INFO', 'TQTF', 'FQDE', 'INDX', 'TQOD', 'FQBR', 'TQCB', 'TQPI', 'TQBD') # Режимы торгов
elif portfolio.startswith('G'): # Портфель валютного счета начинается с G и имеет формат G12345
type = 'fx' # Тип счета
exchanges = (self.exchanges[0],) # Биржа MOEX
boards = ('CETS_SU', 'INDXC', 'CETS') # Режимы торгов
elif portfolio.startswith('750'): # Портфель счета срочного рынка начинается с 750 и имеет формат 750****
type = 'derivatives' # Тип счета
exchanges = (self.exchanges[0],) # Биржа MOEX
boards = ('SPBOPT', 'OPTCOMDTY', 'OPTSPOT', 'SPBFUT', 'OPTCURNCY', 'RFUD', 'ROPD') # Режимы торгов RFUD=SBPFUT, ROPD=SPBOPT
else: # Неизвестный портфель
logging.warning(f'Не определен тип счета для договора {agreement}, портфеля {portfolio}')
continue # Переходим к следующему портфелю, дальше не продолжаем
self.accounts.append(dict(account_id=account_id, agreement=agreement, portfolio=portfolio, type=type, exchanges=exchanges, boards=boards)) # Добавляем договор/портфель/биржи/режимы торгов
account_id += 1 # Смещаем на следующий договор
portfolio_id += 3 # Смещаем на начальную позицию портфелей для следующего договора
self.subscriptions = {} # Справочник подписок. Для возобновления всех подписок после перезагрузки сервера Алор
self.symbols = {} # Справочник тикеров
def __enter__(self):
"""Вход в класс, например, с with"""
return self
# ClientInfo - Информация о клиенте
def get_portfolio_summary(self, portfolio, exchange, format='Simple'):
"""Получение информации о портфеле
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/clients/{exchange}/{portfolio}/summary', params=params, headers=self.get_headers()))
def get_positions(self, portfolio, exchange, without_currency=False, format='Simple'):
"""Получение информации о позициях
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param bool without_currency: Исключить из ответа все денежные инструменты, по умолчанию false
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'withoutCurrency': without_currency, 'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/Clients/{exchange}/{portfolio}/positions', params=params, headers=self.get_headers()))
def get_position(self, portfolio, exchange, symbol, format='Simple'):
"""Получение информации о позициях выбранного инструмента
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/Clients/{exchange}/{portfolio}/positions/{symbol}', params=params, headers=self.get_headers()))
def get_trades(self, portfolio, exchange, with_repo=None, format='Simple'):
"""Получение информации о сделках
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param bool with_repo: Флаг отображения заявок с РЕПО
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
if with_repo:
params['withRepo'] = with_repo
return self.check_result(get(url=f'{self.api_server}/md/v2/Clients/{exchange}/{portfolio}/trades', params=params, headers=self.get_headers()))
def get_trade(self, portfolio, exchange, symbol, format='Simple'):
"""Получение информации о сделках по выбранному инструменту
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/Clients/{exchange}/{portfolio}/{symbol}/trades', params=params, headers=self.get_headers()))
def get_forts_risk(self, portfolio, exchange, format='Simple'):
"""Получение информации о рисках на срочном рынке
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/Clients/{exchange}/{portfolio}/fortsrisk', params=params, headers=self.get_headers()))
def get_risk(self, portfolio, exchange, format='Simple'):
"""Получение информации о рисках
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/Clients/{exchange}/{portfolio}/risk', params=params, headers=self.get_headers()))
def get_login_positions(self, login, without_currency=None, format='Simple'):
"""Получение информации о позициях по логину
:param str login: Логин торгового аккаунта
:param bool without_currency: Исключить из ответа все денежные инструменты
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
if without_currency:
params['withoutCurrency'] = without_currency
return self.check_result(get(url=f'{self.api_server}/md/v2/Clients/{login}/positions', params=params, headers=self.get_headers()))
def get_trades_history_v2(self, portfolio, exchange, ticker=None, date_from=None, id_from=None, limit=None, descending=None, side=None, format='Simple'):
"""Получение истории сделок v2
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str ticker: Тикер/код инструмента. ISIN для облигаций
:param str date_from: Начиная с какой даты отдавать историю сделок. Например, '2021-10-13'
:param int id_from: Начальный номер сделки для фильтра результатов
:param int limit: Количество возвращаемых записей. Не более 1000 сделок за один запрос
:param bool descending: Флаг обратной сортировки выдачи
:param str side: Направление сделки: 'buy' - Покупка, 'sell' - Продажа
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
if ticker:
params['ticker'] = ticker
if date_from:
params['dateFrom'] = date_from
if id_from:
params['from'] = id_from
if limit:
params['limit'] = limit
if descending:
params['descending'] = descending
if side:
params['side'] = side
return self.check_result(get(url=f'{self.api_server}/md/v2/Stats/{exchange}/{portfolio}/history/trades', params=params, headers=self.get_headers()))
def get_trades_symbol_v2(self, portfolio, exchange, symbol, date_from=None, id_from=None, limit=None, descending=None, side=None, format='Simple'):
"""Получение истории сделок (один тикер) v2
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str date_from: Начиная с какой даты отдавать историю сделок. Например, '2021-10-13'
:param int id_from: Начиная с какого ID (номера сделки) отдавать историю сделок
:param int limit: Ограничение на количество выдаваемых результатов поиска
:param bool descending: Флаг загрузки элементов с конца списка
:param str side: Направление сделки: 'buy' - Покупка, 'sell' - Продажа
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
if date_from:
params['dateFrom'] = date_from
if id_from:
params['from'] = id_from
if limit:
params['limit'] = limit
if descending:
params['descending'] = descending
if side:
params['side'] = side
return self.check_result(get(url=f'{self.api_server}/md/v2/Stats/{exchange}/{portfolio}/history/trades/{symbol}', params=params, headers=self.get_headers()))
# Instruments - Ценные бумаги / инструменты
def get_securities(self, symbol, limit=None, offset=None, sector=None, cficode=None, exchange=None, instrument_group=None, include_non_base_boards=None, format='Simple'):
"""Получение информации о торговых инструментах
:param str symbol: Маска тикера. Например SB выведет SBER, SBERP, SBRB ETF и пр.
:param int limit: Ограничение на количество выдаваемых результатов поиска
:param int offset: Смещение начала выборки (для пагинации)
:param str sector: Рынок на бирже. FOND, FORTS, CURR
:param str cficode: Код финансового инструмента по стандарту ISO 10962. EXXXXX
:param str exchange: Биржа 'MOEX' или 'SPBX':
:param str instrument_group: Код режима торгов
:param str include_non_base_boards: Флаг выгрузки инструментов для всех режимов торгов, включая отличающиеся от установленного для инструмента значения параметра
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'query': symbol, 'format': format}
if limit:
params['limit'] = limit
if offset:
params['offset'] = offset
if sector:
params['sector'] = sector
if cficode:
params['cficode'] = cficode
if exchange:
params['exchange'] = exchange
if instrument_group:
params['instrumentGroup'] = instrument_group
if include_non_base_boards:
params['includeNonBaseBoards'] = include_non_base_boards
return self.check_result(get(url=f'{self.api_server}/md/v2/Securities', params=params, headers=self.get_headers()))
def get_securities_exchange(self, exchange, market=None, include_old=None, limit=None, include_non_base_boards=None, offset=None, format='Simple'):
"""Получение информации о торговых инструментах на выбранной бирже
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str market: Рынок на бирже. FOND, FORTS, CURR
:param bool include_old: Флаг загрузки устаревших инструментов
:param int limit: Ограничение на количество выдаваемых результатов поиска
:param str include_non_base_boards: Флаг выгрузки инструментов для всех режимов торгов, включая отличающиеся от установленного для инструмента значения параметра
:param int offset: Смещение начала выборки (для пагинации)
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
if market:
params['market'] = market
if include_old:
params['includeOld'] = include_old
if limit:
params['limit'] = limit
if include_non_base_boards:
params['includeNonBaseBoards'] = include_non_base_boards
if offset:
params['offset'] = offset
return self.check_result(get(url=f'{self.api_server}/md/v2/Securities/{exchange}', params=params, headers=self.get_headers()))
def get_symbol(self, exchange, symbol, instrument_group=None, format='Simple'):
"""Получение информации о выбранном финансовом инструменте
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str instrument_group: Код режима торгов
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
if instrument_group:
params['instrumentGroup'] = instrument_group
result: dict = self.check_result(get(url=f'{self.api_server}/md/v2/Securities/{exchange}/{symbol}', params=params, headers=self.get_headers())) # Результат в виде словаря
result['decimals'] = int(log10(1 / result['minstep']) + 0.99) # Кол-во десятичных знаков получаем из шага цены, добавляем в полученный словарь
return result
def get_available_boards(self, exchange, symbol):
"""Получение списка бордов для выбранного финансового инструмента
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
"""
return self.check_result(get(url=f'{self.api_server}/md/v2/Securities/{exchange}/{symbol}/availableBoards', headers=self.get_headers()))
def get_all_trades(self, exchange, symbol, instrument_group=None, seconds_from=None, seconds_to=None, id_from=None, id_to=None,
qty_from=None, qty_to=None, price_from=None, price_to=None, side=None, offset=None, take=None, descending=None, include_virtual_trades=None, format='Simple'):
"""Получение информации о всех сделках по ценным бумагам за сегодня
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str instrument_group: Код режима торгов
:param int seconds_from: Дата и время UTC в секундах для первой запрашиваемой сделки
:param int seconds_to: Дата и время UTC в секундах для первой запрашиваемой сделки
:param int id_from: Начальный номер сделки для фильтра результатов
:param int id_to: Конечный номер сделки для фильтра результатов
:param int qty_from: Нижняя граница объёма сделки в лотах
:param int qty_to: Верхняя граница объёма сделки в лотах
:param float price_from: Нижняя граница цены, по которой была совершена сделка
:param float price_to: Верхняя граница цены, по которой была совершена сделка
:param str side: Направление сделки: 'buy' - Покупка, 'sell' - Продажа
:param int offset: Смещение начала выборки (для пагинации)
:param int take: Количество загружаемых элементов
:param bool descending: Флаг загрузки элементов с конца списка
:param bool include_virtual_trades: Флаг загрузки виртуальных (индикативных) сделок, полученных из заявок на питерской бирже
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
if instrument_group:
params['instrumentGroup'] = instrument_group
if seconds_from:
params['from'] = seconds_from
if seconds_to:
params['to'] = seconds_to
if id_from:
params['fromId'] = id_from
if id_to:
params['toId'] = id_to
if qty_from:
params['qtyFrom'] = qty_from
if qty_to:
params['qtyTo'] = qty_to
if price_from:
params['priceFrom'] = price_from
if price_to:
params['priceTo'] = price_to
if side:
params['side'] = side
if offset:
params['offset'] = offset
if take:
params['take'] = take
if descending:
params['descending'] = descending
if include_virtual_trades:
params['includeVirtualTrades'] = include_virtual_trades
return self.check_result(get(url=f'{self.api_server}/md/v2/Securities/{exchange}/{symbol}/alltrades', params=params, headers=self.get_headers()))
def get_all_trades_history(self, exchange, symbol, instrument_group=None, seconds_from=None, seconds_to=None, limit=50000, offset=None, format='Simple'):
"""Получение исторической информации о всех сделках по ценным бумагам
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str instrument_group: Код режима торгов
:param int seconds_from: Начало отрезка времени UTC в секундах для фильтра результатов
:param int seconds_to: Начало отрезка времени UTC в секундах для фильтра результатов
:param int limit: Ограничение на количество выдаваемых результатов поиска (1-50000)
:param int offset: Смещение начала выборки (для постраничного вывода)
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'limit': limit, 'format': format}
if instrument_group:
params['instrumentGroup'] = instrument_group
if seconds_from:
params['from'] = seconds_from
if seconds_to:
params['to'] = seconds_to
if offset:
params['offset'] = offset
return self.check_result(get(url=f'{self.api_server}/md/v2/Securities/{exchange}/{symbol}/alltrades/history', params=params, headers=self.get_headers()))
def get_actual_futures_quote(self, exchange, symbol, format='Simple'):
"""Получение котировки по ближайшему фьючерсу (код)
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/Securities/{exchange}/{symbol}/actualFuturesQuote', params=params, headers=self.get_headers()))
def get_quotes(self, symbols, format='Simple'):
"""Получение информации о котировках для выбранных инструментов
:param str symbols: Принимает несколько пар биржа-тикер. Пары отделены запятыми. Биржа и тикер разделены двоеточием.
Пример: MOEX:SBER,MOEX:GAZP,SPBX:AAPL
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/Securities/{symbols}/quotes', params=params, headers=self.get_headers()))
def get_currency_pairs(self, format='Simple'):
"""Получение информации о валютных парах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/Securities/currencyPairs', params=params, headers=self.get_headers()))
def get_order_book(self, exchange, symbol, depth=20, format='Simple'):
"""Получение информации о биржевом стакане
:param exchange: Биржа 'MOEX' или 'SPBX'
:param symbol: Тикер
:param depth: Глубина стакана. Стандартное и максимальное значение - 20 (20х20)
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'depth': depth, 'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/orderbooks/{exchange}/{symbol}', params=params, headers=self.get_headers()))
def get_risk_rates(self, exchange, ticker=None, risk_category_id=None, search=None, limit=None, offset=None):
"""Запрос ставок риска
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str ticker: Тикер, код инструмента, ISIN для облигаций
:param int risk_category_id: Id вашей (или той которая интересует) категории риска. Можно получить из запроса информации по клиенту или через кабинет клиента
:param str search: Часть Тикера, кода инструмента, ISIN для облигаций. Вернет все совпадения, начинающиеся с
:param int limit: Ограничение на количество выдаваемых результатов поиска
:param int offset: Смещение начала выборки (для пагинации)
"""
params = {'exchange': exchange}
if ticker:
params['ticker'] = ticker
if risk_category_id:
params['riskCategoryId'] = risk_category_id
if search:
params['search'] = search
if limit:
params['limit'] = limit
if offset:
params['offset'] = offset
return self.check_result(get(url=f'{self.api_server}/md/v2/risk/rates', params=params, headers=self.get_headers()))
def get_history(self, exchange, symbol, tf, seconds_from=0, seconds_to=32536799999, untraded=False, format='Simple'):
"""Запрос истории рынка для выбранных биржи и финансового инструмента
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param int|str tf: Длительность временнОго интервала в секундах или код ("D" - дни, "W" - недели, "M" - месяцы, "Y" - годы)
:param int seconds_from: Дата и время UTC в секундах для первого запрашиваемого бара
:param int seconds_to: Дата и время UTC в секундах для последнего запрашиваемого бара
:param bool untraded: Флаг для поиска данных по устаревшим или экспирированным инструментам. При использовании требуется точное совпадение тикера
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'exchange': exchange, 'symbol': symbol, 'tf': tf, 'from': seconds_from, 'to': seconds_to, 'untraded': untraded, 'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/history', params=params, headers=self.get_headers()))
# Other - Другое
def get_time(self):
"""Запрос текущего UTC времени в секундах на сервере
Если этот запрос выполнен без авторизации, то будет возвращено время, которое было 15 минут назад
"""
return self.check_result(get(url=f'{self.api_server}/md/v2/time', headers=self.get_headers()))
# Orders Работа с заявками
def get_orders(self, portfolio, exchange, format='Simple'):
"""Получение информации о всех заявках
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/clients/{exchange}/{portfolio}/orders', params=params, headers=self.get_headers()))
def get_order(self, portfolio, exchange, order_id, format='Simple'):
"""Получение информации о выбранной заявке
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param int order_id: Номер заявки на бирже
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/clients/{exchange}/{portfolio}/orders/{order_id}', params=params, headers=self.get_headers()))
def create_market_order(self, portfolio, exchange, symbol, side, quantity, comment='', time_in_force='GoodTillCancelled'):
"""Создание рыночной заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param str comment: Пользовательский комментарий к заявке
:param str time_in_force: 'OneDay' - До конца дня, 'ImmediateOrCancel' - Снять остаток, 'FillOrKill' - Исполнить целиком или отклонить, 'GoodTillCancelled' - Активна до отмены
"""
headers = self.get_headers()
headers['X-ALOR-REQID'] = f'{portfolio};{self.get_request_id()}' # Портфель с уникальным идентификатором запроса
j = {'side': side, 'type': 'market', 'quantity': abs(quantity), 'instrument': {'symbol': symbol, 'exchange': exchange}, 'user': {'portfolio': portfolio}, 'comment': comment, 'timeInForce': time_in_force}
return self.check_result(post(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/actions/market', headers=headers, json=j))
def create_limit_order(self, portfolio, exchange, symbol, side, quantity, limit_price, comment='', time_in_force='GoodTillCancelled', iceberg_fixed=None, iceberg_variance=None):
"""Создание лимитной заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param float limit_price: Лимитная цена
:param str comment: Пользовательский комментарий к заявке
:param str time_in_force: 'OneDay' - До конца дня, 'ImmediateOrCancel' - Снять остаток, 'FillOrKill' - Исполнить целиком или отклонить, 'GoodTillCancelled' - Активна до отмены
:param int iceberg_fixed: Видимая постоянная часть айсберг-заявки в лотах
:param int iceberg_variance: Амплитуда отклонения (в % от icebergFixed) случайной надбавки к видимой части айсберг-заявки. Только срочный рынок
"""
headers = self.get_headers()
headers['X-ALOR-REQID'] = f'{portfolio};{self.get_request_id()}' # Портфель с уникальным идентификатором запроса
j = {'side': side, 'type': 'limit', 'quantity': abs(quantity), 'price': limit_price, 'instrument': {'symbol': symbol, 'exchange': exchange}, 'user': {'portfolio': portfolio}, 'comment': comment, 'timeInForce': time_in_force}
if iceberg_fixed:
j['icebergFixed'] = iceberg_fixed
if iceberg_variance:
j['icebergVariance'] = iceberg_variance
return self.check_result(post(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/actions/limit', headers=headers, json=j))
def edit_market_order(self, account, portfolio, exchange, order_id, symbol, side, quantity, comment='', time_in_force='GoodTillCancelled'):
"""Изменение рыночной заявки
:param str account: Счет
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param int order_id: Номер заявки
:param str symbol: Тикер
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param str comment: Пользовательский комментарий к заявке
:param str time_in_force: 'OneDay' - До конца дня, 'ImmediateOrCancel' - Снять остаток, 'FillOrKill' - Исполнить целиком или отклонить, 'GoodTillCancelled' - Активна до отмены
"""
headers = self.get_headers()
headers['X-ALOR-REQID'] = f'{portfolio};{order_id};{quantity}' # Портфель с уникальным идентификатором запроса и кол-вом в лотах
j = {'side': side, 'type': 'market', 'id': order_id, 'quantity': abs(quantity), 'instrument': {'symbol': symbol, 'exchange': exchange}, 'user': {'account': account, 'portfolio': portfolio}, 'comment': comment, 'timeInForce': time_in_force}
return self.check_result(put(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/actions/market/{order_id}', headers=headers, json=j))
def edit_limit_order(self, portfolio, exchange, order_id, symbol, side, quantity, limit_price, comment='', time_in_force='GoodTillCancelled', iceberg_fixed=None, iceberg_variance=None):
"""Изменение лимитной заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param int order_id: Номер заявки
:param str symbol: Тикер
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param float limit_price: Лимитная цена
:param str comment: Пользовательский комментарий к заявке
:param str time_in_force: 'OneDay' - До конца дня, 'ImmediateOrCancel' - Снять остаток, 'FillOrKill' - Исполнить целиком или отклонить, 'GoodTillCancelled' - Активна до отмены
:param int iceberg_fixed: Видимая постоянная часть айсберг-заявки в лотах
:param int iceberg_variance: Амплитуда отклонения (в % от icebergFixed) случайной надбавки к видимой части айсберг-заявки. Только срочный рынок
"""
headers = self.get_headers()
headers['X-ALOR-REQID'] = f'{portfolio};{order_id};{quantity}' # Портфель с уникальным идентификатором запроса и кол-вом в лотах
j = {'side': side, 'type': 'limit', 'quantity': abs(quantity), 'price': limit_price, 'instrument': {'symbol': symbol, 'exchange': exchange}, 'user': {'portfolio': portfolio}, 'comment': comment, 'timeInForce': time_in_force}
if iceberg_fixed:
j['icebergFixed'] = iceberg_fixed
if iceberg_variance:
j['icebergVariance'] = iceberg_variance
return self.check_result(put(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/actions/limit/{order_id}', headers=headers, json=j))
def estimate_order(self, portfolio, exchange, symbol, price, quantity, board, include_limit_orders=False):
"""Провести оценку одной заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param float price: Цена покупки
:param int quantity: Кол-во в лотах
:param str board: Режим торгов (борд). TQBR - акции, TQOB - облигации, RFUD - фьючерсы, ...
:param bool include_limit_orders: Учитывать ли лимитные заявки при расчете
"""
j = {'portfolio': portfolio, 'ticker': symbol, 'exchange': exchange, 'price': price, 'lotQuantity': quantity, 'board': board, 'includeLimitOrders': include_limit_orders}
return self.check_result(post(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/estimate', json=j))
def estimate_orders(self, orders):
"""Провести оценку нескольких заявок
:param dict orders: Список заявок. Оформлять каждую заявку как в EstimateOrder:
{'portfolio': portfolio, 'ticker': symbol, 'exchange': exchange, 'price': price, 'lotQuantity': quantity, 'board': board, 'includeLimitOrders': include_limit_orders}
"""
return self.check_result(post(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/estimate/all', json=orders))
def delete_order(self, portfolio, exchange, order_id, stop=False, format='Simple'):
"""Снятие заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param int order_id: Номер заявки
:param bool stop: Является ли стоп заявкой
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
headers = self.get_headers()
headers['X-ALOR-REQID'] = self.get_request_id() # Уникальный идентификатор запроса
params = {'portfolio': portfolio, 'exchange': exchange, 'stop': stop, 'jsonResponse': True, 'format': format}
return self.check_result(delete(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/{order_id}', params=params, headers=headers))
# Subscriptions - Подписки и события (WebSocket)
def order_book_get_and_subscribe(self, exchange, symbol, depth=20, frequency=0, format='Simple') -> str:
"""Подписка на информацию о биржевом стакане для выбранных биржи и финансового инструмента
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param int depth: Глубина стакана. Стандартное и максимальное значение - 20 (20х20)
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'OrderBookGetAndSubscribe', 'exchange': exchange, 'code': symbol, 'depth': depth, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def bars_get_and_subscribe(self, exchange, symbol, tf, seconds_from, skip_history=False, frequency=0, format='Simple') -> str:
"""Подписка на историю цен (свечи) для выбранных биржи и финансового инструмента
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param tf: Длительность временнОго интервала в секундах или код ("D" - дни, "W" - недели, "M" - месяцы, "Y" - годы)
:param int seconds_from: Дата и время UTC в секундах для первого запрашиваемого бара
:param bool skip_history: Флаг отсеивания исторических данных: True — отображать только новые данные, False — отображать в том числе данные из истории
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'BarsGetAndSubscribe', 'exchange': exchange, 'code': symbol, 'tf': tf, 'from': int(seconds_from), 'delayed': False, 'skipHistory': skip_history, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def quotes_subscribe(self, exchange, symbol, frequency=0, format='Simple') -> str:
"""Подписка на информацию о котировках для выбранных инструментов и бирж
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'QuotesSubscribe', 'exchange': exchange, 'code': symbol, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def all_trades_subscribe(self, exchange, symbol, depth=0, include_virtual_trades=False, frequency=0, format='Simple') -> str:
"""Подписка на информацию о всех сделках
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param int depth: Если указать, то перед актуальными данными придут данные о последних N сделках. Максимум 5000
:param bool include_virtual_trades: Указывает, нужно ли отправлять виртуальные (индикативные) сделки
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'AllTradesGetAndSubscribe', 'code': symbol, 'exchange': exchange, 'depth': depth, 'includeVirtualTrades': include_virtual_trades, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def positions_get_and_subscribe_v2(self, portfolio, exchange, skip_history=False, frequency=0, format='Simple') -> str:
"""Подписка на информацию о текущих позициях по ценным бумагам и деньгам
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param bool skip_history: Флаг отсеивания исторических данных: True — отображать только новые данные, False — отображать в том числе данные из истории
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'PositionsGetAndSubscribeV2', 'exchange': exchange, 'portfolio': portfolio, 'skipHistory': skip_history, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def summaries_get_and_subscribe_v2(self, portfolio, exchange, skip_history=False, frequency=0, format='Simple') -> str:
"""Подписка на сводную информацию по портфелю
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param bool skip_history: Флаг отсеивания исторических данных: True — отображать только новые данные, False — отображать в том числе данные из истории
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'SummariesGetAndSubscribeV2', 'exchange': exchange, 'portfolio': portfolio, 'skipHistory': skip_history, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def risks_get_and_subscribe(self, portfolio, exchange, skip_history=False, frequency=0, format='Simple') -> str:
"""Подписка на сводную информацию по портфельным рискам
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param bool skip_history: Флаг отсеивания исторических данных: True — отображать только новые данные, False — отображать в том числе данные из истории
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'RisksGetAndSubscribe', 'exchange': exchange, 'portfolio': portfolio, 'skipHistory': skip_history, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def spectra_risks_get_and_subscribe(self, portfolio, exchange, skip_history=False, frequency=0, format='Simple') -> str:
"""Подписка на информацию по рискам срочного рынка (FORTS)
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param bool skip_history: Флаг отсеивания исторических данных: True — отображать только новые данные, False — отображать в том числе данные из истории
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'SpectraRisksGetAndSubscribe', 'exchange': exchange, 'portfolio': portfolio, 'skipHistory': skip_history, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def trades_get_and_subscribe_v2(self, portfolio, exchange, skip_history=False, frequency=0, format='Simple') -> str:
"""Подписка на информацию о сделках
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param bool skip_history: Флаг отсеивания исторических данных: True — отображать только новые данные, False — отображать в том числе данные из истории
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'TradesGetAndSubscribeV2', 'exchange': exchange, 'portfolio': portfolio, 'skipHistory': skip_history, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def orders_get_and_subscribe_v2(self, portfolio, exchange, order_statuses=None, skip_history=False, frequency=0, format='Simple') -> str:
"""Подписка на информацию о текущих заявках на рынке для выбранных биржи и финансового инструмента
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param list[str] order_statuses: Опциональный фильтр по статусам заявок. Влияет только на фильтрацию первичных исторических данных при подписке
Статус исполнения. Пример: order_statuses=['filled', 'canceled']
'working' - На исполнении
'filled' - Исполнена
'canceled' - Отменена
'rejected' - Отклонена
:param bool skip_history: Флаг отсеивания исторических данных: True — отображать только новые данные, False — отображать в том числе данные из истории
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'OrdersGetAndSubscribeV2', 'exchange': exchange, 'portfolio': portfolio, 'skipHistory': skip_history, 'frequency': frequency, 'format': format} # Запрос на подписку
if order_statuses:
request['orderStatuses'] = order_statuses
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def instruments_get_and_subscribe_v2(self, exchange, symbol, frequency=0, format='Simple') -> str:
"""Подписка на изменение информации о финансовых инструментах на выбранной бирже
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'InstrumentsGetAndSubscribeV2', 'code': symbol, 'exchange': exchange, 'frequency': frequency, 'format': format} # Запрос на подписку
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def unsubscribe(self, guid) -> str:
"""Отмена существующей подписки
:param str guid: Уникальный идентификатор подписки
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'unsubscribe', 'token': str(self.get_jwt_token()), 'guid': guid} # Запрос на отмену подписки
get_event_loop().run_until_complete(self.ws_socket.send(dumps(request))) # Отправляем запрос. Дожидаемся его выполнения
del self.subscriptions[guid] # Удаляем подписку из справочника
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
def stop_orders_get_and_subscribe_v2(self, portfolio, exchange, order_statuses=None, skip_history=False, frequency=0, format='Simple') -> str:
"""Подписка на информацию о текущих стоп заявках на рынке для выбранных биржи и финансового инструмента
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param list[str] order_statuses: Опциональный фильтр по статусам заявок. Влияет только на фильтрацию первичных исторических данных при подписке
Статус исполнения. Пример: order_statuses=['filled', 'canceled']
'working' - На исполнении
'filled' - Исполнена
'canceled' - Отменена
'rejected' - Отклонена
:param bool skip_history: Флаг отсеивания исторических данных: True — отображать только новые данные, False — отображать в том числе данные из истории
:param int frequency: Максимальная частота отдачи данных сервером в миллисекундах
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
:return: Уникальный идентификатор подписки
"""
request = {'opcode': 'StopOrdersGetAndSubscribeV2', 'exchange': exchange, 'portfolio': portfolio, 'skipHistory': skip_history, 'frequency': frequency, 'format': format} # Запрос на подписку
if order_statuses:
request['orderStatuses'] = order_statuses
return self.subscribe(request) # Отправляем запрос, возвращаем уникальный идентификатор подписки
# StopOrdersV2 - Стоп-заявки v2
def get_stop_orders(self, portfolio, exchange, format='Simple'):
"""Получение информации о стоп-заявках
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/clients/{exchange}/{portfolio}/stoporders', params=params, headers=self.get_headers()))
def get_stop_order(self, portfolio, exchange, order_id, format='Simple'):
"""Получение информации о выбранной стоп-заявке
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param int order_id: Номер заявки на бирже
:param str format: Формат принимаемых данных 'Simple', 'Slim', 'Heavy'
"""
params = {'format': format}
return self.check_result(get(url=f'{self.api_server}/md/v2/clients/{exchange}/{portfolio}/stoporders/{order_id}', params=params, headers=self.get_headers()))
def create_stop_order(self, portfolio, exchange, symbol, class_code, side, quantity, stop_price, condition='Less', seconds_order_end=0, activate=True):
"""Создание стоп-заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str class_code: Режим торгов
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param float stop_price: Стоп цена
:param str condition: Условие срабатывания 'More', 'Less', 'MoreOrEqual', 'LessOrEqual'
:param int seconds_order_end: Дата и время UTC в секундах завершения сделки
:param bool activate: Флаг активной заявки
"""
headers = self.get_headers()
headers['X-ALOR-REQID'] = f'{portfolio};{self.get_request_id()}' # Портфель с уникальным идентификатором запроса
j = {'side': side, 'condition': condition, 'triggerPrice': stop_price, 'stopEndUnixTime': seconds_order_end, 'quantity': abs(quantity),
'instrument': {'symbol': symbol, 'exchange': exchange, 'instrumentGroup': class_code},
'user': {'portfolio': portfolio, 'exchange': exchange}, 'activate': activate}
return self.check_result(
post(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/actions/stop', headers=headers, json=j))
def create_stop_limit_order(self, portfolio, exchange, symbol, class_code, side, quantity, stop_price, limit_price, condition='Less', seconds_order_end=0,
time_in_force='GoodTillCancelled', iceberg_fixed=None, iceberg_variance=None, activate=True):
"""Создание стоп-лимитной заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str class_code: Класс инструмента
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param float stop_price: Стоп цена
:param float limit_price: Лимитная цена
:param str condition: Условие срабатывания 'More', 'Less', 'MoreOrEqual', 'LessOrEqual'
:param int seconds_order_end: Дата и время UTC в секундах завершения сделки
:param str time_in_force: 'OneDay' - До конца дня, 'ImmediateOrCancel' - Снять остаток, 'FillOrKill' - Исполнить целиком или отклонить, 'GoodTillCancelled' - Активна до отмены
:param int iceberg_fixed: Видимая постоянная часть айсберг-заявки в лотах
:param int iceberg_variance: Амплитуда отклонения (в % от icebergFixed) случайной надбавки к видимой части айсберг-заявки. Только срочный рынок
:param bool activate: Флаг активной заявки
"""
headers = self.get_headers()
headers['X-ALOR-REQID'] = f'{portfolio};{self.get_request_id()}' # Портфель с уникальным идентификатором запроса
j = {'side': side, 'condition': condition, 'triggerPrice': stop_price, 'stopEndUnixTime': seconds_order_end, 'price': limit_price, 'quantity': abs(quantity),
'instrument': {'symbol': symbol, 'exchange': exchange, 'instrumentGroup': class_code}, 'user': {'portfolio': portfolio, 'exchange': exchange},
'timeInForce': time_in_force, 'activate': activate}
if iceberg_fixed:
j['icebergFixed'] = iceberg_fixed
if iceberg_variance:
j['icebergVariance'] = iceberg_variance
return self.check_result(post(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/actions/stopLimit', headers=headers, json=j))
def edit_stop_order_v2(self, portfolio, exchange, order_id, symbol, class_code, side, quantity, stop_price, condition='Less', seconds_order_end=0, activate=True):
"""Изменение стоп-заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param int order_id: Номер заявки
:param str symbol: Тикер
:param str class_code: Класс инструмента
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param float stop_price: Стоп цена
:param str condition: Условие срабатывания 'More', 'Less', 'MoreOrEqual', 'LessOrEqual'
:param int seconds_order_end: Дата и время UTC в секундах завершения сделки
:param bool activate: Флаг активной заявки
"""
headers = self.get_headers()
headers['X-ALOR-REQID'] = f'{portfolio};{self.get_request_id()}' # Портфель с уникальным идентификатором запроса
j = {'side': side, 'condition': condition, 'triggerPrice': stop_price, 'stopEndUnixTime': seconds_order_end, 'quantity': abs(quantity),
'instrument': {'symbol': symbol, 'exchange': exchange, 'instrumentGroup': class_code}, 'user': {'portfolio': portfolio, 'exchange': exchange}, 'activate': activate}
return self.check_result(put(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/actions/stop/{order_id}', headers=headers, json=j))
def edit_stop_limit_order_v2(self, portfolio, exchange, order_id, symbol, class_code, side, quantity, stop_price, limit_price, condition='Less', seconds_order_end=0,
time_in_force='GoodTillCancelled', iceberg_fixed=None, iceberg_variance=None, activate=True):
"""Изменение стоп-лимитной заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param int order_id: Номер заявки
:param str symbol: Тикер
:param str class_code: Класс инструмента
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param float stop_price: Стоп цена
:param float limit_price: Лимитная цена
:param str condition: Условие срабатывания 'More', 'Less', 'MoreOrEqual', 'LessOrEqual'
:param int seconds_order_end: Дата и время UTC в секундах завершения сделки
:param str time_in_force: 'OneDay' - До конца дня, 'ImmediateOrCancel' - Снять остаток, 'FillOrKill' - Исполнить целиком или отклонить, 'GoodTillCancelled' - Активна до отмены
:param int iceberg_fixed: Видимая постоянная часть айсберг-заявки в лотах
:param int iceberg_variance: Амплитуда отклонения (в % от icebergFixed) случайной надбавки к видимой части айсберг-заявки. Только срочный рынок
:param bool activate: Флаг активной заявки
"""
headers = self.get_headers()
headers['X-ALOR-REQID'] = f'{portfolio};{self.get_request_id()}' # Портфель с уникальным идентификатором запроса
j = {'side': side, 'condition': condition, 'triggerPrice': stop_price, 'stopEndUnixTime': seconds_order_end, 'price': limit_price, 'quantity': abs(quantity),
'instrument': {'symbol': symbol, 'exchange': exchange, 'instrumentGroup': class_code}, 'user': {'portfolio': portfolio, 'exchange': exchange},
'timeInForce': time_in_force, 'activate': activate}
if iceberg_fixed:
j['icebergFixed'] = iceberg_fixed
if iceberg_variance:
j['icebergVariance'] = iceberg_variance
return self.check_result(put(url=f'{self.api_server}/commandapi/warptrans/TRADE/v2/client/orders/actions/stopLimit/{order_id}', headers=headers, json=j))
# OrdersWebSocket - Работа с заявками (WebSocket)
def authorize_websocket(self):
"""Авторизация"""
return self.send_websocket({'opcode': 'authorize', 'token': self.get_jwt_token()})
def create_market_order_websocket(self, portfolio, exchange, board, symbol, side, quantity, comment='', time_in_force='GoodTillCancelled', check_duplicates=True):
"""Создание рыночной заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str board: Режим торгов (борд). TQBR - акции, TQOB - облигации, RFUD - фьючерсы, ...
:param str symbol: Тикер
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param str comment: Пользовательский комментарий к заявке
:param str time_in_force: 'OneDay' - До конца дня, 'ImmediateOrCancel' - Снять остаток, 'FillOrKill' - Исполнить целиком или отклонить, 'GoodTillCancelled' - Активна до отмены
:param bool check_duplicates: Флаг, отвечающий за проверку уникальности команд
"""
request = {'opcode': 'create:market', 'side': side, 'quantity': abs(quantity), 'instrument': {'exchange': exchange, 'symbol': symbol},
'board': board, 'user': {'portfolio': portfolio}, 'comment': comment, 'timeInForce': time_in_force, 'checkDuplicates': check_duplicates}
return self.send_websocket(request)
def create_limit_order_websocket(self, portfolio, exchange, board, symbol, side, quantity, limit_price, comment='', time_in_force='GoodTillCancelled', iceberg_fixed=None, iceberg_variance=None, check_duplicates=True):
"""Создание лимитной заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str board: Режим торгов (борд). TQBR - акции, TQOB - облигации, RFUD - фьючерсы, ...
:param str symbol: Тикер
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param float limit_price: Лимитная цена
:param str comment: Пользовательский комментарий к заявке
:param str time_in_force: 'OneDay' - До конца дня, 'ImmediateOrCancel' - Снять остаток, 'FillOrKill' - Исполнить целиком или отклонить, 'GoodTillCancelled' - Активна до отмены
:param int iceberg_fixed: Видимая постоянная часть айсберг-заявки в лотах
:param int iceberg_variance: Амплитуда отклонения (в % от icebergFixed) случайной надбавки к видимой части айсберг-заявки. Только срочный рынок
:param bool check_duplicates: Флаг, отвечающий за проверку уникальности команд
"""
request = {'opcode': 'create:limit', 'side': side, 'quantity': abs(quantity), 'price': limit_price, 'instrument': {'exchange': exchange, 'symbol': symbol},
'board': board, 'user': {'portfolio': portfolio}, 'comment': comment, 'timeInForce': time_in_force, 'checkDuplicates': check_duplicates}
if iceberg_fixed:
request['icebergFixed'] = iceberg_fixed
if iceberg_variance:
request['icebergVariance'] = iceberg_variance
return self.send_websocket(request)
def create_stop_order_websocket(self, portfolio, exchange, symbol, board, side, quantity, stop_price, comment='', condition='Less', seconds_order_end=0, check_duplicates=True, activate=True):
"""Создание стоп-заявки
:param str portfolio: Идентификатор клиентского портфеля
:param str exchange: Биржа 'MOEX' или 'SPBX'
:param str symbol: Тикер
:param str board: Режим торгов (борд). TQBR - акции, TQOB - облигации, RFUD - фьючерсы, ...
:param str side: Покупка 'buy' или продажа 'sell'
:param int quantity: Кол-во в лотах
:param float stop_price: Стоп цена
:param str comment: Пользовательский комментарий к заявке
:param str condition: Условие срабатывания 'More', 'Less', 'MoreOrEqual', 'LessOrEqual'