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.
- {tradingapi-0.3.9 → tradingapi-0.3.11}/PKG-INFO +1 -1
- {tradingapi-0.3.9 → tradingapi-0.3.11}/pyproject.toml +1 -1
- tradingapi-0.3.11/tests/test_fivepaisa_stream_reconnect.py +135 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/broker_base.py +7 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/config/config_sample.yaml +7 -1
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/dhan.py +28 -7
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/fivepaisa.py +204 -74
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/flattrade.py +19 -2
- tradingapi-0.3.11/tradingapi/ib.py +864 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/icicidirect.py +10 -3
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/market_data_exchanges.py +1 -0
- tradingapi-0.3.11/tradingapi/proxy_utils.py +294 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/shoonya.py +17 -2
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/utils.py +35 -1
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/PKG-INFO +1 -1
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/SOURCES.txt +2 -0
- tradingapi-0.3.9/tradingapi/proxy_utils.py +0 -130
- {tradingapi-0.3.9 → tradingapi-0.3.11}/README.md +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/setup.cfg +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tests/test_broker_side_terminal_order.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tests/test_calculate_delta_realtime_quotes.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tests/test_find_option_with_delta.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/__init__.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/allocation.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/attribution.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/config/commissions_20241216.yaml +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/config.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/error_handling.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/exceptions.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/globals.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/icicidirect_generate_session.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi/span.py +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/dependency_links.txt +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/entry_points.txt +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/requires.txt +0 -0
- {tradingapi-0.3.9 → tradingapi-0.3.11}/tradingapi.egg-info/top_level.txt +0 -0
|
@@ -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
|
-
#
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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(
|
|
2358
|
+
return int(str(value).strip())
|
|
2344
2359
|
except (TypeError, ValueError):
|
|
2345
|
-
|
|
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"))
|