Skip to content

Commit

Permalink
Merge pull request #199 from naiduasn/paytm
Browse files Browse the repository at this point in the history
Adding Paytm Integration to OpenAlgo
  • Loading branch information
marketcalls authored Mar 9, 2025
2 parents fbbaf79 + dfa176a commit 7c0257c
Show file tree
Hide file tree
Showing 11 changed files with 1,296 additions and 0 deletions.
5 changes: 5 additions & 0 deletions blueprints/brlogin.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ def broker_callback(broker,para=None):
auth_token, error_message = auth_function(otp,token,sid,userid,access_token,hsServerId)
forward_url = 'kotak.html'

elif broker == 'paytm':
request_token = request.args.get('requestToken')
print(f'The request token is {request_token}')
auth_token, error_message = auth_function(request_token)

else:
code = request.args.get('code') or request.args.get('request_token')
print(f'The code is {code}')
Expand Down
Empty file added broker/paytm/api/__init__.py
Empty file.
47 changes: 47 additions & 0 deletions broker/paytm/api/auth_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import os
import requests

def authenticate_broker(request_token):
"""
Authenticate with Paytm Money broker API.
The authentication flow works as follows:
1. Navigate to Paytm Money API endpoint: https://login.paytmmoney.com/merchant-login?apiKey={api_key}&state={state_key}
2. After successful login, a request_token is returned as URL parameter to the registered redirect URL
3. Use the request_token to generate an access_token
Args:
code: The request token received from the redirect URL after successful login
Returns:
tuple: (access_token, error_message)
- access_token: The token to use for subsequent API calls
- error_message: Error details if authentication fails, None on success
"""
try:
BROKER_API_KEY = os.getenv('BROKER_API_KEY')
BROKER_API_SECRET = os.getenv('BROKER_API_SECRET')

url = 'https://developer.paytmmoney.com/accounts/v2/gettoken'
data = {
'api_key': BROKER_API_KEY,
'api_secret_key': BROKER_API_SECRET,
'request_token': request_token
}
headers = {'Content-Type': 'application/json'}
response = requests.post(url, json=data, headers=headers)

if response.status_code == 200:
response_data = response.json()
if 'access_token' in response_data:
return response_data['access_token'], None
else:
return None, "Authentication succeeded but no access token was returned. Please check the response."
else:
# Parsing the error message from the API response
error_detail = response.json() # Assuming the error is in JSON format
error_messages = error_detail.get('errors', [])
detailed_error_message = "; ".join([error['message'] for error in error_messages])
return None, f"API error: {error_messages}" if detailed_error_message else "Authentication failed. Please try again."
except Exception as e:
return None, f"An exception occurred: {str(e)}"
269 changes: 269 additions & 0 deletions broker/paytm/api/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import http.client
import json
import os
import urllib.parse
from database.token_db import get_br_symbol, get_token
from broker.paytm.database.master_contract_db import SymToken, db_session
import logging
import pandas as pd
from datetime import datetime, timedelta

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def get_api_response(endpoint, auth, method="GET", payload=''):
AUTH_TOKEN = auth
conn = http.client.HTTPSConnection("developer.paytmmoney.com")
headers = {
'x-jwt-token': AUTH_TOKEN,
'Content-Type': 'application/json',
'Accept': 'application/json',
}

try:
# Log the complete request details for Postman
logger.info("=== API Request Details ===")
logger.info(f"URL: https://developer.paytmmoney.com{endpoint}")
logger.info(f"Method: {method}")
logger.info(f"Headers: {json.dumps(headers, indent=2)}")
if payload:
logger.info(f"Payload: {payload}")

conn.request(method, endpoint, payload, headers)
res = conn.getresponse()
data = res.read()
response = json.loads(data.decode("utf-8"))

# Log the complete response
logger.info("=== API Response Details ===")
logger.info(f"Status Code: {res.status}")
logger.info(f"Response Headers: {dict(res.getheaders())}")
logger.info(f"Response Body: {json.dumps(response, indent=2)}")

return response
except Exception as e:
logger.error(f"API request failed: {str(e)}")
raise

class BrokerData:
def __init__(self, auth_token):
"""Initialize Paytm data handler with authentication token"""
self.auth_token = auth_token

# PAYTM does not support historical data API
# Map common timeframe format to Paytm intervals
self.timeframe_map = {
# Minutes
'1m': 'minute',
'3m': '3minute',
'5m': '5minute',
'10m': '10minute',
'15m': '15minute',
'30m': '30minute',
'60m': '60minute',
# Daily
'D': 'day'
}

