Skip to content

Commit f173be1

Browse files
committed
added app files
1 parent 4709adf commit f173be1

18 files changed

Lines changed: 1352 additions & 57 deletions

File tree

app/__init__.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,41 @@
1-
from flask import Flask, render_template
2-
import pandas as pd
1+
app/__init__.py
2+
from flask import Flask
3+
from flask_login import LoginManager
4+
import os
35

46
def create_app():
57
app = Flask(__name__)
6-
7-
@app.route("/")
8-
def index():
9-
df = pd.DataFrame({
10-
"date": pd.date_range(end=pd.Timestamp.today(), periods=10),
11-
"price": range(100, 110)
12-
})
13-
return render_template("index.html", data=df.to_dict(orient="records"))
14-
8+
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key-change-in-production')
9+
10+
# Login Manager setup
11+
login_manager = LoginManager()
12+
login_manager.init_app(app)
13+
login_manager.login_view = 'auth.login'
14+
login_manager.login_message = 'Please log in to access the trading dashboard.'
15+
login_manager.login_message_category = 'info'
16+
17+
@login_manager.user_loader
18+
def load_user(user_id):
19+
from app.auth import User
20+
return User.get(user_id)
21+
22+
# Register Blueprints
23+
from app.routes.main import main_bp
24+
from app.routes.portfolio import portfolio_bp
25+
from app.routes.signals import signals_bp
26+
from app.routes.backtest import backtest_bp
27+
from app.routes.query import query_bp
28+
from app.routes.admin import admin_bp
29+
from app.routes.api import api_bp
30+
from app.routes.auth import auth_bp
31+
32+
app.register_blueprint(main_bp)
33+
app.register_blueprint(portfolio_bp, url_prefix='/portfolio')
34+
app.register_blueprint(signals_bp, url_prefix='/signals')
35+
app.register_blueprint(backtest_bp, url_prefix='/backtest')
36+
app.register_blueprint(query_bp, url_prefix='/query')
37+
app.register_blueprint(admin_bp, url_prefix='/admin')
38+
app.register_blueprint(api_bp, url_prefix='/api')
39+
app.register_blueprint(auth_bp, url_prefix='/auth')
40+
1541
return app

app/auth.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# app/auth.py
2+
"""
3+
Semplice sistema di autenticazione con password hash.
4+
In produzione, considera di usare un database per gli utenti.
5+
"""
6+
import hashlib
7+
import os
8+
from flask_login import UserMixin
9+
10+
class User(UserMixin):
11+
def __init__(self, username):
12+
self.id = username
13+
self.username = username
14+
15+
@staticmethod
16+
def get(username):
17+
# Lista utenti hardcoded - in produzione usare DB
18+
valid_users = ['admin', 'trader', 'analyst']
19+
if username in valid_users:
20+
return User(username)
21+
return None
22+
23+
@staticmethod
24+
def authenticate(username, password):
25+
"""
26+
Autentica utente con password.
27+
Password di default: 'trading123' (cambiarla in produzione!)
28+
"""
29+
user = User.get(username)
30+
if not user:
31+
return None
32+
33+
# Hash della password con salt
34+
salt = os.environ.get('PASSWORD_SALT', 'default_salt_change_me')
35+
expected_hash = hashlib.sha256((password + salt).encode()).hexdigest()
36+
37+
# Password di default per tutti gli utenti (in produzione: DB lookup)
38+
default_password = 'trading123'
39+
default_hash = hashlib.sha256((default_password + salt).encode()).hexdigest()
40+
41+
if expected_hash == default_hash:
42+
return user
43+
44+
return None

app/routes.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

