fyers-apiv3 3.1.4__py3-none-any.whl → 3.1.6__py3-none-any.whl

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.
@@ -0,0 +1,50 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: msg.proto
4
+ # Protobuf Python Version: 5.26.1
5
+ """Generated protocol buffer code."""
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import symbol_database as _symbol_database
9
+ from google.protobuf.internal import builder as _builder
10
+ # @@protoc_insertion_point(imports)
11
+
12
+ _sym_db = _symbol_database.Default()
13
+
14
+
15
+ from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2
16
+
17
+
18
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tmsg.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xbb\x01\n\x0bMarketLevel\x12*\n\x05price\x18\x01 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12)\n\x03qty\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12*\n\x04nord\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12)\n\x03num\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\"\x95\x01\n\x05\x44\x65pth\x12)\n\x03tbq\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12)\n\x03tsq\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x1a\n\x04\x61sks\x18\x03 \x03(\x0b\x32\x0c.MarketLevel\x12\x1a\n\x04\x62ids\x18\x04 \x03(\x0b\x32\x0c.MarketLevel\"\xb7\x02\n\x05Quote\x12(\n\x03ltp\x18\x01 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12)\n\x03ltt\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12)\n\x03ltq\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12)\n\x03vtt\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12.\n\x08vtt_diff\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12(\n\x02oi\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12)\n\x04ltpc\x18\x07 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\"\x88\x03\n\rExtendedQuote\x12(\n\x03\x61tp\x18\x01 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12\'\n\x02\x63p\x18\x02 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12(\n\x02lc\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12(\n\x02uc\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\'\n\x02yh\x18\x05 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12\'\n\x02yl\x18\x06 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12)\n\x03poi\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12)\n\x04oich\x18\x08 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12(\n\x02pc\x18\t \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\"\x88\x02\n\nDailyQuote\x12\'\n\x02\x64o\x18\x01 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12\'\n\x02\x64h\x18\x02 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12\'\n\x02\x64l\x18\x03 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12\'\n\x02\x64\x63\x18\x04 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12*\n\x04\x64hoi\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12*\n\x04\x64loi\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\"\x8e\x02\n\x05OHLCV\x12)\n\x04open\x18\x01 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12)\n\x04high\x18\x02 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12(\n\x03low\x18\x03 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12*\n\x05\x63lose\x18\x04 \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12,\n\x06volume\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12+\n\x05\x65poch\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\"\x1d\n\tSymDetail\x12\x10\n\x08ticksize\x18\x01 \x01(\t\"\xcd\x02\n\nMarketFeed\x12\x15\n\x05quote\x18\x01 \x01(\x0b\x32\x06.Quote\x12\x1a\n\x02\x65q\x18\x02 \x01(\x0b\x32\x0e.ExtendedQuote\x12\x17\n\x02\x64q\x18\x03 \x01(\x0b\x32\x0b.DailyQuote\x12\x15\n\x05ohlcv\x18\x04 \x01(\x0b\x32\x06.OHLCV\x12\x15\n\x05\x64\x65pth\x18\x05 \x01(\x0b\x32\x06.Depth\x12/\n\tfeed_time\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12/\n\tsend_time\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\r\n\x05token\x18\x08 \x01(\t\x12\x13\n\x0bsequence_no\x18\t \x01(\x04\x12\x10\n\x08snapshot\x18\n \x01(\x08\x12\x0e\n\x06ticker\x18\x0b \x01(\t\x12\x1d\n\tsymdetail\x18\x0c \x01(\x0b\x32\n.SymDetail\"\xbe\x01\n\rSocketMessage\x12\x1a\n\x04type\x18\x01 \x01(\x0e\x32\x0c.MessageType\x12(\n\x05\x66\x65\x65\x64s\x18\x02 \x03(\x0b\x32\x19.SocketMessage.FeedsEntry\x12\x10\n\x08snapshot\x18\x03 \x01(\x08\x12\x0b\n\x03msg\x18\x04 \x01(\t\x12\r\n\x05\x65rror\x18\x05 \x01(\x08\x1a\x39\n\nFeedsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1a\n\x05value\x18\x02 \x01(\x0b\x32\x0b.MarketFeed:\x02\x38\x01*\x86\x01\n\x0bMessageType\x12\x08\n\x04ping\x10\x00\x12\t\n\x05quote\x10\x01\x12\x12\n\x0e\x65xtended_quote\x10\x02\x12\x0f\n\x0b\x64\x61ily_quote\x10\x03\x12\x10\n\x0cmarket_level\x10\x04\x12\t\n\x05ohlcv\x10\x05\x12\t\n\x05\x64\x65pth\x10\x06\x12\x07\n\x03\x61ll\x10\x07\x12\x0c\n\x08response\x10\x08\x42\nZ\x08/gencodeb\x06proto3')
19
+
20
+ _globals = globals()
21
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
22
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'msg_pb2', _globals)
23
+ if not _descriptor._USE_C_DESCRIPTORS:
24
+ _globals['DESCRIPTOR']._loaded_options = None
25
+ _globals['DESCRIPTOR']._serialized_options = b'Z\010/gencode'
26
+ _globals['_SOCKETMESSAGE_FEEDSENTRY']._loaded_options = None
27
+ _globals['_SOCKETMESSAGE_FEEDSENTRY']._serialized_options = b'8\001'
28
+ _globals['_MESSAGETYPE']._serialized_start=2197
29
+ _globals['_MESSAGETYPE']._serialized_end=2331
30
+ _globals['_MARKETLEVEL']._serialized_start=46
31
+ _globals['_MARKETLEVEL']._serialized_end=233
32
+ _globals['_DEPTH']._serialized_start=236
33
+ _globals['_DEPTH']._serialized_end=385
34
+ _globals['_QUOTE']._serialized_start=388
35
+ _globals['_QUOTE']._serialized_end=699
36
+ _globals['_EXTENDEDQUOTE']._serialized_start=702
37
+ _globals['_EXTENDEDQUOTE']._serialized_end=1094
38
+ _globals['_DAILYQUOTE']._serialized_start=1097
39
+ _globals['_DAILYQUOTE']._serialized_end=1361
40
+ _globals['_OHLCV']._serialized_start=1364
41
+ _globals['_OHLCV']._serialized_end=1634
42
+ _globals['_SYMDETAIL']._serialized_start=1636
43
+ _globals['_SYMDETAIL']._serialized_end=1665
44
+ _globals['_MARKETFEED']._serialized_start=1668
45
+ _globals['_MARKETFEED']._serialized_end=2001
46
+ _globals['_SOCKETMESSAGE']._serialized_start=2004
47
+ _globals['_SOCKETMESSAGE']._serialized_end=2194
48
+ _globals['_SOCKETMESSAGE_FEEDSENTRY']._serialized_start=2137
49
+ _globals['_SOCKETMESSAGE_FEEDSENTRY']._serialized_end=2194
50
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,594 @@
1
+ from typing import Any, Callable, Dict, Optional
2
+ from pkg_resources import resource_filename
3
+ import websocket
4
+ from threading import Thread
5
+ import logging
6
+ import threading
7
+ import time
8
+ import json
9
+ from fyers_apiv3.FyersWebsocket import defines
10
+ from fyers_apiv3.fyers_logger import FyersLogger
11
+ from typing import Set, List
12
+ import fyers_apiv3.FyersWebsocket.msg_pb2 as protomsg
13
+ from enum import Enum
14
+ import requests
15
+
16
+ ## Models and definitions
17
+
18
+ def getUrl(access_token: str):
19
+ """
20
+ Get the URL for the WebSocket connection.
21
+
22
+ Args:
23
+ access_token (str): The access token to authenticate with. Format: APPID:SECRET_KEY
24
+
25
+ Returns:
26
+ str: The URL for the WebSocket connection.
27
+ """
28
+ data = requests.get('https://api-t1.fyers.in/indus/home/tbtws', headers={'Authorization': f'{access_token}'})
29
+ if data.status_code == 200:
30
+ return data.json()['data']['socket_url']
31
+ return "wss://rtsocket-api.fyers.in/versova"
32
+
33
+ class SubscriptionModes(Enum):
34
+ DEPTH = "depth"
35
+
36
+ class Depth:
37
+ def __init__(self):
38
+ self.tbq: int = 0
39
+ self.tsq: int = 0
40
+ self.bidprice: List[float] = [0.0] * 50
41
+ self.askprice: List[float] = [0.0] * 50
42
+ self.bidqty: List[float] = [0] * 50
43
+ self.askqty: List[float] = [0] * 50
44
+ self.bidordn: List[float] = [0] * 50
45
+ self.askordn: List[float] = [0] * 50
46
+ self.snapshot: bool = False
47
+ self.timestamp: int = 0
48
+ self.sendtime: int = 0
49
+ self.seqNo: int = 0
50
+
51
+ def __str__(self):
52
+ return (f"Depth{{ts: {self.timestamp}, "
53
+ f"send_ts: {self.sendtime}, "
54
+ f"tbq: {self.tbq}, tsq: {self.tsq}, "
55
+ f"bidprice: {self.bidprice}, askprice: {self.askprice}, "
56
+ f"bidqty: {self.bidqty}, askqty: {self.askqty}, "
57
+ f"bidordn: {self.bidordn}, askordn: {self.askordn}, "
58
+ f"snapshot: {self.snapshot}, sNo: {self.seqNo} }}")
59
+
60
+ def _addDepth(self, currdata: protomsg.MarketFeed, isSnapshot: bool):
61
+ if currdata.HasField('depth'):
62
+ self.snapshot = isSnapshot
63
+ if currdata.depth.HasField('tbq'):
64
+ self.tbq = currdata.depth.tbq.value
65
+
66
+ if currdata.depth.HasField('tsq'):
67
+ self.tsq = currdata.depth.tsq.value
68
+
69
+ if currdata.depth.asks is not None:
70
+ for i in range(len(currdata.depth.asks)):
71
+ if currdata.depth.asks[i].HasField('price'):
72
+ self.askprice[i] = currdata.depth.asks[i].price.value / 100
73
+
74
+ if currdata.depth.asks[i].HasField('qty'):
75
+ self.askqty[i] = currdata.depth.asks[i].qty.value
76
+
77
+ if currdata.depth.asks[i].HasField('nord'):
78
+ self.askordn[i] = currdata.depth.asks[i].nord.value
79
+
80
+ if currdata.depth.bids is not None:
81
+ for i in range(len(currdata.depth.bids)):
82
+ if currdata.depth.bids[i].HasField('price'):
83
+ self.bidprice[i] = currdata.depth.bids[i].price.value / 100
84
+
85
+ if currdata.depth.bids[i].HasField('qty'):
86
+ self.bidqty[i] = currdata.depth.bids[i].qty.value
87
+
88
+ if currdata.depth.bids[i].HasField('nord'):
89
+ self.bidordn[i] = currdata.depth.bids[i].nord.value
90
+
91
+ self.timestamp = currdata.feed_time.value
92
+ self.sendtime = currdata.send_time.value
93
+ self.seqNo = currdata.sequence_no
94
+
95
+ class SubscriptionInfo:
96
+ def __init__(self) -> None:
97
+ self._symbols: Dict[str, Set[str]] = {}
98
+ self._modeInfo: Dict[str, SubscriptionModes] = {}
99
+ self._activeChannels: Set[str] = set()
100
+
101
+ def subscribe(self, symbols: Set[str], channelNo: str, mode: SubscriptionModes) -> None:
102
+ if channelNo in self._symbols:
103
+ self._symbols[channelNo].update(symbols)
104
+ else:
105
+ self._symbols[channelNo] = set(symbols)
106
+ self._modeInfo[channelNo] = mode
107
+
108
+ def unsubscribe(self, symbols: Set[str], channelNo: str) -> None:
109
+ if channelNo in self._symbols:
110
+ self._symbols[channelNo].difference_update(symbols)
111
+ if not self._symbols[channelNo]:
112
+ del self._symbols[channelNo]
113
+
114
+ def updateChannels(self, pauseChannels: Set[str], resumeChannels: Set[str]) -> None:
115
+ self._activeChannels.difference_update(pauseChannels)
116
+ self._activeChannels.update(resumeChannels)
117
+
118
+ def updateMode(self, modeConfig: Dict[str, SubscriptionModes]) -> None:
119
+ for channelNo, mode in modeConfig.items():
120
+ self._modeInfo[channelNo] = mode
121
+
122
+ def getSymbolsInfo(self, chanNo: str) -> Set[str]:
123
+ return self._symbols[chanNo]
124
+
125
+ def getModeInfo(self, chanNo: str) -> SubscriptionModes:
126
+ return self._modeInfo[chanNo]
127
+
128
+ def getChannelInfo(self) -> Set[str]:
129
+ return self._activeChannels
130
+
131
+
132
+ class DataStore:
133
+ depth: Dict[str, Depth] = {}
134
+
135
+ def updateDepth(self, packet: protomsg.SocketMessage, cb: Optional[Callable], diffOnly: bool):
136
+ if packet.feeds is not None:
137
+ for _, value in packet.feeds.items():
138
+ symbol = value.ticker
139
+ if symbol not in self.depth:
140
+ self.depth[symbol] = Depth()
141
+ if not diffOnly:
142
+ self.depth[symbol]._addDepth(value, packet.snapshot)
143
+ cb(symbol, self.depth[symbol])
144
+ else:
145
+ depth = Depth()
146
+ depth._addDepth(value, packet.snapshot)
147
+ cb(symbol, depth)
148
+
149
+
150
+ class FyersTbtSocket:
151
+
152
+ _instance = None
153
+
154
+ def __new__(cls, *args, **kwargs):
155
+ if not cls._instance:
156
+ cls._instance = super().__new__(cls)
157
+ return cls._instance
158
+
159
+ def __init__(
160
+ self,
161
+ access_token: str,
162
+ write_to_file: Optional[bool] = False,
163
+ log_path: Optional[str] = None,
164
+ on_depth_update: Optional[Callable] = None,
165
+ on_error_message: Optional[Callable] = None,
166
+ on_error: Optional[Callable] = None,
167
+ on_connect: Optional[Callable] = None,
168
+ on_close: Optional[Callable] = None,
169
+ on_open: Optional[Callable] = None,
170
+ reconnect : Optional[Callable] = True,
171
+ diff_only: bool = False,
172
+ reconnect_retry: int = 5
173
+ ) -> None:
174
+ """
175
+ Initializes the class instance.
176
+
177
+ Args:
178
+ access_token (str): The access token to authenticate with.
179
+ write_to_file (bool, optional): Flag indicating whether to save data to a file. Defaults to False.
180
+ log_path (str, optional): The path to the log file. Defaults to None.
181
+ on_depth_update (callable, optional): Callback function for 50 depth events. Defaults to None.
182
+ on_error_message (callable, optional): Callback function for error msg received from server. Defaults to None.
183
+ on_error (callable, optional): Callback function for error events. Defaults to None.
184
+ on_connect (callable, optional): Callback function for connect events. Defaults to None.
185
+ on_close (callable, optional): Callback function for close events. Defaults to None.
186
+ on_open (callable, optional): Callback function for open events. Defaults to None.
187
+ reconnect (bool, optional): Flag indicating whether to attempt reconnection on disconnection. Defaults to True.
188
+ """
189
+ self._datastore = DataStore()
190
+ self._subsinfo = SubscriptionInfo()
191
+ self.__access_token = access_token
192
+ self.log_path = log_path
193
+ self.__ws_object = None
194
+ self.__ws_run = False
195
+ self.ping_thread = None
196
+ self.write_to_file = write_to_file
197
+ self.background_flag = False
198
+ self.reconnect_delay = 0
199
+ self.onDepthUpdate = on_depth_update
200
+ self.onErrorMsg = on_error_message
201
+ self.restart_flag = reconnect
202
+ self.onerror = on_error
203
+ self.onopen = on_connect
204
+ self.max_reconnect_attempts = 50
205
+ self.reconnect_attempts = 0
206
+ self.diff_only = diff_only
207
+ if reconnect_retry < self.max_reconnect_attempts:
208
+ self.max_reconnect_attempts = reconnect_retry
209
+
210
+ self.onclose = on_close
211
+ self.onopen = on_open
212
+ self.__ws_object = None
213
+ self.running_thread=None
214
+ self.__url = getUrl(access_token)
215
+
216
+ if log_path:
217
+ self.tbtlogger = FyersLogger(
218
+ "FyersTbtSocket",
219
+ "DEBUG",
220
+ stack_level=2,
221
+ logger_handler=logging.FileHandler(log_path + "/fyersTBTSocket.log"),
222
+ )
223
+ else:
224
+ self.tbtlogger = FyersLogger(
225
+ "FyersTbtSocket",
226
+ "DEBUG",
227
+ stack_level=2,
228
+ logger_handler=logging.FileHandler("fyersTBTSocket.log"),
229
+ )
230
+ self.websocket_task = None
231
+
232
+ self.write_to_file = write_to_file
233
+ self.background_flag = False
234
+
235
+ def subscribe(self, symbol_tickers: Set[str], channelNo: str, mode: SubscriptionModes) -> None:
236
+ """
237
+ Subscribe to a specific channel with the given symbols and mode.
238
+
239
+ Args:
240
+ symbol_tickers (Set[str]): The set of symbol tickers to subscribe to.
241
+ channelNo (str): The channel number to subscribe to. Should be between 1 and 50
242
+ mode (SubscriptionModes): The mode of subscription.
243
+ """
244
+ if (
245
+ self.__ws_object is not None
246
+ and self.__ws_object.sock
247
+ and self.__ws_object.sock.connected
248
+ ):
249
+ self._subsinfo.subscribe(symbol_tickers, channelNo, mode)
250
+ self.__ws_object.send(
251
+ json.dumps(
252
+ {
253
+ "type": 1,
254
+ "data": {
255
+ "subs": 1,
256
+ "symbols": list(symbol_tickers),
257
+ "mode": mode.value,
258
+ "channel": channelNo,
259
+ },
260
+ }
261
+ )
262
+ )
263
+
264
+ def unsubscribe(self, symbol_tickers: Set[str], channelNo: str, mode: SubscriptionModes) -> None:
265
+ """
266
+ Unsubscribe from a specific channel with the given symbols and mode.
267
+
268
+ Args:
269
+ symbol_tickers (Set[str]): The set of symbol tickers to unsubscribe from.
270
+ channelNo (str): The channel number to unsubscribe from. Should be between 1 and 50
271
+ mode (SubscriptionModes): The mode of subscription.
272
+ """
273
+ if (
274
+ self.__ws_object is not None
275
+ and self.__ws_object.sock
276
+ and self.__ws_object.sock.connected
277
+ ):
278
+ self._subsinfo.unsubscribe(symbol_tickers, channelNo)
279
+ self.__ws_object.send(
280
+ json.dumps(
281
+ {
282
+ "type": 1,
283
+ "data": {
284
+ "subs": -1,
285
+ "symbols": list(symbol_tickers),
286
+ "mode": mode.value,
287
+ "channel": channelNo,
288
+ },
289
+ }
290
+ )
291
+ )
292
+
293
+ def switchChannel(self, resume_channels: Set[str], pause_channels: Set[str]) -> None:
294
+ """
295
+ Resume and pause channels to receive data from the server.
296
+
297
+ Args:
298
+ resume_channels (Set[str]): The set of channels to resume. Data will be received for symbols on these channels.
299
+ pause_channels (Set[str]): The set of channels to pause. Data will be paused for symbols on these channels.
300
+ """
301
+ if (
302
+ self.__ws_object is not None
303
+ and self.__ws_object.sock
304
+ and self.__ws_object.sock.connected
305
+ ):
306
+ self._subsinfo.updateChannels(pause_channels, resume_channels)
307
+ self.__ws_object.send(
308
+ json.dumps(
309
+ {
310
+ "type": 2,
311
+ "data": {
312
+ "resumeChannels": list(resume_channels),
313
+ "pauseChannels": list(pause_channels)
314
+ }
315
+ }
316
+ )
317
+ )
318
+
319
+ def on_depth_update(self, ticker: str, message: Depth):
320
+ """
321
+ Callback function for depth update events.
322
+
323
+ Args:
324
+ ticker (str): The ticker symbol.
325
+ message (Depth): The depth message.
326
+ """
327
+ try:
328
+ if self.onDepthUpdate is not None:
329
+ self.onDepthUpdate(ticker, message)
330
+ else:
331
+ if self.write_to_file:
332
+ self.tbtlogger.debug(f"{ticker}: {message}")
333
+ else:
334
+ print(f"{ticker}: {message}")
335
+
336
+ except Exception as e:
337
+ self.tbtlogger.error(e)
338
+ self.On_error(e)
339
+
340
+ def on_error_message(self, message: str):
341
+ """
342
+ Callback function for error message events from the server
343
+
344
+ Args:
345
+ message (str): The error message.
346
+ """
347
+ try:
348
+ if self.onErrorMsg is not None:
349
+ self.onErrorMsg(message)
350
+ else:
351
+ print(f"error received from server: {message}")
352
+ except Exception as e:
353
+ self.tbtlogger.error(e)
354
+ self.On_error(e)
355
+
356
+ def __on_message(self, message: Dict[str, Any]):
357
+ """
358
+ Parses the response data based on its content.
359
+
360
+ Args:
361
+ message (str): The response message to be parsed.
362
+
363
+ Returns:
364
+ Any: The parsed response data.
365
+ """
366
+ try:
367
+ if message != "pong":
368
+ d = protomsg.SocketMessage()
369
+ d.ParseFromString(message)
370
+ if d.error:
371
+ self.on_error_message(d.msg)
372
+ else:
373
+ self._datastore.updateDepth(d, self.on_depth_update, self.diff_only)
374
+
375
+
376
+ except Exception as e:
377
+ self.tbtlogger.error(e)
378
+ self.On_error(e)
379
+
380
+ def On_error(self, message: str) -> None:
381
+ """
382
+ Callback function for handling error events.
383
+
384
+ Args:
385
+ message (str): The error message.
386
+
387
+ """
388
+ if self.onerror is not None:
389
+ self.onerror(message)
390
+ self.tbtlogger.error(message)
391
+ else:
392
+ if self.write_to_file:
393
+ self.tbtlogger.debug(f"Response:{message}")
394
+ else:
395
+ print(f"Error Response : {message}")
396
+
397
+ def __on_open(self, ws):
398
+ """
399
+ Callback function for open events from the server
400
+
401
+ Args:
402
+ ws (WebSocket): The WebSocket object.
403
+ """
404
+ try:
405
+ if self.__ws_object is None:
406
+ self.__ws_object = ws
407
+ self.ping_thread = threading.Thread(target=self.__ping)
408
+ self.ping_thread.start()
409
+ self.reconnect_attempts = 0
410
+ self.reconnect_delay = 0
411
+ self.on_open()
412
+
413
+ except Exception as e:
414
+ self.tbtlogger.error(e)
415
+ self.On_error(e)
416
+
417
+ def __on_close(self, ws, close_code=None, close_reason=None):
418
+ """
419
+ Handle the WebSocket connection close event.
420
+
421
+ Args:
422
+ ws (WebSocket): The WebSocket object.
423
+ close_code (int): The code indicating the reason for closure.
424
+ close_reason (str): The reason for closure.
425
+
426
+ Returns:
427
+ dict: A dictionary containing the response code, message, and s.
428
+ """
429
+ try:
430
+ if self.restart_flag:
431
+ if self.reconnect_attempts < self.max_reconnect_attempts:
432
+ if self.write_to_file:
433
+ self.tbtlogger.debug(
434
+ f"Response:{f'Attempting reconnect {self.reconnect_attempts} of {self.max_reconnect_attempts}...'}"
435
+ )
436
+ else:
437
+ print(
438
+ f"Attempting reconnect {self.reconnect_attempts+1} of {self.max_reconnect_attempts}..."
439
+ )
440
+ if (self.reconnect_attempts) % 5 == 0:
441
+ self.reconnect_delay += 5
442
+ time.sleep(self.reconnect_delay)
443
+ self.reconnect_attempts += 1
444
+
445
+ self.__ws_object = None
446
+ self.connect()
447
+ else:
448
+ if self.write_to_file:
449
+ self.tbtlogger.debug(
450
+ f"Response:{'Max reconnect attempts reached. Connection abandoned.'}"
451
+ )
452
+ else:
453
+ print("Max reconnect attempts reached. Connection abandoned.")
454
+ else:
455
+
456
+ self.on_close(
457
+ {
458
+ "code": defines.SUCCESS_CODE,
459
+ "message": defines.CONNECTION_CLOSED,
460
+ "s": defines.SUCCESS,
461
+ }
462
+ )
463
+ except Exception as e:
464
+ self.tbtlogger.error(e)
465
+ self.On_error(e)
466
+
467
+ def __ping(self) -> None:
468
+ """
469
+ Sends periodic ping messages to the server to maintain the WebSocket connection.
470
+
471
+ The method continuously sends "__ping" messages to the server at a regular interval
472
+ as long as the WebSocket connection is active.
473
+
474
+ """
475
+
476
+ while (
477
+ self.__ws_object is not None
478
+ and self.__ws_object.sock
479
+ and self.__ws_object.sock.connected
480
+ ):
481
+ self.__ws_object.send("ping")
482
+ time.sleep(10)
483
+
484
+ def on_close(self, message: dict) -> None:
485
+ """
486
+ Handles the close event.
487
+
488
+ Args:
489
+ message (dict): The close message .
490
+ """
491
+
492
+ if self.onclose:
493
+ self.onclose(message)
494
+ else:
495
+ print(f"Response: {message}")
496
+
497
+ def on_open(self) -> None:
498
+ """
499
+ Performs initialization and waits before executing further actions.
500
+ """
501
+ try:
502
+ if self.onopen:
503
+ self.onopen()
504
+ open_chans = self._subsinfo.getChannelInfo()
505
+ self.switchChannel(self._subsinfo.getChannelInfo(), Set())
506
+ for channel in open_chans:
507
+ self.subscribe(self._subsinfo.getSymbolsInfo(channel), channel, self._subsinfo.getModeInfo(channel))
508
+ except Exception as e:
509
+ self.On_error(e)
510
+
511
+ def is_connected(self):
512
+ """
513
+ Check if the websocket is connected.
514
+
515
+ Returns:
516
+ bool: True if the websocket is connected, False otherwise.
517
+ """
518
+ if self.__ws_object:
519
+ return True
520
+ else:
521
+ return False
522
+
523
+
524
+ def __init_connection(self):
525
+ """
526
+ Initializes the WebSocket connection and starts the WebSocketApp.
527
+
528
+ The method creates a WebSocketApp object with the specified URL and sets the appropriate event handlers.
529
+ It then starts the WebSocketApp in a separate thread.
530
+ """
531
+ try:
532
+ if self.__ws_object is None:
533
+ if self.write_to_file:
534
+ self.background_flag = False
535
+ header = {"authorization": self.__access_token}
536
+ ws = websocket.WebSocketApp(
537
+ self.__url,
538
+ header=header,
539
+ on_message=lambda ws, msg: self.__on_message(msg),
540
+ on_error=lambda ws, msg: self.On_error(msg),
541
+ on_close=lambda ws, close_code, close_reason: self.__on_close(
542
+ ws, close_code, close_reason
543
+ ),
544
+ on_open=lambda ws: self.__on_open(ws),
545
+ )
546
+ self.t = Thread(target=ws.run_forever)
547
+ self.t.daemon = self.background_flag
548
+ self.t.start()
549
+
550
+ except Exception as e:
551
+ self.tbtlogger.error(e)
552
+
553
+ def keep_running(self):
554
+ """
555
+ Starts an infinite loop to keep the program running.
556
+
557
+ """
558
+ self.__ws_run = True
559
+ self.running_thread = Thread(target=self.infinite_loop)
560
+ self.running_thread.start()
561
+
562
+ def stop_running(self):
563
+ self.__ws_run = False
564
+
565
+ def infinite_loop(self):
566
+ while self.__ws_run:
567
+ time.sleep(0.5)
568
+
569
+ def connect(self) -> None:
570
+ """
571
+ Establishes a connection to the WebSocket.
572
+
573
+ If the WebSocket object is not already initialized, this method will create the
574
+ WebSocket connection.
575
+
576
+ """
577
+ if self.__ws_object is None:
578
+ self.__init_connection()
579
+ time.sleep(2)
580
+
581
+
582
+ def close_connection(self):
583
+ """
584
+ Closes the WebSocket connection
585
+
586
+ """
587
+ if self.__ws_object is not None:
588
+ self.restart_flag = False
589
+ self.__ws_object.close()
590
+ self.__ws_object = None
591
+ self.__ws_run = None
592
+ self.running_thread.join()
593
+ self.t.join()
594
+ self.ping_thread.join()
fyers_apiv3/fyersModel.py CHANGED
@@ -26,7 +26,9 @@ class Config:
26
26
  convert_position = "/positions"
