fyers-apiv3 3.1.5__tar.gz → 3.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/PKG-INFO +2 -1
  2. fyers_apiv3-3.1.6/fyers_apiv3/FyersWebsocket/msg_pb2.py +50 -0
  3. fyers_apiv3-3.1.6/fyers_apiv3/FyersWebsocket/tbt_ws.py +594 -0
  4. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3/fyersModel.py +13 -0
  5. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3.egg-info/PKG-INFO +2 -1
  6. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3.egg-info/SOURCES.txt +3 -1
  7. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3.egg-info/requires.txt +1 -0
  8. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/setup.py +3 -2
  9. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/LICENSE.txt +0 -0
  10. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/README.md +0 -0
  11. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3/FyersWebsocket/__init__.py +0 -0
  12. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3/FyersWebsocket/data_ws.py +0 -0
  13. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3/FyersWebsocket/defines.py +0 -0
  14. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3/FyersWebsocket/map.json +0 -0
  15. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3/FyersWebsocket/order_ws.py +0 -0
  16. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3/__init__.py +0 -0
  17. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3/fyers_logger.py +0 -0
  18. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3.egg-info/dependency_links.txt +0 -0
  19. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/fyers_apiv3.egg-info/top_level.txt +0 -0
  20. {fyers_apiv3-3.1.5 → fyers_apiv3-3.1.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fyers_apiv3
3
- Version: 3.1.5
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
@@ -15,6 +15,7 @@ Requires-Dist: asyncio==3.4.3
15
15
  Requires-Dist: aiohttp==3.9.3
16
16
  Requires-Dist: aws_lambda_powertools==1.25.5
17
17
  Requires-Dist: websocket-client==1.6.1
18
+ Requires-Dist: protobuf==5.29.3
18
19
  Dynamic: author
19
20
  Dynamic: author-email
20
21
  Dynamic: classifier
@@ -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()
@@ -40,6 +40,7 @@ class Config:
40
40
  market_depth = "/depth"
41
41
  option_chain = "/options-chain-v3"
42
42
  multileg_orders = "/multileg/orders/sync"
43
+ logout = "/logout"
43
44
 
44
45
 
45
46
 
@@ -657,6 +658,18 @@ class FyersModel:
657
658
  response = self.service.get_call(Config.holdings, self.header)
658
659
  return response
659
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
+
660
673
  def get_orders(self, data) -> dict:
661
674
  """
662
675
  Retrieves order details by ID.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fyers_apiv3
3
- Version: 3.1.5
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
@@ -15,6 +15,7 @@ Requires-Dist: asyncio==3.4.3
15
15
  Requires-Dist: aiohttp==3.9.3
16
16
  Requires-Dist: aws_lambda_powertools==1.25.5
17
17
  Requires-Dist: websocket-client==1.6.1
18
+ Requires-Dist: protobuf==5.29.3
18
19
  Dynamic: author
19
20
  Dynamic: author-email
20
21
  Dynamic: classifier
@@ -13,4 +13,6 @@ fyers_apiv3/FyersWebsocket/__init__.py
13
13
  fyers_apiv3/FyersWebsocket/data_ws.py
14
14
  fyers_apiv3/FyersWebsocket/defines.py
15
15
  fyers_apiv3/FyersWebsocket/map.json
16
- fyers_apiv3/FyersWebsocket/order_ws.py
16
+ fyers_apiv3/FyersWebsocket/msg_pb2.py
17
+ fyers_apiv3/FyersWebsocket/order_ws.py
18
+ fyers_apiv3/FyersWebsocket/tbt_ws.py
@@ -3,3 +3,4 @@ asyncio==3.4.3
3
3
  aiohttp==3.9.3
4
4
  aws_lambda_powertools==1.25.5
5
5
  websocket-client==1.6.1
6
+ protobuf==5.29.3
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name='fyers_apiv3',
8
- version='3.1.5',
8
+ version='3.1.6',
9
9
  author="Fyers-Tech",
10
10
  author_email="support@fyers.in",
11
11
  description="Fyers trading APIs.",
@@ -14,7 +14,7 @@ setuptools.setup(
14
14
  url="https://github.com/FyersDev/fyers-api-sample-code/tree/sample_v3/v3/python",
15
15
  packages=setuptools.find_packages(),
16
16
  package_data={
17
- 'fyers_apiv3': ['/Users/em374/Documents/Git/fyers-api-py/fyers_apiv3/FyersWebsocket/map.json']
17
+ 'fyers_apiv3': ['/Users/em997/Documents/Fyers/Backend/sdks/fyers-api-py/fyers_apiv3/FyersWebsocket/map.json']
18
18
  },
19
19
  include_package_data=True,
20
20
  install_requires=[
@@ -23,6 +23,7 @@ setuptools.setup(
23
23
  'aiohttp==3.9.3',
24
24
  'aws_lambda_powertools==1.25.5',
25
25
  'websocket-client==1.6.1',
26
+ 'protobuf==5.29.3',
26
27
  ],
27
28
  classifiers=[
28
29
  "Programming Language :: Python :: 3",
File without changes
File without changes
File without changes