Skip to content

Commit 5c860ee

Browse files
authored
Merge pull request #553 from mex-web/main
Main
2 parents 4151ed6 + de46167 commit 5c860ee

File tree

21 files changed

+1181
-9
lines changed

21 files changed

+1181
-9
lines changed

.sample.env

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ REDIRECT_URL = 'http://127.0.0.1:5000/<broker>/callback' # Change if different
1919

2020
# Valid Brokers Configuration
2121

22-
VALID_BROKERS = 'fivepaisa,fivepaisaxts,aliceblue,angel,compositedge,dhan,dhan_sandbox,definedge,firstock,flattrade,fyers,groww,ibulls,iifl,indmoney,kotak,motilal,paytm,pocketful,shoonya,tradejini,upstox,wisdom,zebu,zerodha'
22+
VALID_BROKERS = 'mstock,fivepaisa,fivepaisaxts,aliceblue,angel,compositedge,dhan,dhan_sandbox,definedge,firstock,flattrade,fyers,groww,ibulls,iifl,indmoney,kotak,motilal,paytm,pocketful,shoonya,tradejini,upstox,wisdom,zebu,zerodha,mstock'
23+
24+
# mstock Configuration
25+
# BROKER_API_KEY = 'YOUR_MSTOCK_API_KEY'
26+
# BROKER_USERNAME = 'YOUR_MSTOCK_CLIENT_ID'
27+
# BROKER_PIN = 'YOUR_MSTOCK_PIN'
28+
# BROKER_TOTP_CODE = 'YOUR_MSTOCK_TOTP'
2329

2430

2531
# Security Configuration

app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,4 +458,4 @@ def run_catchup():
458458
url = f"http://{host_ip}:{port}"
459459
log_startup_banner(logger, "OpenAlgo is running!", url)
460460

461-
socketio.run(app, host=host_ip, port=port, debug=debug)
461+
socketio.run(app, host=host_ip, port=port, debug=debug)

blueprints/brlogin.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ def broker_callback(broker,para=None):
8181
user_id = clientcode
8282
auth_token, feed_token, error_message = auth_function(clientcode, broker_pin, totp_code)
8383
forward_url = 'angel.html'
84+
85+
elif broker == 'mstock':
86+
if request.method == 'GET':
87+
return render_template('mstock.html')
88+
89+
elif request.method == 'POST':
90+
totp_code = request.form.get('totp')
91+
api_key = get_broker_api_key()
92+
auth_token, feed_token, error_message = auth_function(api_key, totp_code)
93+
forward_url = 'mstock.html'
8494

8595
elif broker == 'aliceblue':
8696
if request.method == 'GET':

broker/mstock/__init__.py

Whitespace-only changes.

broker/mstock/api/__init__.py

Whitespace-only changes.

broker/mstock/api/auth_api.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import httpx
2+
import json
3+
import os
4+
from utils.httpx_client import get_httpx_client
5+
6+
def authenticate_broker(api_key, totp_code):
7+
"""
8+
Authenticate with mstock and return the auth token.
9+
"""
10+
try:
11+
client = get_httpx_client()
12+
13+
# If TOTP is enabled, we can directly verify it without a prior login call
14+
headers = {
15+
'X-Mirae-Version': '1',
16+
'Content-Type': 'application/x-www-form-urlencoded',
17+
}
18+
data = {
19+
'api_key': api_key,
20+
'totp': totp_code,
21+
}
22+
23+
response = client.post(
24+
'https://api.mstock.trade/openapi/typea/session/verifytotp',
25+
headers=headers,
26+
data=data
27+
)
28+
29+
response.raise_for_status()
30+
data_dict = response.json()
31+
32+
if data_dict.get("status") == "success" and "data" in data_dict:
33+
auth_token = data_dict["data"].get("access_token")
34+
# mstock does not provide a separate feed token in this response
35+
feed_token = None
36+
return auth_token, feed_token, None
37+
else:
38+
error_message = data_dict.get("message", "Authentication failed.")
39+
return None, None, error_message
40+
41+
except httpx.HTTPStatusError as e:
42+
return None, None, f"HTTP error occurred: {e.response.status_code} - {e.response.text}"
43+
except Exception as e:
44+
return None, None, str(e)

