Spaces:
Sleeping
Sleeping
| """ | |
| 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 = {} | |
| def index(): | |
| """Serve the main React app""" | |
| return send_from_directory(app.static_folder, 'index.html') | |
| 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') | |
| 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) | |
| 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) | |
| 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) | |
| 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 | |
| }) | |
| 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) | |
| 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) | |
| 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 | |
| def handle_connect(): | |
| """Handle client connection""" | |
| sid = request.sid | |
| client_subscriptions[sid] = {} | |
| emit('connected', {'status': 'connected', 'sid': sid}) | |
| 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] | |
| 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)) | |
| }) | |
| 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) | |