base version only KD indicator with jpm, ft yahoo as sources

This commit is contained in:
2026-01-27 04:15:19 +08:00
commit 61b32bbdd7
12 changed files with 15992 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
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