app/routes/admin.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# app/routes/admin.py
2+
from flask import Blueprint, render_template, jsonify
3+
from flask_login import login_required
4+
5+
from scripts.database import execute_query
6+
from scripts.portfolio import create_portfolio_tables
7+
8+
admin_bp = Blueprint('admin', __name__)
9+
10+
@admin_bp.route('/')
11+
@login_required
12+
def index():
13+
"""Pagina amministrativa per gestione sistema"""
14+
try:
15+
# Statistiche database
16+
stats_queries = {
17+
'total_records': "SELECT COUNT(*) FROM universe",
18+
'unique_tickers': "SELECT COUNT(DISTINCT ticker) FROM universe",
19+
'date_range': "SELECT MIN(date), MAX(date) FROM universe",
20+
'last_update': "SELECT MAX(date) FROM universe",
21+
'portfolios_count': "SELECT COUNT(DISTINCT portfolio_name) FROM portfolio_snapshots"
22+
}
23+
24+
stats = {}
25+
for stat_name, query in stats_queries.items():
26+
try:
27+
result = execute_query(query)
28+
stats[stat_name] = result[0] if result else None
29+
except:
30+
stats[stat_name] = "Error"
31+
32+
return render_template("admin/index.html", stats=stats)
33+
34+
except Exception as e:
35+
return render_template("admin/index.html", error=str(e))
36+
37+
@admin_bp.route('/init_portfolio_tables', methods=['POST'])
38+
@login_required
39+
def init_portfolio_tables():
40+
"""Inizializza tabelle portfolio"""
41+
try:
42+
create_portfolio_tables()
43+
return jsonify({'success': True, 'message': 'Portfolio tables initialized'})
44+
except Exception as e:
45+
return jsonify({'error': str(e)}), 400

app/routes/api.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# app/routes/api.py
2+
from flask import Blueprint, jsonify, request
3+
from flask_login import login_required
4+
from datetime import datetime, timedelta
5+
6+
from scripts.database import get_universe_data, get_available_tickers
7+
8+
api_bp = Blueprint('api', __name__)
9+
10+
@api_bp.route('/ticker/<ticker>/chart')
11+
@login_required
12+
def ticker_chart_data(ticker):
13+
"""API per dati chart di un singolo ticker"""
14+
try:
15+
days = int(request.args.get('days', 30))
16+
end_date = datetime.now().strftime('%Y-%m-%d')
17+
start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
18+
19+
df = get_universe_data(start_date=start_date, end_date=end_date, tickers=[ticker])
20+
21+
if df.empty:
22+
return jsonify({'error': f'No data for {ticker}'}), 404
23+
24+
chart_data = [
25+
{
26+
'date': row['date'].strftime('%Y-%m-%d'),
27+
'open': row['Open'],
28+
'high': row['High'],
29+
'low': row['Low'],
30+
'close': row['Close'],
31+
'volume': row['Volume']
32+
}
33+
for _, row in df.iterrows()
34+
]
35+
36+
return jsonify({'data': chart_data, 'ticker': ticker})
37+
38+
except Exception as e:
39+
return jsonify({'error': str(e)}), 400
40+
41+
@api_bp.route('/tickers/search')
42+
@login_required
43+
def search_tickers():
44+
"""API per ricerca tickers"""
45+
query = request.args.get('q', '').upper()
46+
all_tickers = get_available_tickers()
47+
48+
if query:
49+
filtered = [t for t in all_tickers if query in t]
50+
else:
51+
filtered = all_tickers[:20] # Prime 20
52+
53+
return jsonify({'tickers': filtered})

