tradingapi 0.3.9__tar.gz → 0.3.11__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 (36) hide show
  1. {tradingapi-0.3.9 → tradingapi-0.3.11}/PKG-INFO +1 -1
  2. {tradingapi-0.3.9 → tradingapi-0.3.11}/pyproject.toml +1 -1
  3. tradingapi-0.3.11/tests/test_fivepaisa_stream_reconnect.py +135 -0
  4. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/broker_base.py +7 -0
  5. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/config/config_sample.yaml +7 -1
  6. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/dhan.py +28 -7
  7. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/fivepaisa.py +204 -74
  8. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/flattrade.py +19 -2
  9. tradingapi-0.3.11/tradingapi/ib.py +864 -0
  10. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/icicidirect.py +10 -3
  11. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/market_data_exchanges.py +1 -0
  12. tradingapi-0.3.11/tradingapi/proxy_utils.py +294 -0
  13. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/shoonya.py +17 -2
  14. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/utils.py +35 -1
  15. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/PKG-INFO +1 -1
  16. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/SOURCES.txt +2 -0
  17. tradingapi-0.3.9/tradingapi/proxy_utils.py +0 -130
  18. {tradingapi-0.3.9 → tradingapi-0.3.11}/README.md +0 -0
  19. {tradingapi-0.3.9 → tradingapi-0.3.11}/setup.cfg +0 -0
  20. {tradingapi-0.3.9 → tradingapi-0.3.11}/tests/test_broker_side_terminal_order.py +0 -0
  21. {tradingapi-0.3.9 → tradingapi-0.3.11}/tests/test_calculate_delta_realtime_quotes.py +0 -0
  22. {tradingapi-0.3.9 → tradingapi-0.3.11}/tests/test_find_option_with_delta.py +0 -0
  23. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/__init__.py +0 -0
  24. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/allocation.py +0 -0
  25. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/attribution.py +0 -0
  26. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/config/commissions_20241216.yaml +0 -0
  27. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/config.py +0 -0
  28. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/error_handling.py +0 -0
  29. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/exceptions.py +0 -0
  30. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/globals.py +0 -0
  31. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/icicidirect_generate_session.py +0 -0
  32. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/span.py +0 -0
  33. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/dependency_links.txt +0 -0
  34. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/entry_points.txt +0 -0
  35. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/requires.txt +0 -0
  36. {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tradingapi
3
- Version: 0.3.9
3
+ Version: 0.3.11
4
4
  Summary: Trade integration with brokers
5
5
  Author-email: Pankaj Sharma <sharma.pankaj.kumar@gmail.com>
6
6
  License-Expression: MIT
@@ -28,7 +28,7 @@ packages = ["tradingapi"]
28
28
 
29
29
  [project]
30
30
  name = "tradingapi"
31
- version = "0.3.9"
31
+ version = "0.3.11"
32
32
  description = "Trade integration with brokers"
33
33
  readme = "README.md"
34
34
  license = "MIT"
@@ -0,0 +1,135 @@
1
+ import json
2
+ import sys
3
+ import threading
4
+ import time
5
+ from unittest.mock import patch
6
+
7
+ sys.path.insert(0, "/home/psharma/onedrive/code/tradingapi2")
8
+
9
+ from tradingapi.broker_base import Brokers
10
+ from tradingapi.fivepaisa import FivePaisa
11
+ from tradingapi import fivepaisa as fivepaisa_module
12
+
13
+
14
+ class _SocketState:
15
+ connected = True
16
+
17
+
18
+ class _WebSocket:
19
+ def __init__(self, sent_requests):
20
+ self.sock = _SocketState()
21
+ self.on_open = None
22
+ self.sent_requests = sent_requests
23
+
24
+ def send(self, payload):
25
+ self.sent_requests.append(json.loads(payload))
26
+
27
+
28
+ class _DelayedStreamingApi:
29
+ def __init__(self, connect_started, allow_connect, stop_stream):
30
+ self.ws = None
31
+ self.connect_started = connect_started
32
+ self.allow_connect = allow_connect
33
+ self.stop_stream = stop_stream
34
+ self.connect_calls = 0
35
+ self.sent_requests = []
36
+
37
+ def Request_Feed(self, feed_type, operation, symbols):
38
+ return {"feed": feed_type, "operation": operation, "symbols": symbols}
39
+
40
+ def connect(self, _request):
41
+ self.connect_calls += 1
42
+ self.connect_started.set()
43
+ assert self.allow_connect.wait(timeout=2)
44
+ self.ws = _WebSocket(self.sent_requests)
45
+
46
+ def error_data(self, callback):
47
+ self.error_callback = callback
48
+
49
+ def receive_data(self, callback):
50
+ callback(self.ws, json.dumps({"Token": 1, "LastRate": 100.0}))
51
+ self.stop_stream.wait(timeout=2)
52
+
53
+ def close_data(self):
54
+ if self.ws is not None:
55
+ self.ws.sock.connected = False
56
+
57
+
58
+ def _make_broker(api, fresh_login):
59
+ broker = object.__new__(FivePaisa)
60
+ broker.api = api
61
+ broker.broker = Brokers.FIVEPAISA
62
+ broker.account_key = broker.broker.name
63
+ broker.subscribe_thread = None
64
+ broker.subscribed_symbols = []
65
+ broker._stream_reconnect_lock = threading.Lock()
66
+ broker._stream_reconnect_serial_lock = threading.Lock()
67
+ broker._stream_subscriptions_lock = threading.Lock()
68
+ broker._stream_reconnect_active = False
69
+ broker._suppress_stream_reconnect = False
70
+ broker._last_stream_tick_ts = time.time() - 1
71
+ broker._fp_susertoken_path = "/tmp/fivepaisa-token"
72
+ broker._fp_restore_session_from_token = lambda _path: True
73
+ broker._fp_fresh_login = fresh_login
74
+ broker._wait_for_stream_request_rate_limit = lambda: None
75
+ broker.map_exchange_for_api = lambda _symbol, _exchange: "N"
76
+ broker.map_exchange_for_db = lambda _symbol, exchange: exchange
77
+ broker.exchange_mappings = {
78
+ "N": {
79
+ "symbol_map": {"TEST": 1, "TEST2": 2},
80
+ "symbol_map_reversed": {1: "TEST", 2: "TEST2"},
81
+ "exchangetype_map": {"TEST": "C", "TEST2": "C"},
82
+ }
83
+ }
84
+ return broker
85
+
86
+
87
+ def test_reconnect_waits_for_startup_and_coalesces_queued_callers():
88
+ connect_started = threading.Event()
89
+ allow_connect = threading.Event()
90
+ stop_stream = threading.Event()
91
+ api = _DelayedStreamingApi(connect_started, allow_connect, stop_stream)
92
+ fresh_login_calls = []
93
+ broker = _make_broker(api, lambda path: fresh_login_calls.append(path))
94
+ errors = []
95
+
96
+ def start_stream(symbol):
97
+ try:
98
+ broker.start_quotes_streaming("s", [symbol], exchange="NSE")
99
+ except Exception as exc:
100
+ errors.append(exc)
101
+
102
+ first_caller = threading.Thread(target=start_stream, args=("TEST",))
103
+ second_caller = threading.Thread(target=start_stream, args=("TEST2",))
104
+ with patch.object(fivepaisa_module.config, "get", return_value="/tmp/fivepaisa-token"):
105
+ first_caller.start()
106
+ assert connect_started.wait(timeout=2)
107
+ second_caller.start()
108
+ time.sleep(0.05)
109
+ allow_connect.set()
110
+ first_caller.join(timeout=2)
111
+ second_caller.join(timeout=2)
112
+ broker.start_quotes_streaming("s", ["TEST"], exchange="NSE")
113
+
114
+ stop_stream.set()
115
+ if broker.subscribe_thread is not None:
116
+ broker.subscribe_thread.join(timeout=2)
117
+
118
+ assert not first_caller.is_alive()
119
+ assert not second_caller.is_alive()
120
+ assert not errors
121
+ assert api.connect_calls == 1
122
+ assert fresh_login_calls == ["/tmp/fivepaisa-token"]
123
+ assert broker.subscribed_symbols == ["TEST", "TEST2"]
124
+ assert api.sent_requests == [
125
+ {
126
+ "feed": "mf",
127
+ "operation": "s",
128
+ "symbols": [{"Exch": "N", "ExchType": "C", "ScripCode": 2}],
129
+ },
130
+ {
131
+ "feed": "mf",
132
+ "operation": "s",
133
+ "symbols": [{"Exch": "N", "ExchType": "C", "ScripCode": 1}],
134
+ },
135
+ ]
@@ -571,6 +571,7 @@ class Price:
571
571
  high: float = float("nan"),
572
572
  low: float = float("nan"),
573
573
  volume: int = 0,
574
+ oi: int = 0,
574
575
  symbol: str = "",
575
576
  exchange: str = "",
576
577
  src: str = "",
@@ -589,6 +590,7 @@ class Price:
589
590
  high: High price
590
591
  low: Low price
591
592
  volume: Total volume
593
+ oi: Open interest
592
594
  symbol: Trading symbol
593
595
  exchange: Exchange name
594
596
  src: Source of the price data
@@ -606,6 +608,7 @@ class Price:
606
608
  self.high = high
607
609
  self.low = low
608
610
  self.volume = volume
611
+ self.oi = oi
609
612
  self.symbol = symbol
610
613
  self.exchange = exchange
611
614
  self.src = src
@@ -633,6 +636,7 @@ class Price:
633
636
  high=safe_add(self.high, other.high),
634
637
  low=safe_add(self.low, other.low),
635
638
  volume=safe_add_volume(self.volume, other.volume),
639
+ oi=safe_add_volume(self.oi, other.oi),
636
640
  )