27
27
  funds = "/funds"
28
28
  orders_endpoint = "/orders/sync"
29
+ gtt_orders_sync = "/gtt/orders/sync"
29
30
  orderbook = "/orders"
31
+ gtt_orders = "/gtt/orders"
30
32
  market_status = "/marketStatus"
31
33
  auth = "/generate-authcode"
32
34
  generate_access_token = "/validate-authcode"
@@ -38,6 +40,7 @@ class Config:
38
40
  market_depth = "/depth"
39
41
  option_chain = "/options-chain-v3"
40
42
  multileg_orders = "/multileg/orders/sync"
43
+ logout = "/logout"
41
44
 
42
45
 
43
46
 
@@ -655,6 +658,18 @@ class FyersModel:
655
658
  response = self.service.get_call(Config.holdings, self.header)
656
659
  return response
657
660
 
661
+ def logout(self) -> dict:
662
+ """
663
+ Invalidates the access token.
664
+
665
+ """
666
+ if self.is_async:
667
+ response = self.service.post_async_call(Config.logout, self.header)
668
+
669
+ else:
670
+ response = self.service.post_call(Config.logout, self.header)
671
+ return response
672
+
658
673
  def get_orders(self, data) -> dict:
659
674
  """
660
675
  Retrieves order details by ID.
@@ -688,7 +703,21 @@ class FyersModel:
688
703
  else:
689
704
  response = self.service.get_call(Config.orderbook, self.header, data)
690
705
  return response
706
+
707
+ def gtt_orderbook(self, data = None) -> dict:
708
+ """
709
+ Retrieves the gtt order information.
691
710
 
