SmartTrade / backend /app.py
Sanyam400's picture
Upload 4 files
bac132b verified
"""
ProTrade Charting Platform - Flask Backend
Handles API endpoints and WebSocket connections for real-time data.
"""
import os
import sys
import json
import time
import threading
from datetime import datetime
from flask import Flask, send_from_directory, jsonify, request
from flask_cors import CORS
from flask_socketio import SocketIO, emit
# Add current directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from data_feeds import data_feed_manager
from indicators import calculate_all_indicators, calculate_volume_profile, calculate_fair_value_gaps
import pandas as pd
# Create Flask app - static folder is relative to backend/ folder
import os as os_module
static_path = os_module.path.join(os_module.path.dirname(os_module.path.abspath(__file__)), '..', 'dist')
app = Flask(__name__, static_folder=static_path, static_url_path='')
CORS(app)
# SocketIO with eventlet for production
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet',
ping_timeout=60, ping_interval=25)
# Client subscription tracking
client_subscriptions = {}
@app.route('/')
def index():
"""Serve the main React app"""
return send_from_directory(app.static_folder, 'index.html')
@app.route('/<path:path>')
def serve_static(path):
"""Serve static files"""
if os.path.exists(os.path.join(app.static_folder, path)):
return send_from_directory(app.static_folder, path)
return send_from_directory(app.static_folder, 'index.html')
@app.route('/api/search')
def search_symbols():
"""Search for symbols across markets"""
query = request.args.get('q', '')
market_type = request.args.get('market', 'us_stock')
if not query:
return jsonify([])
results = data_feed_manager.search_symbols(query, market_type)
return jsonify(results)
@app.route('/api/symbol-info')
def symbol_info():
"""Get detailed symbol information"""
symbol = request.args.get('symbol', '')
market_type = request.args.get('market', 'us_stock')
if not symbol:
return jsonify({'error': 'Symbol required'}), 400
info = data_feed_manager.get_symbol_info(symbol, market_type)
return jsonify(info)
@app.route('/api/historical')
def get_historical():
"""Get historical OHLCV data"""
symbol = request.args.get('symbol', '')
market_type = request.args.get('market', 'us_stock')
timeframe = request.args.get('timeframe', '1d')
if not symbol:
return jsonify({'error': 'Symbol required'}), 400
if market_type == 'crypto':
candles = data_feed_manager.fetch_hyperliquid_data(symbol, timeframe)
else:
candles = data_feed_manager.fetch_yfinance_data(symbol, market_type, timeframe)
data = [{
'time': c.time,
'open': c.open,
'high': c.high,
'low': c.low,
'close': c.close,
'volume': c.volume
} for c in candles]
return jsonify(data)
@app.route('/api/indicators')
def get_indicators():
"""Calculate technical indicators"""
symbol = request.args.get('symbol', '')
market_type = request.args.get('market', 'us_stock')
timeframe = request.args.get('timeframe', '1d')
if not symbol:
return jsonify({'error': 'Symbol required'}), 400
# Parse indicator config from request
indicators_config = request.args.get('indicators', '[]')
try:
indicators_config = json.loads(indicators_config)
except:
indicators_config = []
if market_type == 'crypto':
candles = data_feed_manager.fetch_hyperliquid_data(symbol, timeframe)
else:
candles = data_feed_manager.fetch_yfinance_data(symbol, market_type, timeframe)
if not candles:
return jsonify({'error': 'No data available'}), 404
# Convert to DataFrame
df = pd.DataFrame([{
'open': c.open,
'high': c.high,
'low': c.low,
'close': c.close,
'volume': c.volume
} for c in candles])
df.index = pd.to_datetime([c.time * 1000 for c in candles], unit='ms')
# Calculate indicators
results = calculate_all_indicators(df, {'indicators': indicators_config})
# Add timestamps
for key in results:
if key in ['macd']:
for k in results[key]:
results[key][k] = [None if pd.isna(v) else v for v in results[key][k]]
elif key not in ['volume_profile', 'fvg']:
if isinstance(results[key], list):
results[key] = [None if pd.isna(v) else v for v in results[key]]
return jsonify({
'timestamps': [c.time for c in candles],
'indicators': results
})
@app.route('/api/volume-profile')
def get_volume_profile():
"""Get Volume Profile analysis"""
symbol = request.args.get('symbol', '')
market_type = request.args.get('market', 'us_stock')
timeframe = request.args.get('timeframe', '1d')
if not symbol:
return jsonify({'error': 'Symbol required'}), 400
if market_type == 'crypto':
candles = data_feed_manager.fetch_hyperliquid_data(symbol, timeframe)
else:
candles = data_feed_manager.fetch_yfinance_data(symbol, market_type, timeframe)
if not candles:
return jsonify({'error': 'No data available'}), 404
df = pd.DataFrame([{
'open': c.open,
'high': c.high,
'low': c.low,
'close': c.close,
'volume': c.volume
} for c in candles])
vp = calculate_volume_profile(df)
return jsonify(vp)
@app.route('/api/fvg')
def get_fvg():
"""Get Fair Value Gaps"""
symbol = request.args.get('symbol', '')
market_type = request.args.get('market', 'us_stock')
timeframe = request.args.get('timeframe', '1d')
if not symbol:
return jsonify({'error': 'Symbol required'}), 400
if market_type == 'crypto':
candles = data_feed_manager.fetch_hyperliquid_data(symbol, timeframe)
else:
candles = data_feed_manager.fetch_yfinance_data(symbol, market_type, timeframe)
if not candles:
return jsonify({'error': 'No data available'}), 404
df = pd.DataFrame([{
'open': c.open,
'high': c.high,
'low': c.low,
'close': c.close,
'volume': c.volume
} for c in candles])
df.index = pd.to_datetime([c.time * 1000 for c in candles], unit='ms')
fvg = calculate_fair_value_gaps(df)
return jsonify(fvg)
@app.route('/api/market-status')
def market_status():
"""Get market status for a symbol"""
symbol = request.args.get('symbol', '')
market_type = request.args.get('market', 'us_stock')
if market_type == 'crypto':
return jsonify({'status': 'open', 'message': '24/7 Market'})
# Check US market hours (9:30 AM - 4:00 PM ET, Mon-Fri)
now = datetime.now()
weekday = now.weekday()
if weekday >= 5: # Weekend
return jsonify({'status': 'closed', 'message': 'Market Closed - Weekend'})
# Rough check - not accounting for timezone differences
hour = now.hour
if 14 <= hour <= 21: # Approximate US market hours in UTC
return jsonify({'status': 'open', 'message': 'Market Open'})
else:
return jsonify({'status': 'closed', 'message': 'Market Closed'})
# Socket.IO Events
@socketio.on('connect')
def handle_connect():
"""Handle client connection"""
sid = request.sid
client_subscriptions[sid] = {}
emit('connected', {'status': 'connected', 'sid': sid})
@socketio.on('disconnect')
def handle_disconnect():
"""Handle client disconnection"""
sid = request.sid
if sid in client_subscriptions:
for key in client_subscriptions[sid]:
data_feed_manager.unsubscribe(key)
del client_subscriptions[sid]
@socketio.on('subscribe')
def handle_subscribe(data):
"""Subscribe to real-time data feed"""
sid = request.sid
symbol = data.get('symbol', '')
market_type = data.get('marketType', 'us_stock')
timeframe = data.get('timeframe', '1d')
chart_id = data.get('chartId', 'default')
if not symbol:
emit('error', {'message': 'Symbol required'})
return
def on_data(candles):
"""Callback for new data"""
formatted = [{
'time': c.time,
'open': c.open,
'high': c.high,
'low': c.low,
'close': c.close,
'volume': c.volume
} for c in candles]
socketio.emit('data_update', {
'chartId': chart_id,
'symbol': symbol,
'timeframe': timeframe,
'data': formatted
}, room=sid)
key = data_feed_manager.subscribe(symbol, market_type, timeframe, on_data)
if sid not in client_subscriptions:
client_subscriptions[sid] = {}
client_subscriptions[sid][chart_id] = key
emit('subscribed', {
'chartId': chart_id,
'symbol': symbol,
'timeframe': timeframe,
'dataPoints': len(data_feed_manager.get_latest_data(key))
})
@socketio.on('unsubscribe')
def handle_unsubscribe(data):
"""Unsubscribe from a data feed"""
sid = request.sid
chart_id = data.get('chartId', 'default')
if sid in client_subscriptions and chart_id in client_subscriptions[sid]:
key = client_subscriptions[sid][chart_id]
data_feed_manager.unsubscribe(key)
del client_subscriptions[sid][chart_id]
emit('unsubscribed', {'chartId': chart_id})
def start_polling():
"""Start background polling for data updates"""
while True:
try:
with data_feed_manager.lock:
subs = list(data_feed_manager.subscriptions.items())
for key, sub in subs:
if sub.market_type == 'crypto':
# Refresh crypto data
new_data = data_feed_manager.fetch_hyperliquid_data(
sub.symbol, sub.timeframe
)
else:
# Refresh stock data
new_data = data_feed_manager.fetch_yfinance_data(
sub.symbol, sub.market_type, sub.timeframe
)
if new_data and len(new_data) > 0:
data_feed_manager.update_data(key, new_data)
except Exception as e:
print(f"Polling error: {e}")
time.sleep(10) # Poll every 10 seconds
# Start background polling thread
polling_thread = threading.Thread(target=start_polling, daemon=True)
polling_thread.start()
if __name__ == '__main__':
port = int(os.environ.get('PORT', 7860))
socketio.run(app, host='0.0.0.0', port=port, debug=False)