637
641
  # dont change symbol
638
642
 
@@ -646,6 +650,7 @@ class Price:
646
650
  self.high = other.high * size if other.high * size is not float("nan") else self.high
647
651
  self.low = other.low * size if other.low * size is not float("nan") else self.low
648
652
  self.volume = other.volume if other.volume is not float("nan") else self.volume
653
+ self.oi = other.oi if other.oi is not float("nan") else self.oi
649
654
  self.symbol = other.symbol
650
655
  self.exchange = other.exchange
651
656
  self.src = other.src
@@ -662,6 +667,7 @@ class Price:
662
667
  "high": self.high,
663
668
  "low": self.low,
664
669
  "volume": self.volume,
670
+ "oi": self.oi,
665
671
  "symbol": self.symbol,
666
672
  "exchange": self.exchange,
667
673
  "src": self.src,
@@ -680,6 +686,7 @@ class Price:
680
686
  high=data.get("high", float("nan")),
681
687
  low=data.get("low", float("nan")),
682
688
  volume=data.get("volume", 0),
689
+ oi=data.get("oi", 0),
683
690
  symbol=data.get("symbol", ""),
684
691
  exchange=data.get("exchange", ""),
685
692
  src=data.get("src", ""),
@@ -2,13 +2,14 @@
2
2
  bhavcopy_folder : /home/psharma/onedrive/rfiles/data/bhavcopy