711
+ Returns:
712
+ The response JSON as a dictionary.
713
+ """
714
+ if self.is_async:
715
+ response = self.service.get_async_call(Config.gtt_orders, self.header, data)
716
+
717
+ else:
718
+ response = self.service.get_call(Config.gtt_orders, self.header, data)
719
+ return response
720
+
692
721
  def market_status(self) -> dict:
693
722
  """
694
723
  Retrieves market status.
@@ -749,6 +778,23 @@ class FyersModel:
749
778
  else:
750
779
  response = self.service.delete_call(Config.orders_endpoint, self.header, data)
751
780
  return response
781
+
782
+ def cancel_gtt_order(self,data) -> dict:
783
+ """
784
+ Cancel order.
785
+
786
+ Args:
787
+ id (str): Unique identifier for the order to be cancelled, e.g., "25010700000001".
788
+
789
+ Returns:
790
+ The response JSON as a dictionary.
791
+ """
792
+
793
+ if self.is_async:
794
+ response = self.service.delete_async_call(Config.gtt_orders_sync, self.header, data)
795
+ else:
796
+ response = self.service.delete_call(Config.gtt_orders_sync, self.header, data)
797
+ return response
752
798
 
753
799
  def place_order(self, data) -> dict:
754
800
  """
@@ -777,6 +823,35 @@ class FyersModel:
777
823
  else:
778
824
  response = self.service.post_call(Config.orders_endpoint, self.header, data)
779
825
  return response
826
+
827
+ def place_gtt_order(self,data) -> dict:
828
+ """
829
+ Places an order based on the provided data.
830
+
831
+ Args:
832
+ data (dict): A dictionary containing the order details.
833
+ - 'id*' (str): Unique identifier for the order to be modified, e.g., "25010700000001".
834
+ - 'side' (int): Indicates the side of the order: 1 for buy, -1 for sell.
835
+ - 'symbol' (str): The instrument's unique identifier, e.g., "NSE:CHOLAFIN-EQ"
836
+ - 'productType*' (str): The product type for the order. Valid values: "CNC", "MARGIN", "MTF".
837
+ - 'orderInfo*' (object): Contains information about the GTT/OCO order legs.
838
+ - 'orderInfo.leg1*' (object): Details for GTT order leg. Mandatory for all orders.
839
+ - 'orderInfo.leg1.price*' (number): Price at which the order.
840
+ - 'orderInfo.leg1.triggerPrice' (number): Trigger price for the GTT order. NOTE: for OCO order this leg trigger price should be always above LTP
841
+ - 'orderInfo.leg1.qty*' (int): Quantity for the GTT order leg.
842
+ - 'orderInfo.leg2*' (object): Details for OCO order leg. Optional and included only for OCO orders.
843
+ - 'orderInfo.leg2.price*' (number): Price at which the second leg of the OCO order should be placed.
844
+ - 'orderInfo.leg2.triggerPrice*' (number): Trigger price for the second leg of the OCO order.NOTE: for OCO order this leg trigger price should be always below LTP
845
+ - 'orderInfo.leg2.qty*' (integer): Quantity for the second leg of the OCO order.
846
+
847
+ Returns:
848
+ The response JSON as a dictionary.
849
+ """
850
+ if self.is_async:
851
+ response = self.service.post_async_call(Config.gtt_orders_sync, self.header, data)
852
+ else:
853
+ response = self.service.post_call(Config.gtt_orders_sync, self.header, data)
854
+ return response
780
855
 
781
856
  def modify_order(self, data) -> dict:
782
857
  """
