Skip to content

Commit

Permalink
add fetch bse expiry method
Browse files Browse the repository at this point in the history
  • Loading branch information
Apurv-Salunke committed Aug 22, 2024
1 parent 1625d14 commit ddeb5d0
Show file tree
Hide file tree
Showing 4 changed files with 616 additions and 0 deletions.
95 changes: 95 additions & 0 deletions core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# ------------------------------------------------------------------------------


__version__ = "1.0.5"


# ------------------------------------------------------------------------------

from threading import Thread

from core.brokers.base import Broker # noqa: F401


from core.brokers.base import errors # noqa: F401
from core.brokers.base.errors import InputError # noqa: F401
from core.brokers.base.errors import ResponseError # noqa: F401
from core.brokers.base.errors import TokenDownloadError # noqa: F401
from core.brokers.base.errors import RequestTimeout # noqa: F401
from core.brokers.base.errors import NetworkError # noqa: F401
from core.brokers.base.errors import BrokerError # noqa: F401


from core.brokers.base import constants # noqa: F401
from core.brokers.base.constants import Side # noqa: F401
from core.brokers.base.constants import Root # noqa: F401
from core.brokers.base.constants import WeeklyExpiry # noqa: F401
from core.brokers.base.constants import Option # noqa: F401
from core.brokers.base.constants import OrderType # noqa: F401
from core.brokers.base.constants import ExchangeCode # noqa: F401
from core.brokers.base.constants import Product # noqa: F401
from core.brokers.base.constants import Validity # noqa: F401
from core.brokers.base.constants import Variety # noqa: F401
from core.brokers.base.constants import Status # noqa: F401
from core.brokers.base.constants import Order # noqa: F401
from core.brokers.base.constants import Position # noqa: F401
from core.brokers.base.constants import Profile # noqa: F401
from core.brokers.base.constants import UniqueID # noqa: F401


brokers = [
"aliceblue",
"angelone",
"choice",
"finvasia",
"fivepaisa",
"fyers",
"iifl",
"kotak",
"kotakneo",
"kunjee",
"mastertrust",
"motilaloswal",
"paper",
"symphony",
"upstox",
"vpc",
"zerodha",
]

base = [
"Broker",
"brokers",
"constants",
]

__all__ = base + errors.__all__ + brokers + constants.__all__


headers = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "en-GB,en;q=0.9",
"dnt": "1",
"sec-ch-ua": '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
}

temp_session = Broker._create_session()
response = temp_session.request(
method="GET", url="https://www.nseindia.com/option-chain", headers=headers
)
Broker.cookies = dict(response.cookies)

if not Broker.expiry_dates:
for root in [Root.BNF, Root.NF, Root.FNF, Root.MIDCPNF]:
Thread(target=Broker.download_expiry_dates_nfo, args=(root,)).start()

for root in [Root.SENSEX, Root.BANKEX]:
Thread(target=Broker.download_expiry_dates_bfo, args=(root,)).start()
69 changes: 69 additions & 0 deletions core/brokers/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
ConnectionError as RequestsConnectionError,
)