3
3
 
4
4
  # Optional: proxy for broker HTTP requests (e.g. connect, save_symbol_data).
5
- # When a broker has USE_PROXY: true, proxies are fetched from the source (e.g. webshare).
5
+ # USE_PROXY: trading API calls. USE_PROXY_SYMBOL_DOWNLOAD: symbol master download only.
6
6
  proxy:
7
7
  source: webshare
8
8
  api_key: # Webshare API key (or set WEBSHARE_PROXY_API_KEY env)
9
9
  username: # Optional: override per-proxy username from API
10
10
  password: # Optional: override per-proxy password from API
11
11
  country_code: # Optional: e.g. IN
12
+ max_proxies: 30 # NordVPN servers to try when rotating (symbol download)
12
13
  mode: direct
13
14
 
14
15
  commissions:
@@ -22,6 +23,7 @@ commissions:
22
23
  FIVEPAISA:
23
24
  EXCHANGES: [NSE, BSE, MCX]
24
25
  USE_PROXY: false
26
+ USE_PROXY_SYMBOL_DOWNLOAD: false
25
27
  APP_NAME:
26
28
  APP_SOURCE:
27
29
  USER_ID:
@@ -41,6 +43,7 @@ FIVEPAISA:
41
43
  SHOONYA:
42
44
  EXCHANGES: [NSE, BSE, MCX]
43
45
  USE_PROXY: false
46
+ USE_PROXY_SYMBOL_DOWNLOAD: false
44
47
  USER:
45
48
  PWD:
46
49
  VC:
@@ -56,6 +59,7 @@ SHOONYA:
56
59
  ICICIDIRECT:
57
60
  EXCHANGES: [NSE, BSE, MCX]
58
61
  USE_PROXY: false
62
+ USE_PROXY_SYMBOL_DOWNLOAD: false
59
63
  API_KEY:
60
64
  API_SECRET:
61
65
  USER_ID: # Optional: used by icicidirect-generate-session (AUTO_SESSION_TOKEN_CMD)
@@ -91,6 +95,7 @@ ICICIDIRECT:
91
95
  DHAN:
