-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #199 from naiduasn/paytm
Adding Paytm Integration to OpenAlgo
- Loading branch information
Showing
11 changed files
with
1,296 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} |
Oops, something went wrong.