app/routes/auth.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# app/routes/auth.py
2+
from flask import Blueprint, render_template, request, redirect, url_for, flash
3+
from flask_login import login_user, logout_user, login_required, current_user
4+
from app.auth import User
5+
6+
auth_bp = Blueprint('auth', __name__)
7+
8+
@auth_bp.route('/login', methods=['GET', 'POST'])
9+
def login():
10+
if current_user.is_authenticated:
11+
return redirect(url_for('main.index'))
12+
13+
if request.method == 'POST':
14+
username = request.form.get('username', '').strip().lower()
15+
password = request.form.get('password', '')
16+
remember = bool(request.form.get('remember'))
17+
18+
if not username or not password:
19+
flash('Username and password are required.', 'danger')
20+
return render_template('auth/login.html')
21+
22+
user = User.authenticate(username, password)
23+
if user:
24+
login_user(user, remember=remember)
25+
next_page = request.args.get('next')
26+
flash(f'Welcome back, {username}!', 'success')
27+
return redirect(next_page or url_for('main.index'))
28+
else:
29+
flash('Invalid username or password.', 'danger')
30+
31+
return render_template('auth/login.html')
32+
33+
@auth_bp.route('/logout')
34+
@login_required
35+
def logout():
36+
logout_user()
37+
flash('You have been logged out.', 'info')
38+
return redirect(url_for('auth.login'))

app/routes/backtest.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# app/routes/backtest.py
2+
from flask import Blueprint, render_template, request, jsonify
3+
from flask_login import login_required
4+
5+
from scripts.database import get_available_tickers
6+
from scripts import strategies
7+
from scripts.config import DEFAULT_STRATEGY_PARAMS
8+
from scripts.backtest import run_backtest
9+
10+
backtest_bp = Blueprint('backtest', __name__)
11+
12+
@backtest_bp.route('/')
13+
@login_required
14+
def index():
15+
"""Pagina per eseguire backtest"""
16+
try:
17+
# Strategie disponibili
18+
strategy_functions = []
19+
for name in dir(strategies):
20+
if not name.startswith('_') and callable(getattr(strategies, name)):
21+
strategy_functions.append(name)
22+
23+
tickers = get_available_tickers()
24+
25+
return render_template("backtest/index.html",
26+
strategies=strategy_functions,
27+
tickers=tickers,
28+
default_params=DEFAULT_STRATEGY_PARAMS)
29+
30+
except Exception as e:
31+
return render_template("backtest/index.html", error=str(e))
32+
33+
@backtest_bp.route('/run', methods=['POST'])
34+
@login_required
35+
def run_backtest_route():
36+
"""Esegue backtest di una strategia"""
37+
try:
38+
strategy_name = request.form.get('strategy')
39+
start_date = request.form.get('start_date')
40+
end_date = request.form.get('end_date')
41+
selected_tickers = request.form.getlist('tickers')
42+
initial_capital = float(request.form.get('initial_capital', 100000))
43+
44+
if not all([strategy_name, start_date, end_date, selected_tickers]):
45+
return jsonify({'error': 'All fields are required'}), 400
46+
47+
# Carica strategia
48+
strategy_fn = getattr(strategies, strategy_name)
49+
50+
# Parametri custom
51+
params = DEFAULT_STRATEGY_PARAMS.get(strategy_name, {}).copy()
52+
for key, default_value in params.items():
53+
form_value = request.form.get(f'param_{key}')
54+
if form_value:
55+
if isinstance(default_value, int):
56+
params[key] = int(form_value)
57+
elif isinstance(default_value, float):
58+
params[key] = float(form_value)
59+
60+
# Esegui backtest
61+
results = run_backtest(
62+
strategy_fn=strategy_fn,
63+
start_date=start_date,
64+
end_date=end_date,
65+
tickers=selected_tickers,
66+
initial_capital=initial_capital,
67+
**params
68+
)
69+
70+
# Prepara dati per visualizzazione
71+
portfolio_history = results['portfolio_history'].to_dict('records')
72+
trades_history = results['trades_history'].to_dict('records')
73+
74+
return jsonify({
75+
'success': True,
76+
'results': results['portfolio_metrics'],
77+
'trade_metrics': results['trade_metrics'],
78+
'portfolio_history': portfolio_history,
79+
'trades_history': trades_history,
80+
'strategy': strategy_name,
81+
'period': f"{start_date} to {end_date}"
82+
})
83+
84+
except Exception as e:
85+
return jsonify({'error': str(e)}), 400

0 commit comments

Comments
 (0)