@@ -798,6 +873,30 @@ class FyersModel:
798
873
  else:
799
874
  response = self.service.patch_call(Config.orders_endpoint, self.header, data)
800
875
  return response
876
+
877
+ def modify_gtt_order(self,data) -> dict:
878
+ """
879
+ Modifies the parameters of a pending order based on the provided details.
880
+
881
+ Parameters:
882
+ id (str): Unique identifier for the order to be modified, e.g., "25010700000001"
883
+ orderInfo* (object): Contains updated information about the GTT/OCO order legs.
884
+ orderInfo.leg1* (object): Details for GTT order leg. Mandatory for all modifications.
885
+ orderInfo.leg1.price* (number): Updated price at which the order should be placed.
886
+ orderInfo.leg1.triggerPrice* (number): Updated trigger price for the GTT order. NOTE: for OCO order this leg trigger price should be always above LTP.
887
+ orderInfo.leg1.qty** (integer): Updated quantity for the GTT order leg.
888
+ orderInfo.leg2* (object): Details for OCO order leg. Required if the order is an OCO type.
889
+ orderInfo.leg2.triggerPrice* (number): Updated trigger price for the second leg of the OCO order.NOTE: for OCO order this leg trigger price should be always below LTP.
890
+ orderInfo.leg2.qty* (integer): Updated quantity for the second leg of the OCO order.
891
+
892
+ Returns:
893
+ The response JSON as a dictionary.
894
+ """
895
+ if self.is_async:
896
+ response = self.service.patch_async_call(Config.gtt_orders_sync, self.header, data)
897
+ else:
898
+ response = self.service.patch_call(Config.gtt_orders_sync, self.header, data)
899
+ return response
801
900
 
