Files
historical-prices/app.py
T

171 lines
5.9 KiB
Python

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 = DataEngine()
instruments = engine.load_instruments_from_csv('instruments.csv')
results = []
latest_mtime = 0 # To track the actual freshest data file
for item in instruments:
# Update engine target
engine.symbol = item['symbol'].strip().upper()
engine.file_path = os.path.join(engine.cache_dir, f"{engine.symbol}.csv")
# --- NEW: UPDATE LATEST_MTIME HERE ---
if os.path.exists(engine.file_path):
file_time = os.path.getmtime(engine.file_path)
if file_time > latest_mtime:
latest_mtime = file_time
# -------------------------------------
metrics = engine.get_local_metrics()
display_name = item.get('name') or engine.symbol
if metrics:
metrics.update({
'name': display_name,
'symbol': engine.symbol
})
results.append(metrics)
else:
# IMPORTANT: If metrics are None, we still need to send
# an object so the frontend knows the row exists!
results.append({
'name': display_name,
'symbol': engine.symbol,
'last_close': 'N/A',
'error': True
})
# Format the latest sync time found
if latest_mtime > 0:
last_mod_time = datetime.fromtimestamp(latest_mtime).strftime('%d/%b %H:%M:%S')
else:
last_mod_time = "Never"
return jsonify({
"last_sync": last_mod_time,
"data": results # Fixed the 'data_list' syntax error here
})
@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()
if not symbol:
return jsonify({"error": "Symbol is required"}), 400
try:
# 1. Initialize Engine (Uses the absolute path logic we fixed)
data_eng = DataEngine(symbol=symbol)
# 2. Smart Check: Request URL if file is missing
if not os.path.exists(data_eng.file_path):
print(f"--- ⚠️ {symbol} not found in cache. Triggering auto-fetch... ---")
# This will use the default Yahoo template if not in instruments.csv
data_eng.fetch_data()
# 3. Verify it worked before handing off to Strategy
if not os.path.exists(data_eng.file_path):
return jsonify({"error": f"Failed to retrieve data for {symbol}"}), 404
# 4. Run Strategy Logic (Restored)
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": f"Analysis failed: {str(e)}"}), 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)