broker/mstock/api/data.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from utils.httpx_client import get_httpx_client
2+
from broker.mstock.mapping.order_data import transform_positions_data, transform_holdings_data
3+
4+
def get_positions(auth_token):
5+
api_key = os.getenv('BROKER_API_KEY')
6+
"""
7+
Retrieves the user's positions.
8+
"""
9+
headers = {
10+
'X-Mirae-Version': '1',
11+
'Authorization': f'token {api_key}:{auth_token}',
12+
}
13+
14+
try:
15+
client = get_httpx_client()
16+
response = client.get(
17+
'https://api.mstock.trade/openapi/typea/portfolio/positions',
18+
headers=headers,
19+
)
20+
response.raise_for_status()
21+
positions = response.json()
22+
return transform_positions_data(positions), None
23+
except Exception as e:
24+
return None, str(e)
25+
26+
def get_holdings(auth_token):
27+
api_key = os.getenv('BROKER_API_KEY')
28+
"""
29+
Retrieves the user's holdings.
30+
"""
31+
headers = {
32+
'X-Mirae-Version': '1',
33+
'Authorization': f'token {api_key}:{auth_token}',
34+
}
35+
36+
try:
37+
client = get_httpx_client()
38+
response = client.get(
39+
'https://api.mstock.trade/openapi/typea/portfolio/holdings',
40+
headers=headers,
41+
)
42+
response.raise_for_status()
43+
holdings = response.json()
44+
return transform_holdings_data(holdings), None
45+
except Exception as e:
46+
return None, str(e)

broker/mstock/api/funds.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import os
2+
import httpx
3+
from utils.httpx_client import get_httpx_client
4+
from utils.logging import get_logger
5+
from broker.mstock.database import master_contract_db
6+
7+
logger = get_logger(__name__)
8+
9+
def get_margin_data(auth_token):
10+
"""Fetch margin (fund) data from MStock API using Type A authentication."""
11+
api_key = os.getenv('BROKER_API_KEY')
12+
13+
if not api_key:
14+
logger.error("Missing environment variable: BROKER_API_KEY")
15+
return {}
16+
17+
headers = {
18+
'X-Mirae-Version': '1',
19+
'Authorization': f'token {api_key}:{auth_token}',
20+
}
21+
22+
try:
23+
client = get_httpx_client()
24+
response = client.get(
25+
'https://api.mstock.trade/openapi/typea/user/fundsummary',
26+
headers=headers,
27+
timeout=10.0
28+
)
29+
response.raise_for_status()
30+
margin_data = response.json()
31+
32+
if margin_data.get('status') == 'success' and margin_data.get('data'):
33+
data = margin_data['data'][0]
34+
key_mapping = {
35+
"AVAILABLE_BALANCE": "availablecash",
36+
"COLLATERALS": "collateral",
37+
"REALISED_PROFITS": "m2mrealized",
38+
"MTM_COMBINED": "m2munrealized",
39+
"AMOUNT_UTILIZED": "utiliseddebits",
40+
}
41+
42+
filtered_data = {}
43+
for mstock_key, openalgo_key in key_mapping.items():
44+
value = data.get(mstock_key)
45+
if value in (None, "None", ""):
46+
value = 0
47+
try:
48+
formatted_value = "{:.2f}".format(float(value))
49+
except (ValueError, TypeError):
50+
formatted_value = "0.00"
51+
filtered_data[openalgo_key] = formatted_value
52+
53+
logger.info(f"filteredMargin Data: {filtered_data}")
54+
return filtered_data
55+
56+
logger.error(f"Margin API failed: {margin_data.get('message', 'No data')}")
57+
return {}
58+
59+
except httpx.HTTPStatusError as e:
60+
logger.error(f"HTTP Error while fetching margin data: {e}")
61+
return {}
62+
except httpx.RequestError as e:
63+
logger.error(f"Network Error while fetching margin data: {e}")
64+
return {}
65+
except Exception as e:
66+
logger.exception("Unexpected error while fetching margin data.")
67+
return {}

0 commit comments

Comments
 (0)