from flask import Flask, render_template, jsonify from engine import DataEngine import concurrent.futures from flask_caching import Cache import csv import os import logging app = Flask(__name__) # Configure caching cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'}) # Configure logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') # Function to load instruments from a CSV file def load_instruments_from_csv(file_path): instruments = [] try: abs_path = os.path.join(os.path.dirname(__file__), file_path) # 'utf-8-sig' handles the hidden characters Excel often adds with open(abs_path, mode='r', encoding='utf-8-sig') as csvfile: # Clean spaces from column names automatically reader = csv.DictReader(csvfile) reader.fieldnames = [name.strip().lower() for name in reader.fieldnames] for row in reader: symbol = row.get('symbol', '').strip() cusip = row.get('cusip', '').strip() if symbol and cusip: url = f"https://am.jpmorgan.com/FundsMarketingHandler/historicalData?cusip={cusip}&country=hk&role=per&userLoggedIn=false&language=en&version=6.9.0_1684" instruments.append({"symbol": symbol, "url": url}) logging.info(f"Successfully loaded {len(instruments)} instruments.") except Exception as e: logging.error(f"Error reading CSV file: {e}") return instruments # Load instruments from CSV URL_CONFIG = load_instruments_from_csv('instruments.csv') # Log the contents of URL_CONFIG to verify instruments are loaded logging.debug(f"Loaded instruments: {URL_CONFIG}") @cache.memoize(timeout=86400) # Cache results for 24 hours def fetch_and_calculate(config): try: logging.debug(f"Fetching data for: {config['symbol']}") engine = DataEngine(config['url']) df = engine.fetch_data() if df is None or df.empty: logging.warning(f"No data fetched for: {config['symbol']}") return None metrics = engine.calculate_table_metrics(df) if metrics: metrics['symbol'] = config['symbol'] logging.debug(f"Metrics calculated for {config['symbol']}: {metrics}") return metrics logging.warning(f"Metrics calculation failed for: {config['symbol']}") return None except Exception as e: logging.error(f"Error processing {config['symbol']}: {e}") return None @app.route('/') def index(): return render_template('index.html') @app.route('/api/summary') def get_summary(): results = [] # Use ThreadPoolExecutor for faster parallel fetching with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: future_to_config = {executor.submit(fetch_and_calculate, cfg): cfg for cfg in URL_CONFIG} for future in concurrent.futures.as_completed(future_to_config): res = future.result() if res: results.append(res) # Sort by symbol name results.sort(key=lambda x: x['symbol']) return jsonify(results) if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000) # Changed port back to 5000