802
901
  def exit_positions(self, data={}) -> dict:
803
902
  """
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: fyers_apiv3
3
- Version: 3.1.4
3
+ Version: 3.1.6
4
4
  Summary: Fyers trading APIs.
5
5
  Home-page: https://github.com/FyersDev/fyers-api-sample-code/tree/sample_v3/v3/python
6
6
  Author: Fyers-Tech
@@ -10,11 +10,20 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE.txt
13
- Requires-Dist: requests ==2.31.0
14
- Requires-Dist: asyncio ==3.4.3
15
- Requires-Dist: aiohttp ==3.9.3
16
- Requires-Dist: aws-lambda-powertools ==1.25.5
17
- Requires-Dist: websocket-client ==1.6.1
13
+ Requires-Dist: requests==2.31.0
14
+ Requires-Dist: asyncio==3.4.3
15
+ Requires-Dist: aiohttp==3.9.3
16
+ Requires-Dist: aws_lambda_powertools==1.25.5
17
+ Requires-Dist: websocket-client==1.6.1
18
+ Requires-Dist: protobuf==5.29.3
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: classifier
22
+ Dynamic: description
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: requires-dist
26
+ Dynamic: summary
18
27
 
19
28
  # The Fyers API Python client - v3
20
29
 
@@ -1,13 +1,15 @@
1
1
  fyers_apiv3/__init__.py,sha256=1vA_F8dAtLKcyOqrmdUOkw1Syl8gIFUtxDoLde8y94E,18
2
- fyers_apiv3/fyersModel.py,sha256=yfNp2vHexQtF0YRIv8bVN_fab864HLiYbT3oyc1TXpU,40332
2
+ fyers_apiv3/fyersModel.py,sha256=MHo1NWgphuZBUx-t1VAnqsuLZZS_-9P5xwWOBMIwj_U,45096
3
3
  fyers_apiv3/fyers_logger.py,sha256=S_WiIwBLytQ_tyHd9bUC8gZo7GHpANCJO0CS6rCJE90,3795
4
4
  fyers_apiv3/FyersWebsocket/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  fyers_apiv3/FyersWebsocket/data_ws.py,sha256=fSNfsUB_I5-MkWX4hQZvklRQhuQ9n3w5hMDRFrMaBE8,68105
6
6
  fyers_apiv3/FyersWebsocket/defines.py,sha256=mhKkkQ6FZHFa6e0_83cRYMOucmSn5yHtcod1Jn2ygCc,1143
7
7
  fyers_apiv3/FyersWebsocket/map.json,sha256=GfAgk-zqzUki5Cu0ZlG08PiMhfKTyGIsPo62mIYotZ4,10075
8
+ fyers_apiv3/FyersWebsocket/msg_pb2.py,sha256=Po6emBFB6aCmt3rkuN6zjDA7JEzsixejfNSOQYtYgnE,6272
8
9
  fyers_apiv3/FyersWebsocket/order_ws.py,sha256=npLBtCcpPxclc5YRaVtLvBkRDCF3oV9NabK_y5EwoAk,16998
9
- fyers_apiv3-3.1.4.dist-info/LICENSE.txt,sha256=_a5I4lWvSmoZQxwGSPGVVvUbuYby780N9YevsBqNY3k,1063
10
- fyers_apiv3-3.1.4.dist-info/METADATA,sha256=UaKAmI6CAumPJnAHZiK9BIAjdnzuGcdtWjQstodgz78,16279
11
- fyers_apiv3-3.1.4.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
12
- fyers_apiv3-3.1.4.dist-info/top_level.txt,sha256=IaT774gXqIM6uJpgCQPvXruJBOINsupO9oTe2ao6pkc,12
13
- fyers_apiv3-3.1.4.dist-info/RECORD,,
10
+ fyers_apiv3/FyersWebsocket/tbt_ws.py,sha256=-UUO9LD3FVjre23m7SfVpJ44HcMBACPAnSUyWIS3yPA,21191
11
+ fyers_apiv3-3.1.6.dist-info/LICENSE.txt,sha256=_a5I4lWvSmoZQxwGSPGVVvUbuYby780N9YevsBqNY3k,1063
12
+ fyers_apiv3-3.1.6.dist-info/METADATA,sha256=vLA3LkYMTBqA-oMR26HEPEBFxmQdU04_zqg7F8nVKnE,16478
13
+ fyers_apiv3-3.1.6.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
14
+ fyers_apiv3-3.1.6.dist-info/top_level.txt,sha256=IaT774gXqIM6uJpgCQPvXruJBOINsupO9oTe2ao6pkc,12
15
+ fyers_apiv3-3.1.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5