# Market timing configuration for different exchanges
self.market_timings = {
'NSE': {
'start': '09:15:00',
'end': '15:30:00'
},
'BSE': {
'start': '09:15:00',
'end': '15:30:00'
},
'NFO': {
'start': '09:15:00',
'end': '15:30:00'
},
'CDS': {
'start': '09:00:00',
'end': '17:00:00'
},
'BCD': {
'start': '09:00:00',
'end': '17:00:00'
}
}

# Default market timings if exchange not found
self.default_market_timings = {
'start': '09:15:00',
'end': '15:29:59'
}

def get_market_timings(self, exchange: str) -> dict:
"""Get market start and end times for given exchange"""
return self.market_timings.get(exchange, self.default_market_timings)

def get_quotes(self, symbol: str, exchange: str) -> dict:
"""
Get real-time quotes for given symbol
Args:
symbol: Trading symbol
exchange: Exchange (e.g., NSE, BSE)
Returns:
dict: Quote data with required fields
"""
try:
# Convert symbol to broker format
token = get_token(symbol, exchange)
logger.info(f"Fetching quotes for {exchange}:{token}")

br_symbol = get_br_symbol(symbol, exchange)
# Determine opt_type based on symbol format
parts = br_symbol.split('-')
if len(parts) > 2:
if parts[-1] in ['CE', 'PE']:
opt_type = 'OPTION'
elif 'FUT' in parts[-1]:
opt_type = 'FUTURE'
else:
opt_type = 'EQUITY'
else:
opt_type = 'EQUITY'

# URL encode the symbol to handle special characters
# Paytm expects the symbol to be in the format "exchange:symbol" E,g: NSE:335:EQUITY
# INDEX, EQUITY, ETF, FUTURE, OPTION
encoded_symbol = urllib.parse.quote(f"{exchange}:{token}:{opt_type}")

response = get_api_response(f"/data/v1/price/live?mode=QUOTE&pref={encoded_symbol}", self.auth_token)

if not response or not response.get('data', []):
raise Exception(f"Error from Paytm API: {response.get('message', 'Unknown error')}")


# Return quote data
quote = response.get('data', [])[0] if response.get('data') else {}
if not quote:
raise Exception("No quote data found")

return {
'ask': 0, # Not available in new format
'bid': 0, # Not available in new format
'high': quote.get('ohlc', {}).get('high', 0),
'low': quote.get('ohlc', {}).get('low', 0),
'ltp': quote.get('last_price', 0),
'open': quote.get('ohlc', {}).get('open', 0),
'prev_close': quote.get('ohlc', {}).get('close', 0),
'volume': quote.get('volume_traded', 0)
}

except Exception as e:
logger.error(f"Error fetching quotes: {str(e)}")
raise Exception(f"Error fetching quotes: {str(e)}")


def get_market_depth(self, symbol: str, exchange: str) -> dict:
"""
Get market depth for given symbol
Args:
symbol: Trading symbol
exchange: Exchange (e.g., NSE, BSE)
Returns:
dict: Market depth data
"""
try:
# Convert symbol to broker format
token = get_token(symbol, exchange)
logger.info(f"Fetching quotes for {exchange}:{token}")

br_symbol = get_br_symbol(symbol, exchange)
# Determine opt_type based on symbol format
parts = br_symbol.split('-')
if len(parts) > 2:
if parts[-1] in ['CE', 'PE']:
opt_type = 'OPTION'
elif 'FUT' in parts[-1]:
opt_type = 'FUTURE'
else:
opt_type = 'EQUITY'
else:
opt_type = 'EQUITY'

# URL encode the symbol to handle special characters
# Paytm expects the symbol to be in the format "exchange:symbol" E,g: NSE:335:EQUITY
# INDEX, EQUITY, ETF, FUTURE, OPTION
encoded_symbol = urllib.parse.quote(f"{exchange}:{token}:{opt_type}")

response = get_api_response(f"/data/v1/price/live?mode=FULL&pref={encoded_symbol}", self.auth_token)

if not response or not response.get('data', []):
raise Exception(f"Error from Paytm API: {response.get('message', 'Unknown error')}")


# Return quote data
quote = response.get('data', [])[0] if response.get('data') else {}
if not quote:
raise Exception("No quote data found")

depth = quote.get('depth', {})

# Format asks and bids data
asks = []
bids = []

