from flask import Flask, render_template,request, jsonify from datetime import datetime, timedelta from engine import DataEngine, StrategyEngine import concurrent.futures from flask_caching import Cache import csv, os, logging from concurrent.futures import ThreadPoolExecutor import pandas as pd app = Flask(__name__) cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'}) # The CSV is in the root directory (same level as app.py) CSV_PATH = os.path.join(os.path.dirname(__file__), 'instruments.csv') @app.route('/settings') def settings(): instruments = [] last_updated = "Never" if os.path.exists(CSV_PATH): # 1. Get the last modified time from the Synology filesystem mtime = os.path.getmtime(CSV_PATH) last_updated = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S') # 2. Read the data df = pd.read_csv(CSV_PATH) df = df.fillna('') instruments = df.to_dict(orient='records') return render_template('settings.html', instruments=instruments, last_updated=last_updated) @app.route('/settings/save', methods=['POST']) def save_settings(): try: names = request.form.getlist('name[]') cusips = request.form.getlist('cusip[]') providers = request.form.getlist('provider[]') # Create a new DataFrame from the web form data new_data = { 'name': [s.strip() for s in names if s.strip()], 'cusip': [u.strip() for u in cusips if u.strip()], 'provider': [p.strip() for p in providers if p.strip()] } df = pd.DataFrame(new_data) # Save directly back to the root folder df.to_csv(CSV_PATH, index=False) return redirect('/settings') except Exception as e: print(f"Error saving CSV: {e}") return "Internal Server Error", 500 @cache.memoize(timeout=3600) def fetch_and_calculate(config): engine = DataEngine(config['symbol'], config['url'], config['provider']) df = engine.fetch_data() if df is not None: metrics = engine.calculate_table_metrics(df) if metrics: metrics['symbol'] = config['symbol'] return metrics return None @app.route('/') def index(): return render_template('index.html') @app.route('/api/summary') def get_summary(): engine_base = DataEngine() instruments = engine_base.load_instruments_from_csv('instruments.csv') results = [] for item in instruments: engine = DataEngine( symbol=item['symbol'], url=item['url'], provider=item['provider'] ) metrics = engine.get_local_metrics() if metrics and isinstance(metrics, dict): metrics['symbol'] = item['symbol'] results.append(metrics) else: # 🔥 FIX: Include the 'error' key so JavaScript hits the Gatekeeper results.append({ "symbol": item['symbol'], "last_close": None, # Ensure this is null, not the string "No Data" "error": True }) return jsonify(results) @app.route('/api/sync', methods=['POST']) def run_sync(): # ✅ THIS RUNS THE FULL fetch_data() WITH NETWORK ACCESS engine = DataEngine() report = engine.global_sync() return jsonify(report) @app.route('/api/backtest', methods=['POST']) def api_backtest(): data = request.get_json() or {} symbol = data.get('symbol', '').strip().upper() engine = DataEngine(symbol=symbol) if not symbol: return jsonify({"error": "Symbol is required"}), 400 try: # 1. Initialize the Engine # (The Engine's __init__ should handle looking up the URL in instruments.csv) data_eng = DataEngine(symbol=symbol) # 2. Trigger Smart Fetch # (Inside engine.py, this checks the 24h clock and updates if needed) data_eng.fetch_data() # 3. Verify data exists before proceeding if not os.path.exists(data_eng.file_path): return jsonify({"error": f"No data found for {symbol}"}), 404 # 4. Run Strategy strat_eng = StrategyEngine(data_eng) history = strat_eng.run_simulation( start_date=data.get('startDate', '2024-01-01'), monthly_goal=float(data.get('monthly_target', 0)), initial_inv=float(data.get('initial_inv', 0)), frequency=data.get('frequency', 'Monthly'), allow_sell=data.get('allow_sell') is True, allow_fractional=data.get('allow_fractional') is True ) return jsonify(history) except Exception as e: app.logger.error(f"Backtest Error: {str(e)}") return jsonify({"error": "Internal server error"}), 500 @app.route('/backtest') # This is the URL you will actually visit def backtest_ui(): # This sends the HTML file to your browser return render_template('val_avg_cal.html') if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)