from core.brokers.base.constants import Root
from core.config.network import RETRY_STRATEGY
from core.brokers.base.errors import (
InputError,
Expand Down Expand Up @@ -575,3 +576,71 @@ def download_expiry_dates_nfo(
print(f"Error: {exc}")

sleep(5)

@classmethod
def download_expiry_dates_bfo(
cls,
root,
):
if root == Root.SENSEX:
scrip_cd = 1
elif root == Root.BANKEX:
scrip_cd = 12

temp_session = req_session()

for _ in range(5):
try:
headers = {
"accept": "application/json, text/plain, */*",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8,hi;q=0.7",
"dnt": "1",
"if-modified-since": "Sun, 24 Mar 2024 11:21:31 GMT",
"origin": "https://www.bseindia.com",
"referer": "https://www.bseindia.com/",
"sec-ch-ua": '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
}

params = {
"ProductType": "IO",
"scrip_cd": scrip_cd,
}

response = temp_session.request(
method="GET",
url=cls.bfo_url,
params=params,
headers=headers,
timeout=10,
)
data = response.json()

expiry_dates = data["Table1"]
expiry_dates = [i["ExpiryDate"] for i in expiry_dates]
cls.expiry_dates[root] = cls.filter_future_dates(expiry_dates)
return None

except Exception as exec:
print(f"Error while fetching BSE data: {exec}")

try:
response = popen(
f"curl '{cls.bfo_url}?ProductType=IO&scrip_cd=1' -H 'accept: application/json, text/plain, */*' -H 'accept-language: en-GB,en-US;q=0.9,en;q=0.8,hi;q=0.7' -H 'dnt: 1' -H 'if-modified-since: Sun, 24 Mar 2024 11:21:31 GMT' -H 'origin: https://www.bseindia.com' -H 'referer: https://www.bseindia.com/' -H 'sec-ch-ua-mobile: ?0' -H 'sec-fetch-dest: empty' -H 'sec-fetch-mode: cors' -H 'sec-fetch-site: same-site' -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'"
).read()
data = loads(response)

expiry_dates = data["Table1"]
expiry_dates = [i["ExpiryDate"] for i in expiry_dates]
cls.expiry_dates[root] = cls.filter_future_dates(expiry_dates)
return None

except Exception as exc: # noqa: E722
print(f"Error: {exc}")

sleep(5)
205 changes: 205 additions & 0 deletions tests/core/brokers/base/test_base_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from core.brokers.base import Broker
from core.brokers.base import RequestTimeout, BrokerError, NetworkError
from core.brokers.base.constants import Root
from core.brokers.base.errors import InputError, ResponseError


Expand Down Expand Up @@ -1557,3 +1558,207 @@ def test_filter_future_dates(mock_session):
assert today.strftime("%Y-%m-%d") not in Broker.expiry_dates["TEST"]
assert future_date1 in Broker.expiry_dates["TEST"]
assert future_date2 in Broker.expiry_dates["TEST"]


@pytest.fixture
def mock_response_data():
today = datetime.now().date()
return {
"Table1": [
{"ExpiryDate": (today + timedelta(days=1)).strftime("%Y-%m-%d")},
{"ExpiryDate": (today + timedelta(days=8)).strftime("%Y-%m-%d")},
{"ExpiryDate": (today + timedelta(days=15)).strftime("%Y-%m-%d")},
]
}


@patch("core.brokers.base.base.req_session")
def test_download_expiry_dates_bfo_success(mock_session, mock_response_data):
mock_response = MagicMock()
mock_response.json.return_value = mock_response_data
mock_session.return_value.request.return_value = mock_response

Broker.bfo_url = "https://example.com/bfo"
Broker.expiry_dates = {}

Broker.download_expiry_dates_bfo(Root.SENSEX)

assert Root.SENSEX in Broker.expiry_dates
assert len(Broker.expiry_dates[Root.SENSEX]) == 3
mock_session.return_value.request.assert_called_once()


@patch("core.brokers.base.base.req_session")
def test_download_expiry_dates_bfo_different_roots(mock_session, mock_response_data):
mock_response = MagicMock()
mock_response.json.return_value = mock_response_data
mock_session.return_value.request.return_value = mock_response

Broker.bfo_url = "https://example.com/bfo"
Broker.expiry_dates = {}

Broker.download_expiry_dates_bfo(Root.SENSEX)
Broker.download_expiry_dates_bfo(Root.BANKEX)

assert Root.SENSEX in Broker.expiry_dates
assert Root.BANKEX in Broker.expiry_dates
assert mock_session.return_value.request.call_count == 2


@patch("core.brokers.base.base.req_session")
@patch("core.brokers.base.base.popen")
def test_download_expiry_dates_bfo_fallback_to_curl(
mock_popen, mock_session, mock_response_data
):
mock_session.return_value.request.side_effect = Exception("Request failed")

mock_popen.return_value.read.return_value = dumps(mock_response_data)

Broker.bfo_url = "https://example.com/bfo"
Broker.expiry_dates = {}

Broker.download_expiry_dates_bfo(Root.SENSEX)

assert Root.SENSEX in Broker.expiry_dates
assert len(Broker.expiry_dates[Root.SENSEX]) == 3
mock_session.return_value.request.assert_called_once()
mock_popen.assert_called_once()


@patch("core.brokers.base.base.req_session")
@patch("core.brokers.base.base.popen")
@patch("core.brokers.base.base.sleep")
def test_download_expiry_dates_bfo_all_attempts_fail(
mock_sleep, mock_popen, mock_session
):
mock_session.return_value.request.side_effect = Exception("Request failed")
mock_popen.return_value.read.side_effect = Exception("Curl failed")

Broker.bfo_url = "https://example.com/bfo"
Broker.expiry_dates = {}

Broker.download_expiry_dates_bfo(Root.SENSEX)

assert Root.SENSEX not in Broker.expiry_dates
assert mock_sleep.call_count == 5


@patch("core.brokers.base.base.req_session")
def test_download_expiry_dates_bfo_filter_past_dates(mock_session):
today = datetime.now().date()
mock_response_data = {
"Table1": [
{"ExpiryDate": (today - timedelta(days=1)).strftime("%Y-%m-%d")},
{"ExpiryDate": today.strftime("%Y-%m-%d")},
{"ExpiryDate": (today + timedelta(days=1)).strftime("%Y-%m-%d")},
]
}
mock_response = MagicMock()
mock_response.json.return_value = mock_response_data
mock_session.return_value.request.return_value = mock_response

Broker.bfo_url = "https://example.com/bfo"
Broker.expiry_dates = {}

Broker.download_expiry_dates_bfo(Root.SENSEX)

assert Root.SENSEX in Broker.expiry_dates
assert len(Broker.expiry_dates[Root.SENSEX]) == 1 # Not Today and future date only
assert all(
date >= today.strftime("%Y-%m-%d") for date in Broker.expiry_dates[Root.SENSEX]
)


# @pytest.fixture
# def sample_dataframe():
# today = datetime.now().date()
# expiry1 = today + timedelta(days=1)
# expiry2 = today + timedelta(days=8)
# expiry3 = today + timedelta(days=15)

# data = {
# 'Token': [1, 2, 3, 4],
# 'Symbol': ['BANKNIFTY23316CE', 'NIFTY23316PE', 'FINNIFTY23316CE', 'MIDCPNIFTY23316PE'],
# 'Expiry': [expiry1, expiry2, expiry3, expiry1],
# 'Option': ['CE', 'PE', 'CE', 'PE'],
# 'StrikePrice': [34500, 17000, 18000, 7500],
# 'LotSize': [25, 50, 40, 75],
# 'Root': ['BANKNIFTY', 'NIFTY', 'FINNIFTY', 'MIDCPNIFTY'],
# 'TickSize': [0.05, 0.05, 0.05, 0.05]
# }
# return DataFrame(data)

# @patch.object(Broker, 'download_expiry_dates_nfo')
# @patch.object(Broker, 'download_expiry_dates_bfo')
# def test_jsonify_expiry_basic_structure(mock_bfo, mock_nfo, sample_dataframe):
# Broker.expiry_dates = {
# Root.BNF: [str(datetime.now().date() + timedelta(days=i)) for i in range(1, 4)],
# Root.NF: [str(datetime.now().date() + timedelta(days=i)) for i in range(1, 4)],
# Root.FNF: [str(datetime.now().date() + timedelta(days=i)) for i in range(1, 4)],
# Root.MIDCPNF: [str(datetime.now().date() + timedelta(days=i)) for i in range(1, 4)]
# }

# result = Broker.jsonify_expiry(sample_dataframe)

# assert set(result.keys()) == {WeeklyExpiry.CURRENT, WeeklyExpiry.NEXT, WeeklyExpiry.FAR, WeeklyExpiry.EXPIRY, WeeklyExpiry.LOTSIZE}
# assert set(result[WeeklyExpiry.CURRENT].keys()) == {Root.BNF, Root.NF, Root.FNF, Root.MIDCPNF, Root.SENSEX, Root.BANKEX}
# assert set(result[WeeklyExpiry.EXPIRY].keys()) == {Root.BNF, Root.NF, Root.FNF, Root.MIDCPNF, Root.SENSEX, Root.BANKEX}
# assert set(result[WeeklyExpiry.LOTSIZE].keys()) == {Root.BNF, Root.NF, Root.FNF, Root.MIDCPNF, Root.SENSEX, Root.BANKEX}

# # def test_jsonify_expiry_lotsize(sample_dataframe):
# # result = Broker.jsonify_expiry(sample_dataframe)

# # assert result[WeeklyExpiry.LOTSIZE][Root.BNF] == 25
# # assert result[WeeklyExpiry.LOTSIZE][Root.NF] == 50
# # assert result[WeeklyExpiry.LOTSIZE][Root.FNF] == 40
# # assert result[WeeklyExpiry.LOTSIZE][Root.MIDCPNF] == 75

# # def test_jsonify_expiry_expiry_dates(sample_dataframe):
# # result = Broker.jsonify_expiry(sample_dataframe)

# # assert len(result[WeeklyExpiry.EXPIRY][Root.BNF]) == 1
# # assert len(result[WeeklyExpiry.EXPIRY][Root.NF]) == 1
# # assert len(result[WeeklyExpiry.EXPIRY][Root.FNF]) == 1
# # assert len(result[WeeklyExpiry.EXPIRY][Root.MIDCPNF]) == 1

# # def test_jsonify_expiry_option_data(sample_dataframe):
# # result = Broker.jsonify_expiry(sample_dataframe)

# # assert 'CE' in result[WeeklyExpiry.CURRENT][Root.BNF]
# # assert 'PE' in result[WeeklyExpiry.CURRENT][Root.NF]
# # assert 34500 in result[WeeklyExpiry.CURRENT][Root.BNF]['CE']
# # assert 17000 in result[WeeklyExpiry.CURRENT][Root.NF]['PE']

# # @patch.object(Broker, 'download_expiry_dates_nfo')
# # @patch.object(Broker, 'download_expiry_dates_bfo')
# # def test_jsonify_expiry_download_calls(mock_bfo, mock_nfo, sample_dataframe):
# # Broker.expiry_dates = {}
# # Broker.jsonify_expiry(sample_dataframe)

# # assert mock_nfo.call_count == 4 # BNF, NF, FNF, MIDCPNF
# # assert mock_bfo.call_count == 2 # SENSEX, BANKEX

# # def test_jsonify_expiry_empty_dataframe():
# # empty_df = DataFrame(columns=['Token', 'Symbol', 'Expiry', 'Option', 'StrikePrice', 'LotSize', 'Root', 'TickSize'])
# # result = Broker.jsonify_expiry(empty_df)

# # assert all(not result[WeeklyExpiry.CURRENT][root] for root in Root)
# # assert all(not result[WeeklyExpiry.EXPIRY][root] for root in Root)
# # assert all(isna(result[WeeklyExpiry.LOTSIZE][root]) for root in Root)

# # @pytest.mark.parametrize("root", [Root.BNF, Root.NF, Root.FNF, Root.MIDCPNF])
# # def test_jsonify_expiry_specific_root(root, sample_dataframe):
# # specific_df = sample_dataframe[sample_dataframe['Root'] == root.value]
# # result = Broker.jsonify_expiry(specific_df)

# # assert result[WeeklyExpiry.CURRENT][root]
# # assert result[WeeklyExpiry.EXPIRY][root]
# # assert not isna(result[WeeklyExpiry.LOTSIZE][root])

# # def test_jsonify_expiry_sorting(sample_dataframe):
# # unsorted_df = sample_dataframe.sort_values(by=['Expiry'], ascending=False)
# # result = Broker.jsonify_expiry(unsorted_df)

# # for root in [Root.BNF, Root.NF, Root.FNF, Root.MIDCPNF]:
# # if result[WeeklyExpiry.EXPIRY][root]:
# # assert result[WeeklyExpiry.EXPIRY][root] == sorted(result[WeeklyExpiry.EXPIRY][root])
Loading

0 comments on commit ddeb5d0

Please sign in to comment.