# Process sell orders (asks)
sell_orders = depth.get('sell', [])
for i in range(5):
if i < len(sell_orders):
asks.append({
'price': sell_orders[i].get('price', 0),
'quantity': sell_orders[i].get('quantity', 0)
})
else:
asks.append({'price': 0, 'quantity': 0})

# Process buy orders (bids)
buy_orders = depth.get('buy', [])
for i in range(5):
if i < len(buy_orders):
bids.append({
'price': buy_orders[i].get('price', 0),
'quantity': buy_orders[i].get('quantity', 0)
})
else:
bids.append({'price': 0, 'quantity': 0})

# Return market depth data
return {
'asks': asks,
'bids': bids,
'high': quote.get('ohlc', {}).get('high', 0),
'low': quote.get('ohlc', {}).get('low', 0),
'ltp': quote.get('last_price', 0),
'ltq': quote.get('last_quantity', 0),
'oi': quote.get('oi', 0),
'open': quote.get('ohlc', {}).get('open', 0),
'prev_close': quote.get('ohlc', {}).get('close', 0),
'totalbuyqty': sum(order.get('quantity', 0) for order in buy_orders),
'totalsellqty': sum(order.get('quantity', 0) for order in sell_orders),
'volume': quote.get('volume', 0)
}

except Exception as e:
logger.error(f"Error fetching market depth: {str(e)}")
raise Exception(f"Error fetching market depth: {str(e)}")

def get_depth(self, symbol: str, exchange: str) -> dict:
"""Alias for get_market_depth to maintain compatibility with common API"""
return self.get_market_depth(symbol, exchange)

def get_history(self, symbol: str, exchange: str, timeframe: str, from_date: str, to_date: str) -> pd.DataFrame:
"""
Get historical data for given symbol and timeframe
Args:
symbol: Trading symbol
exchange: Exchange (e.g., NSE, BSE)
timeframe: Timeframe (e.g., 1m, 5m, 15m, 60m, D)
from_date: Start date in format YYYY-MM-DD
to_date: End date in format YYYY-MM-DD
Returns:
pd.DataFrame: Historical data with OHLCV
"""
raise NotImplementedError("Paytm does not support historical data API")
67 changes: 67 additions & 0 deletions broker/paytm/api/funds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# api/funds.py

import os
import http.client
import json
from broker.paytm.api.order_api import get_positions

def get_margin_data(auth_token):
print(auth_token)
"""Fetch margin data from Paytm API using the provided auth token."""
#api_key = os.getenv('BROKER_API_KEY')
conn = http.client.HTTPSConnection("developer.paytmmoney.com")
headers = {
'x-jwt-token': auth_token,
'Content-Type': 'application/json',
'Accept': 'application/json',
}

request_path = "/accounts/v1/funds/summary?config=true"
print(f"Making request to: https://{conn.host}{request_path}")
conn.request("GET", request_path, '', headers)


res = conn.getresponse()
data = res.read()
margin_data = json.loads(data.decode("utf-8"))

print(f"Funds Details: {margin_data}")


if margin_data.get('status') == 'error':
# Log the error or return an empty dictionary to indicate failure
print(f"Error fetching margin data: {margin_data.get('errors')}")
return {}

try:

position_book = get_positions(auth_token)

print(f'Positionbook : {position_book}')

#position_book = map_position_data(position_book)

def sum_realised_unrealised(position_book):
total_realised = 0
total_unrealised = 0
if isinstance(position_book.get('data', []), list):
for position in position_book['data']:
total_realised += float(position.get('realised_profit', 0))
# Since all positions are closed, unrealized profit is 0
total_unrealised += float(position.get('unrealised_profit', 0))
return total_realised, total_unrealised

total_realised, total_unrealised = sum_realised_unrealised(position_book)

# Construct and return the processed margin data
processed_margin_data = {
"availablecash": "{:.2f}".format(margin_data.get('data', {}).get('funds_summary', {}).get('available_cash', 0)),
"collateral": "{:.2f}".format(margin_data.get('data', {}).get('funds_summary', {}).get('collaterals', 0)),
"m2munrealized": "{:.2f}".format(total_unrealised),
"m2mrealized": "{:.2f}".format(total_realised),
"utiliseddebits": "{:.2f}".format(margin_data.get('data', {}).get('funds_summary', {}).get('utilised_amount', 0)),
}
return processed_margin_data
except KeyError:
# Return an empty dictionary in case of unexpected data structure
return {}
Loading

0 comments on commit 7c0257c

Please sign in to comment.