92
96
  EXCHANGES: [NSE, BSE]
93
97
  USE_PROXY: false
98
+ USE_PROXY_SYMBOL_DOWNLOAD: false
94
99
  CLIENT_ID:
95
100
  ACCESS_TOKEN: # Optional fallback if TOTP/PIN flow is not used
96
101
  TOTP_TOKEN: # Optional: used for auto token refresh
@@ -107,6 +112,7 @@ DHAN:
107
112
  DHAN_ACCOUNT2:
108
113
  EXCHANGES: [NSE, BSE]
109
114
  USE_PROXY: false
115
+ USE_PROXY_SYMBOL_DOWNLOAD: false
110
116
  CLIENT_ID:
111
117
  ACCESS_TOKEN:
112
118
  TOTP_TOKEN:
@@ -287,11 +287,21 @@ def save_symbol_data(saveToFolder: bool = False):
287
287
  dest_file = f"{bhavcopyfolder}/{dt.datetime.today().strftime('%Y%m%d')}_dhan_codes.csv"
288
288
 
289
289
  headers = {"User-Agent": "Mozilla/5.0", "Accept": "*/*"}
290
- response = requests.get(DHAN_SECURITY_LIST_URL, headers=headers, timeout=(10, 300))
291
- if response.status_code != 200:
292
- raise Exception(f"Failed to fetch Dhan symbol data. Status: {response.status_code}")
293
-
294
- df = pd.read_csv(io.BytesIO(response.content), low_memory=False)
290
+ if os.path.exists(dest_file):
291
+ df = pd.read_csv(dest_file, low_memory=False)
292
+ else:
293
+ from .proxy_utils import request_get_with_broker_proxy
294
+
295
+ response = request_get_with_broker_proxy(
296
+ DHAN_SECURITY_LIST_URL,
297
+ "DHAN",
298
+ purpose="symbol_download",
299
+ headers=headers,
300
+ timeout=(10, 300),
301
+ )
302
+ with open(dest_file, "wb") as f:
303
+ f.write(response.content)
304
+ df = pd.read_csv(dest_file, low_memory=False)
295
305
  df.columns = [col.strip() for col in df.columns]
296
306
  object_cols = df.select_dtypes(include=["object"]).columns
297
307
  for col in object_cols:
@@ -1985,6 +1995,11 @@ class Dhan(BrokerBase):
1985
1995
  market_feed.low = float(ohlc.get("low", data.get("dayLow", float("nan"))) or float("nan"))
1986
1996
  market_feed.prior_close = float(ohlc.get("close", data.get("previousClosePrice", float("nan"))) or float("nan"))
1987
1997
  market_feed.volume = int(data.get("volume", data.get("totalTradedVolume", 0)) or 0)
1998
+ raw_oi = data.get("OI", data.get("oi", data.get("open_interest", data.get("openInterest", 0))))
1999
+ try:
2000
+ market_feed.oi = int(str(raw_oi).strip() or 0)
2001
+ except (TypeError, ValueError):
2002
+ market_feed.oi = int(float(raw_oi or 0))
1988
2003
  depth = data.get("depth", {})
1989
2004
  buy_qty = depth.get("buy", [{}])
1990
2005
  sell_qty = depth.get("sell", [{}])
@@ -2340,9 +2355,12 @@ class Dhan(BrokerBase):
2340
2355
  if value in (None, "", "nan"):
2341
2356
  return 0
2342
2357
  try:
2343
- return int(float(value))
2358
+ return int(str(value).strip())
2344
2359
  except (TypeError, ValueError):
2345
- return 0
2360
+ try:
2361
+ return int(float(value))
2362
+ except (TypeError, ValueError):
2363
+ return 0
2346
2364
 
2347
2365
  def _apply_if_valid(current: float, value: Any) -> float:
2348
2366
  parsed = _to_float(value)
@@ -2391,6 +2409,9 @@ class Dhan(BrokerBase):
2391
2409
  volume = _to_int(response.get("volume"))
2392
2410
  if volume:
2393
2411
  price.volume = volume
2412
+ oi = _to_int(response.get("OI") or response.get("oi") or response.get("open_interest") or response.get("openInterest"))
2413
+ if oi:
2414
+ price.oi = oi
2394
2415
  price.bid = _apply_if_valid(price.bid, response.get("bidPrice"))
2395
2416
  price.ask = _apply_if_valid(price.ask, response.get("askPrice"))
2396
2417
  bid_qty = _to_int(response.get("bidQty"))