ALGOblocks 3
ALGOblocks 3
import os
import json
import time
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session
load_dotenv()
app = Flask(__name__)
os.makedirs('data/saved_strategies', exist_ok=True)
ALPACA_SECRET_KEY = os.getenv('APCA_API_SECRET_KEY')
data_fetcher = DataFetcher(api)
backtest_engine = BacktestEngine(data_fetcher)
def login():
if 'user_email' in session:
return redirect(url_for('index'))
if request.method == 'POST':
email = request.form.get('email')
password = request.form.get('password')
if success:
return redirect(url_for('index'))
else:
flash(message, 'danger')
return render_template('login.html')
def register():
if 'user_email' in session:
return redirect(url_for('index'))
if request.method == 'POST':
email = request.form.get('email')
password = request.form.get('password')
username = request.form.get('username')
if success:
return redirect(url_for('verify'))
else:
flash(message, 'danger')
return render_template('register.html')
def verify():
if 'user_email' in session:
return redirect(url_for('index'))
if request.method == 'POST':
email = request.form.get('email')
code = request.form.get('code')
if success:
return redirect(url_for('login'))
else:
flash(message, 'danger')
return render_template('verify.html')
@app.route('/logout')
def logout():
logout_user()
flash('You have been logged out.', 'info')
return redirect(url_for('login'))
@app.route('/')
def index():
return redirect(url_for('login'))
return render_template('index.html')
@app.route('/portfolio')
def portfolio():
return redirect(url_for('login'))
email = session['user_email']
portfolio_data = get_user_portfolio(email)
if not portfolio_data:
return redirect(url_for('index'))
@app.route('/tutorial')
def tutorial():
return redirect(url_for('login'))
return render_template('tutorial.html')
def tutorial_content():
return jsonify(json.load(f))
@app.route('/api/tutorial/terms')
def tutorial_terms():
return jsonify(json.load(f))
@app.route('/api/markets', methods=['GET'])
def get_market_status():
try:
clock = api.get_clock()
return jsonify({
'is_open': clock.is_open,
'next_open': clock.next_open.isoformat(),
'next_close': clock.next_close.isoformat()
})
except Exception as e:
@app.route('/api/symbols', methods=['GET'])
def get_available_symbols():
try:
# Calculate pagination
return jsonify({
'symbols': paginated_symbols,
'total': len(symbols),
'page': page,
})
except Exception as e:
@app.route('/api/search-symbols', methods=['GET'])
def search_symbols():
assets = api.list_assets(status='active')
symbols = [
return jsonify({
'symbols': paginated_symbols,
'total': len(symbols),
'page': page,
})
except Exception as e:
@app.route('/api/historical-data', methods=['GET'])
def get_historical_data():
symbol = request.args.get('symbol', 'AAPL')
try:
return jsonify(data)
except Exception as e:
@app.route('/api/account', methods=['GET'])
def get_account():
try:
account = api.get_account()
return jsonify({
'cash': float(account.cash),
'equity': float(account.equity),
'buying_power': float(account.buying_power),
'portfolio_value': float(account.portfolio_value),
'status': account.status
})
except Exception as e:
@app.route('/api/positions', methods=['GET'])
def get_positions():
try:
positions = api.list_positions()
formatted_positions = []
for position in positions:
formatted_positions.append({
'symbol': position.symbol,
'qty': position.qty,
'avg_entry_price': position.avg_entry_price,
'current_price': position.current_price,
'market_value': position.market_value,
'unrealized_pl': position.unrealized_pl,
'unrealized_plpc': position.unrealized_plpc
})
return jsonify(formatted_positions)
except Exception as e:
@app.route('/api/backtest', methods=['POST'])
def run_backtest():
try:
if not request.is_json:
strategy_config = request.get_json()
blocks = strategy_config.get('blocks')
if isinstance(blocks, dict) and 'indicators' in blocks:
strategy = blocks
else:
parser = StrategyParser(blocks)
strategy = parser.parse_blocks()
results = backtest_engine.run_backtest(
strategy=strategy,
symbol=symbol,
start_date=start_date,
end_date=end_date,
initial_capital=initial_capital
return jsonify(results)
except Exception as e:
@app.route('/api/paper-trade', methods=['POST'])
def submit_paper_trade():
if not request.is_json:
email = session['user_email']
trade_data = request.json
if success:
return jsonify({
'success': True,
'data': result
})
else:
@app.route('/api/portfolio/update', methods=['GET'])
def update_portfolio():
email = session['user_email']
portfolio_data = get_user_portfolio(email)
if not portfolio_data:
return jsonify({
'success': True,
'portfolio': portfolio_data
})
@app.route('/api/save-strategy', methods=['POST'])
def save_strategy():
try:
if not request.is_json:
strategy_data = request.get_json()
if 'blocks' not in strategy_data or not strategy_data['blocks']:
strategy_data['user_email'] = session['user_email']
save_dir = 'data/saved_strategies'
try:
json.dump(strategy_data, f, indent=4)
except PermissionError:
except IOError:
return jsonify({
"success": True,
"filename": f"{strategy_name}.json"
})
except Exception as e:
def list_strategies():
try:
user_email = session['user_email']
strategy_files = os.listdir('data/saved_strategies')
strategies = []
if not filename.endswith('.json'):
continue
try:
strategy_data = json.load(f)
strategies.append(filename.replace('.json', ''))
except:
continue
except Exception as e:
@app.route('/api/load-strategy', methods=['GET'])
def load_strategy():
try:
strategy_name = request.args.get('name')
user_email = session['user_email']
if not strategy_name:
return list_strategies()
if not os.path.exists(filepath):
try:
strategy_data = json.load(f)
except json.JSONDecodeError:
except PermissionError:
return jsonify({"error": "You don't have permission to access this strategy"}), 403
return jsonify(strategy_data)
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(debug=True)
APCA_API_KEY_ID="PK2N1Y7GA"
APCA_API_SECRET_KEY="MnXGX930YGC8l0fje8NYPHAS"
SECRET_KEY="85b75e514785f10ad6a8b83eacb8ea6694a113d3aa1bed7ee60953"
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_PASSWORD="ozdr bkyq"
import smtplib
import os
import random
import string
import json
VERIFICATION_CODES = {}
USER_DATA_FILE = 'data/users.json'
os.makedirs(os.path.dirname(USER_DATA_FILE), exist_ok=True)
if not os.path.exists(USER_DATA_FILE):
json.dump({}, f)
def load_users():
try:
return json.load(f)
except:
return {}
def save_users(users):
json.dump(users, f, indent=4)
def generate_verification_code():
return True
message = MIMEMultipart()
message['From'] = smtp_user
message['To'] = email
# Email body
body = f"""
<html>
<body>
</body>
</html>
"""
message.attach(MIMEText(body, 'html'))
try:
server.starttls()
server.login(smtp_user, smtp_password)
server.send_message(message)
server.quit()
return True
except Exception as e:
return False
users = load_users()
if email in users:
code = generate_verification_code()
VERIFICATION_CODES[email] = code
if send_verification_email(email, code):
users[email] = {
'username': username,
'verified': False,
'portfolio': {
'cash': 10000.00,
'trades': []
save_users(users)
else:
users = load_users()
if email in users:
users[email]['verified'] = True
save_users(users)
del VERIFICATION_CODES[email]
else:
return False, "User not found"
"""Log in a user"""
users = load_users()
user = users[email]
if not user['verified']:
session['user_email'] = email
session['username'] = user['username']
def logout_user():
session.pop('user_email', None)
session.pop('username', None)
import pandas as pd
import numpy as np
import logging
class BacktestEngine:
"""
Args:
"""
self.data_fetcher = data_fetcher
self.logger = logging.getLogger(__name__)
"""
Args:
Returns:
dict: Backtest results including metrics and trades
"""
historical_data = self.data_fetcher.get_historical_data(
symbol=symbol,
timeframe='1D',
return {
'initial_capital': initial_capital,
'final_equity': initial_capital,
'total_return': 0.0,
'sharpe_ratio': 0.0,
'max_drawdown': 0.0,
'total_trades': 0,
'trades': [],
'equity_curve': [initial_capital]
# Convert to DataFrame
df = pd.DataFrame(historical_data)
df['time'] = pd.to_datetime(df['time'])
df.set_index('time', inplace=True)
# Filter by date range if provided
if start_date:
if end_date:
df = self._apply_indicators(df, strategy['indicators'])
df['position'] = 0
df['cash'] = initial_capital
df['shares'] = 0
df['equity'] = initial_capital
yesterday = df.iloc[i-1]
if yesterday['position'] == 0:
if entry_signal:
df.iloc[i, df.columns.get_loc('signal')] = 1
df.iloc[i, df.columns.get_loc('signal')] = -1
df = df.fillna(method='ffill')
df.loc[df.index[i], 'shares'] = 0
else: # No action
# Calculate equity
trades = []
for i in range(1, len(df)):
trades.append({
'date': df.index[i].strftime('%Y-%m-%d'),
'type': 'BUY',
'price': df['close'].iloc[i],
'shares': df['shares'].iloc[i],
})
trades.append({
'date': df.index[i].strftime('%Y-%m-%d'),
'type': 'SELL',
'price': df['close'].iloc[i],
'shares': df['shares'].iloc[i-1],
})
equity_curve = df['equity'].tolist()
# Return results
return {
'initial_capital': initial_capital,
'trades': trades,
"""
Args:
Returns:
"""
try:
if indicator['type'] == 'SMA':
period = indicator['parameters']['period']
df[f'SMA_{period}'] = df['close'].rolling(window=period).mean()
period = indicator['parameters']['period']
period = indicator['parameters']['period']
delta = df['close'].diff()
rs = gain / loss
rs = rs.fillna(0)
fast_period = indicator['parameters']['fast_period']
slow_period = indicator['parameters']['slow_period']
signal_period = indicator['parameters']['signal_period']
df[f'EMA_{fast_period}'] = df['close'].ewm(span=fast_period,
adjust=False).mean()
df[f'EMA_{slow_period}'] = df['close'].ewm(span=slow_period,
adjust=False).mean()
except Exception as e:
return df
def _evaluate_conditions(self, row, conditions):
"""
Args:
Returns:
"""
if not conditions:
return False
results = []
indicator = condition['indicator']
operator = condition['operator']
value = condition['value']
continue
indicator_value = row[indicator]
# Handle None or NaN values
if pd.isna(indicator_value):
continue
compare_value = None
compare_value = row[value]
else:
if value == 'close':
compare_value = row['close']
compare_value = row['open']
compare_value = row['high']
compare_value = row['low']
else:
try:
compare_value = float(value)
continue
# Handle None or NaN values in the comparison value
if pd.isna(compare_value):
continue
# Apply operator
try:
if operator == '>':
results.append(indicator_value == compare_value)
except Exception as e:
continue
if not results:
return False
return all(results)
Args:
Returns:
"""
equity = np.array(equity_curve)
running_max = np.maximum.accumulate(equity)
drawdowns = np.nan_to_num(drawdowns)
return max_drawdown
"""
Returns:
"""
returns = equity_series.pct_change().dropna()
return 0.0
return sharpe
import pandas as pd
import numpy as np
import logging
class DataFetcher:
self.api = api
self.logger = logging.getLogger(__name__)
"""
Args:
timeframe (str): Timeframe for the data ('1D', '1H', '15Min', '5Min', '1Min')
period (str): Time period to fetch ('1D', '1W', '1M', '3M', '6M', '1Y', '5Y')
Returns:
"""
cache_key = f"{symbol}_{timeframe}_{period}"
if cache_key in self.cache:
return data
# Calculate start and end dates based on period
end_date = datetime.now()
if period == '1D':
else: # '5Y'
start_str = start_date.strftime('%Y-%m-%d')
end_str = end_date.strftime('%Y-%m-%d')
timeframe_map = {
'1Min': '1Min',
'5Min': '5Min',
'15Min': '15Min',
'1H': '1Hour',
'1D': '1Day'
try:
try:
bars = self.api.get_bars(
symbol=symbol,
timeframe=alpaca_timeframe,
start=start_str,
end=end_str,
limit=1000
data = []
data.append({
'open': bar.o,
'high': bar.h,
'low': bar.l,
'close': bar.c,
'volume': bar.v
})
return data
except AttributeError:
bars = self.api.get_barset(
symbols=symbol,
timeframe=timeframe,
start=start_str,
end=end_str,
limit=1000
if symbol in bars:
data = []
data.append({
'open': bar.o,
'high': bar.h,
'low': bar.l,
'close': bar.c,
'volume': bar.v
})
return data
except Exception as e:
sample_data = self._get_sample_data(symbol)
return sample_data
end_date = datetime.now()
data = []
current_price = base_price
if trend == 'up':
drift = 0.0005
drift = -0.0005
else:
drift = 0.0
close_price = current_price
high_price = close_price * (1 + np.random.random() * volatility)
close_price = current_price
# Random volume
data.append({
'time': date.strftime('%Y-%m-%d'),
'volume': volume
})
return data
class StrategyParser:
"""
"""
self.blocks = blocks or []
def parse_blocks(self):
"""
Returns:
"""
indicators = []
entry_rules = []
exit_rules = []
if block.get('type') == 'indicator':
indicator_type = block.get('indicatorType')
if indicator_type == 'SMA':
indicators.append({
'type': 'SMA',
'parameters': {
}
})
indicators.append({
'type': 'EMA',
'parameters': {
})
indicators.append({
'type': 'RSI',
'parameters': {
})
indicators.append({
'type': 'MACD',
'parameters': {
'signal_period': block.get('signalPeriod', 9)
})
exit_rules.append(condition)
return {
'indicators': indicators,
'entry_rules': entry_rules,
'exit_rules': exit_rules
import os
import json
import logging
logger = logging.getLogger(__name__)
USER_DATA_FILE = 'data/users.json'
def load_users():
try:
return json.load(f)
except Exception as e:
def save_users(users):
try:
json.dump(users, f, indent=4)
return True
except Exception as e:
return False
"""
Args:
Returns:
"""
try:
users = load_users()
symbol = trade_data.get('symbol')
if not symbol:
if quantity <= 0:
portfolio = users[email]['portfolio']
trade = {
'timestamp': timestamp,
'symbol': symbol,
'quantity': quantity,
'side': side,
'type': order_type,
'status': 'executed',
'notes': notes
if side == 'buy':
else: # sell
portfolio['trades'].append(trade)
success = save_users(users)
if not success:
except Exception as e:
def get_user_portfolio(email):
"""
Args:
Returns:
"""
try:
users = load_users()
if email not in users:
return None
# Base stats
stats = {
'total_trades': len(trades),
'win_rate': 0.0,
'profit_loss': 0.0,
'active_positions': []
position_tracker = {}
profitable_trades = 0
symbol = trade['symbol']
side = trade['side']
quantity = trade['quantity']
price = trade['price']
# Track positions
if side == 'buy':
if symbol not in position_tracker:
position_tracker[symbol] = {
'quantity': 0,
'avg_price': 0,
'total_cost': 0
current = position_tracker[symbol]
position_tracker[symbol] = {
'quantity': new_quantity,
'total_cost': total_cost
if symbol in position_tracker:
current = position_tracker[symbol]
if current['quantity'] > 0:
stats['profit_loss'] += trade_pl
if trade_pl > 0:
profitable_trades += 1
# Update position
if new_quantity <= 0:
position_tracker[symbol] = {
'quantity': 0,
'avg_price': 0,
'total_cost': 0
else:
position_tracker[symbol] = {
'quantity': new_quantity,
'avg_price': current['avg_price'],
if len(trades) > 0:
if position['quantity'] > 0:
stats['active_positions'].append({
'symbol': symbol,
'quantity': position['quantity'],
'avg_price': position['avg_price'],
'total_cost': position['total_cost']
})
return {
'trades': trades,
'stats': stats
except Exception as e:
return None
"body": "This tutorial will guide you through building and backtesting trading strategies."
},
"body": "Use the symbol search to select a stock you want to trade."
},
"body": "Drag indicator and rule blocks onto the canvas to build your strategy."
},
"body": "Click on each block to configure its parameters (e.g., period for SMA)."
},
"body": "Click 'Run Backtest' to see how your strategy would have performed."
},
"body": "Use 'Paper Trade' to simulate real trades and track your portfolio."
"term": "Stock",
},
"term": "Indicator",
"definition": "A mathematical calculation based on price, volume, or open interest, used
to predict market direction."
},
"term": "Backtest",
},
"definition": "A graph showing the value of a trading account over time."
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
</a>
<span class="navbar-toggler-icon"></span>
</button>
<ul class="navbar-nav">
<li class="nav-item">
</li>
<li class="nav-item">
</li>
<li class="nav-item">
</li>
</ul>
<li class="nav-item">
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<span>Strategy Blocks</span>
</h5>
<div class="block-group">
<h6 class="fw-bold">Indicators</h6>
</div>
</div>
</div>
</div>
</div>
<div class="block-group">
<h6 class="fw-bold">Rules</h6>
</div>
</div>
</div>
</div>
<span>Backtest Settings</span>
</h5>
</button>
</div>
</select>
</div>
<div class="mb-3">
</div>
<div class="mb-3">
</div>
<div class="mb-3">
<div class="input-group">
<span class="input-group-text">$</span>
</div>
</div>
</button>
</button>
</form>
</div>
</div>
</button>
</button>
</button>
</div>
</div>
</div>
<div class="col-12">
<div class="canvas-placeholder">
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Price Chart</h5>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="card">
<div class="card-header">
</div>
<div class="card-body">
<div id="performanceMetrics">
</p>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
</div>
<div class="card-body">
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header">
</div>
<div class="card-body">
<div class="table-responsive">
<thead>
<tr>
<th>Date</th>
<th>Type</th>
<th>Price</th>
<th>Shares</th>
<th>Value</th>
</tr>
</thead>
<tbody id="tradesTableBody">
<tr>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-
dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
</div>
<div class="modal-body">
</div>
</div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
</div>
<div class="modal-body">
<form id="paperTradeForm">
<div class="mb-3">
</div>
<div class="mb-3">
</div>
<div class="mb-3">
<option value="buy">Buy</option>
<option value="sell">Sell</option>
</select>
</div>
<div class="mb-3">
<option value="market">Market</option>
<option value="limit">Limit</option>
</select>
</div>
<div class="input-group">
<span class="input-group-text">$</span>
</div>
</div>
<div class="mb-3">
</div>
</form>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></scrip
t>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login - AlgoBlocks</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body class="text-center">
<main class="form-signin">
<form method="POST">
{% if messages %}
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="form-floating">
</div>
<div class="form-floating">
<label for="password">Password</label>
</div>
</form>
</main>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></scrip
t>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Portfolio - AlgoBlocks</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body>
<div class="container-fluid">
</a>
<span class="navbar-toggler-icon"></span>
</button>
<ul class="navbar-nav">
<li class="nav-item">
</li>
<li class="nav-item">
</li>
<li class="nav-item">
</li>
</ul>
<li class="nav-item">
</li>
</a>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
</button>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
</div>
<div class="card-body">
</div>
</div>
</div>
<div class="col-md-6">
</div>
<div class="card-body">
<div class="metric-row">
</div>
<div class="metric-row">
</div>
<div class="metric-row">
<span class="metric-label">Profit/Loss</span>
${{ "%.2f"|format(portfolio.stats.profit_loss) }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header">
</div>
<div class="card-body">
<div class="table-responsive">
<thead>
<tr>
<th>Symbol</th>
<th>Quantity</th>
<th>Average Price</th>
<th>Current Value</th>
<th>Profit/Loss</th>
</tr>
</thead>
<tbody id="positionsTableBody">
{% if portfolio.stats.active_positions %}
<tr>
<td class="text-success">-</td>
</tr>
{% endfor %}
{% else %}
<tr>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
</div>
<div class="card-body">
<div class="table-responsive">
<thead>
<tr>
<th>Date</th>
<th>Symbol</th>
<th>Side</th>
<th>Quantity</th>
<th>Price</th>
<th>Value</th>
<th>Status</th>
<th>Notes</th>
</tr>
</thead>
<tbody id="tradesTableBody">
{% if portfolio.trades %}
<tr>
<td>
{{ trade.side|upper }}
</span>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></scrip
t>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Register - AlgoBlocks</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body class="text-center">
<main class="form-signup">
<form method="POST">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="form-floating">
<label for="username">Username</label>
</div>
<div class="form-floating">
</div>
<div class="form-floating">
<label for="password">Password</label>
</div>
</form>
</main>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></scrip
t>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tutorial - AlgoBlocks</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body>
<div class="container-fluid">
</a>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
</li>
<li class="nav-item">
</li>
<li class="nav-item">
</li>
</ul>
<li class="nav-item">
</li>
</a>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="row">
<div class="col-md-7">
</div>
</div>
</div>
<div class="col-md-5">
<div class="card">
<div class="card-header">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></scrip
t>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body class="text-center">
<main class="form-verify">
<form method="POST">
{% if messages %}
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-
label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
</div>
</p>
</form>
</main>
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></scrip
t>
</body>
</html>
// static/js/portfolio.js
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
fetchMarketStatus();
if (refreshBtn) {
refreshBtn.addEventListener('click', refreshPortfolio);
function fetchMarketStatus() {
fetch('/api/markets')
.then(data => {
if (data.is_open) {
marketStatus.innerHTML = 'Market Status: <span class="text-
success">Open</span>';
} else {
})
.catch(error => {
});
function refreshPortfolio() {
refreshBtn.disabled = true;
fetch('/api/portfolio/update')
.then(data => {
if (data.error) {
return;
}
updatePortfolioUI(data.portfolio);
})
.catch(error => {
})
.finally(() => {
// Reset button
refreshBtn.disabled = false;
});
function updatePortfolioUI(portfolio) {
if (!portfolio) return;
cashBalance.textContent = `$${formatNumber(portfolio.cash)}`;
// Update statistics
if (portfolio.stats) {
totalTrades.textContent = portfolio.stats.total_trades;
winRate.textContent = `${formatNumber(portfolio.stats.win_rate)}%`;
const pl = portfolio.stats.profit_loss;
profitLoss.textContent = `$${formatNumber(pl)}`;
profitLoss.className = pl > 0 ? 'metric-value positive' : pl < 0 ? 'metric-value negative'
: 'metric-value';
if (portfolio.stats.active_positions.length > 0) {
portfolio.stats.active_positions.forEach(position => {
positionsHTML += `
<tr>
<td>${position.symbol}</td>
<td>${position.quantity}</td>
<td>$${formatNumber(position.avg_price)}</td>
<td>$${formatNumber(position.total_cost)}</td>
<td class="text-success">-</td>
</tr>
`;
});
positionsTableBody.innerHTML = positionsHTML;
} else {
portfolio.trades.slice().reverse().forEach(trade => {
tradesHTML += `
<tr>
<td>${trade.timestamp}</td>
<td>${trade.symbol}</td>
<td>
${trade.side.toUpperCase()}
</span>
</td>
<td>${trade.quantity}</td>
<td>$${formatNumber(trade.price)}</td>
<td>$${formatNumber(trade.price * trade.quantity)}</td>
<td>${trade.status}</td>
<td>${trade.notes || ''}</td>
</tr>
`;
});
tradesTableBody.innerHTML = tradesHTML;
} else {
}
}
function formatDateTime(date) {
return date.toLocaleString();
function formatNumber(value) {
return parseFloat(value).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
});
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
// Block management
let nextBlockId = 1;
oneYearAgo.setFullYear(today.getFullYear() - 1);
startDateInput.value = formatDate(oneYearAgo);
endDateInput.value = formatDate(today);
container_id: 'tradingViewChart',
symbol: 'AAPL',
interval: 'D',
timezone: 'Etc/UTC',
theme: 'light',
style: '1',
locale: 'en',
toolbar_bg: '#f1f3f6',
enable_publishing: false,
hide_top_toolbar: false,
hide_legend: false,
save_image: false,
height: '100%',
width: '100%'
});
fetchMarketStatus();
setupDragAndDrop();
runBacktestBtn.addEventListener('click', runBacktest);
paperTradeBtn.addEventListener('click', startPaperTrading);
saveStrategyBtn.addEventListener('click', saveStrategy);
loadStrategyBtn.addEventListener('click', loadStrategy);
clearStrategyBtn.addEventListener('click', clearStrategy);
symbolSelect.addEventListener('change', updateSymbol);
searchSymbolBtn.addEventListener('click', searchSymbols);
symbolSearch.addEventListener('keyup', function(e) {
if (e.key === 'Enter') {
searchSymbols();
});
saveBlockConfigBtn.addEventListener('click', saveBlockConfig);
function searchSymbols() {
fetch(`/api/search-symbols?query=${encodeURIComponent(query)}&per_page=100`)
.then(data => {
if (data.error) {
return;
}
// Update symbol select with results
symbolSelect.innerHTML = '';
data.symbols.forEach(item => {
option.value = item.symbol;
symbolSelect.appendChild(option);
});
updateSymbol();
} else {
})
.catch(error => {
});
function fetchMarketStatus() {
fetch('/api/markets')
.then(data => {
if (data.is_open) {
marketStatus.innerHTML = 'Market Status: <span class="text-
success">Open</span>';
} else {
})
.catch(error => {
});
function updateSymbol() {
if (symbol) {
tradingViewChart.setSymbol(symbol);
function setupDragAndDrop() {
draggableBlocks.forEach(block => {
block.addEventListener('dragstart', handleDragStart);
});
strategyCanvas.addEventListener('dragover', handleDragOver);
strategyCanvas.addEventListener('drop', handleDrop);
function handleDragStart(e) {
e.dataTransfer.setData('text/plain', JSON.stringify({
type: this.dataset.type,
}));
function handleDragOver(e) {
e.preventDefault();
// Handle drop
function handleDrop(e) {
e.preventDefault();
try {
const data = JSON.parse(e.dataTransfer.getData('text/plain'));
} catch (err) {
block.id = blockId;
block.style.left = `${x}px`;
block.style.top = `${y}px`;
let blockData = {
id: blockId,
type: type,
x: x,
y: y
};
blockData.indicatorType = indicatorType;
switch (indicatorType) {
case 'SMA':
blockData.period = 20;
break;
case 'EMA':
blockData.period = 20;
break;
case 'RSI':
blockData.period = 14;
break;
case 'MACD':
blockData.fastPeriod = 12;
blockData.slowPeriod = 26;
blockData.signalPeriod = 9;
blockTitle = 'MACD';
break;
default:
blockTitle = indicatorType;
blockSettings = '';
blockData.conditions = [];
block.innerHTML = `
<div class="block-header">
<span>${blockTitle}</span>
</div>
<div class="block-content">
<div class="block-settings">${blockSettings}</div>
</div>
`;
block.draggable = true;
block.addEventListener('dragstart', function(e) {
e.dataTransfer.setData('text/plain', JSON.stringify({
id: this.id,
offsetX: offsetX,
offsetY: offsetY
}));
});
block.addEventListener('click', function() {
const id = this.id;
if (block) {
selectedBlock = block;
showBlockConfig(block);
});
block.querySelector('.block-remove').addEventListener('click', function(e) {
e.stopPropagation();
removeBlock(this.dataset.id);
});
strategyCanvas.appendChild(block);
blocks.push(blockData);
if (placeholder) {
placeholder.style.display = 'none';
function showBlockConfig(block) {
content = `
<div class="mb-3">
<label class="form-label">Period</label>
</div>
`;
content = `
<div class="mb-3">
</div>
<div class="mb-3">
</div>
<div class="mb-3">
</div>
`;
indicatorId = `RSI_${b.period}`;
indicatorId = 'MACD';
if (indicatorId) {
});
const priceOptions = `
`;
content = `
<div class="mb-3">
<label class="form-label">Indicator</label>
${priceOptions}
${indicatorOptions}
</select>
</div>
<div class="mb-3">
<label class="form-label">Operator</label>
</select>
</div>
<div class="mb-3">
<label class="form-label">Value</label>
</div>
`;
blockConfigBody.innerHTML = content;
blockConfigModal.show();
if (!selectedBlock) return;
selectedBlock.period = period;
selectedBlock.fastPeriod = fastPeriod;
selectedBlock.slowPeriod = slowPeriod;
selectedBlock.signalPeriod = signalPeriod;
if (indicator) {
if (!selectedBlock.conditions) {
selectedBlock.conditions = [];
if (selectedBlock.conditions.length === 0) {
} else {
blockElement.querySelector('.block-settings').textContent = `${indicator}
${operator} ${value}`;
blockConfigModal.hide();
}
// Remove a block from the canvas
function removeBlock(blockId) {
if (blockElement) {
blockElement.remove();
if (blocks.length === 0) {
if (placeholder) {
placeholder.style.display = 'block';
function runBacktest() {
if (blocks.length === 0) {
return;
fetch('/api/backtest', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
blocks: strategy,
symbol: symbolSelect.value,
startDate: startDateInput.value,
endDate: endDateInput.value,
capital: initialCapitalInput.value
})
})
.then(results => {
displayBacktestResults(results);
})
.catch(error => {
});
}
// Build strategy configuration from blocks
function buildStrategyConfig() {
indicators.push({
type: 'SMA',
parameters: {
period: block.period
});
indicators.push({
type: 'EMA',
parameters: {
period: block.period
});
indicators.push({
type: 'RSI',
parameters: {
period: block.period
}
});
indicators.push({
type: 'MACD',
parameters: {
fast_period: block.fastPeriod,
slow_period: block.slowPeriod,
signal_period: block.signalPeriod
});
});
block.conditions.forEach(condition => {
entryRules.push(condition);
});
});
block.conditions.forEach(condition => {
exitRules.push(condition);
});
});
return {
indicators: indicators,
entry_rules: entryRules,
exit_rules: exitRules
};
function displayBacktestResults(results) {
try {
if (results.error) {
} else {
metricsHtml = `
<div class="metric-row">
<span class="metric-value">$${formatNumber(results.initial_capital)}</span>
</div>
<div class="metric-row">
<span class="metric-value">$${formatNumber(results.final_equity)}</span>
</div>
<div class="metric-row">
${formatNumber(results.total_return)}%
</span>
</div>
<div class="metric-row">
<span class="metric-value">${formatNumber(results.sharpe_ratio)}</span>
</div>
<div class="metric-row">
<span class="metric-value
negative">${formatNumber(results.max_drawdown)}%</span>
</div>
<div class="metric-row">
<span class="metric-value">${results.total_trades}</span>
</div>
`;
performanceMetrics.innerHTML = metricsHtml;
results.trades.forEach(trade => {
return;
}
tradesHtml += `
<tr>
<td>${trade.date}</td>
<td>${trade.type}</td>
<td>$${formatNumber(trade.price)}</td>
<td>${trade.shares}</td>
</tr>
`;
});
if (tradesHtml) {
tradesTableBody.innerHTML = tradesHtml;
} else {
} else {
updateEquityCurveChart(results.equity_curve);
} catch (error) {
function updateEquityCurveChart(equityCurve) {
return;
date.setDate(date.getDate() + index);
return date.toLocaleDateString();
});
if (chartInstance) {
chartInstance.destroy();
try {
type: 'line',
data: {
labels: dates,
datasets: [{
data: equityCurve,
fill: true,
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: false,
title: {
display: true,
},
ticks: {
callback: function(value) {
},
x: {
title: {
display: true,
text: 'Date'
},
ticks: {
maxTicksLimit: 10
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
});
} catch (error) {
function startPaperTrading() {
if (blocks.length === 0) {
return;
document.getElementById('tradeSymbol').value = symbolSelect.value;
modal.show();
document.getElementById('submitTradeBtn').onclick = function() {
price = parseFloat(document.getElementById('limitPrice').value);
return;
fetch('/api/paper-trade', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
symbol: symbol,
quantity: quantity,
side: side,
orderType: orderType,
price: price,
notes: notes
})
})
.then(result => {
if (result.error) {
alert(`Error: ${result.error}`);
} else {
modal.hide();
})
.catch(error => {
});
};
document.getElementById('tradeType').addEventListener('change', function() {
limitPriceGroup.classList.remove('d-none');
} else {
limitPriceGroup.classList.add('d-none');
});
function saveStrategy() {
if (blocks.length === 0) {
return;
}
const strategyName = prompt('Enter a name for this strategy:');
if (!strategyName) return;
const strategy = {
name: strategyName,
blocks: blocks,
symbol: symbolSelect.value,
};
fetch('/api/save-strategy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(strategy)
})
.then(result => {
if (result.error) {
alert(`Error: ${result.error}`);
} else {
})
.catch(error => {
});
function loadStrategy() {
fetch('/api/list-strategies')
.then(data => {
if (data.error) {
alert(`Error: ${data.error}`);
return;
return;
strategyList.innerHTML = '';
item.href = '#';
item.textContent = strategy;
item.onclick = function(e) {
e.preventDefault();
loadStrategyByName(strategy);
bootstrap.Modal.getInstance(loadStrategyModal).hide();
};
strategyList.appendChild(item);
});
// Show modal
modal.show();
})
.catch(error => {
});
function loadStrategyByName(strategyName) {
fetch(`/api/load-strategy?name=${encodeURIComponent(strategyName)}`)
.then(strategyData => {
if (strategyData.error) {
alert(`Error: ${strategyData.error}`);
return;
clearStrategy();
// Restore blocks
strategyData.blocks.forEach(block => {
Object.assign(newBlock, block);
if (blockElement) {
} else {
blockElement.querySelector('.block-settings').textContent = settingsText;
});
if (strategyData.symbol) {
if (option) {
symbolSelect.value = strategyData.symbol;
updateSymbol();
})
.catch(error => {
});
function clearStrategy() {
blocks.forEach(block => {
if (blockElement) {
blockElement.remove();
});
blocks = [];
// Show placeholder
if (placeholder) {
placeholder.style.display = 'block';
// Clear results
// Clear chart
if (chartInstance) {
chartInstance.destroy();
chartInstance = null;
function formatDate(date) {
return `${year}-${month}-${day}`;
function formatDateTime(date) {
return date.toLocaleString();
function formatNumber(value) {
return parseFloat(value).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
});
document.addEventListener('DOMContentLoaded', function() {
fetchMarketStatus();
fetch('/api/tutorial/content')
.then(data => {
</div>
<div class="card-body">${step.body}</div>
</div>
`).join('');
})
.catch(error => {
document.getElementById('tutorialSteps').innerHTML = `
`;
});
// Load terms
fetch('/api/tutorial/terms')
.then(data => {
<div class="mb-3">
<strong>${term.term}:</strong> ${term.definition}
</div>
`).join('');
})
.catch(error => {
document.getElementById('termsList').innerHTML = `
</div>
`;
});
function fetchMarketStatus() {
fetch('/api/markets')
.then(response => response.json())
.then(data => {
if (data.is_open) {
} else {
})
.catch(error => {
});
function formatDateTime(date) {
return date.toLocaleString();
});
/* Auth-specific styles */
html,
body {
height: 100%;
}
body {
display: flex;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
font-weight: 400;
.form-signin .form-floating:focus-within,
.form-signup .form-floating:focus-within,
.form-verify .form-floating:focus-within {
z-index: 2;
.form-signin input[type="email"],
.form-signup input[type="text"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
.form-signin input[type="password"],
.form-signup input[type="email"] {
margin-bottom: -1px;
border-radius: 0;
.form-signup input[type="password"],
.form-verify input[type="text"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
.form-verify input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
.bi {
color: #0d6efd;
.btn-primary {
margin-top: 10px;
.bd-placeholder-img-lg {
font-size: 3.5rem;
/* General styles */
body {
background-color: #f8f9fa;
/* Sidebar */
.sidebar {
position: fixed;
bottom: 0;
left: 0;
z-index: 100;
padding: 0;
overflow-y: auto;
.sidebar-heading {
font-size: 0.85rem;
text-transform: uppercase;
/* Block styles */
.block-container {
margin-bottom: 20px;
.block-group {
margin-bottom: 15px;
.block {
padding: 10px;
margin-bottom: 5px;
border-radius: 4px;
cursor: move;
user-select: none;
font-size: 0.9rem;
.indicator-block {
background-color: #e3f2fd;
.indicator-block:hover {
background-color: #bbdefb;
.rule-block {
background-color: #f1f8e9;
.rule-block:hover {
background-color: #dcedc8;
.entry-block {
}
.exit-block {
/* Strategy canvas */
.strategy-canvas {
min-height: 300px;
background-color: #fff;
border-radius: 4px;
position: relative;
padding: 15px;
margin-bottom: 15px;
.canvas-placeholder {
position: absolute;
top: 50%;
left: 50%;
text-align: center;
color: #aaa;
.canvas-placeholder i {
display: block;
margin-bottom: 10px;
}
/* Canvas blocks */
.canvas-block {
position: absolute;
width: 200px;
border-radius: 4px;
background-color: #fff;
z-index: 10;
cursor: move;
.canvas-block .block-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
font-size: 0.9rem;
.canvas-block .block-content {
padding: 10px;
.canvas-block .block-settings {
font-size: 0.85rem;
color: #555;
.canvas-block .block-remove {
cursor: pointer;
font-size: 1.2rem;
margin-left: 5px;
color: #999;
.canvas-block .block-remove:hover {
color: #f44336;
.canvas-block.indicator {
.canvas-block.entry {
.canvas-block.exit {
/* Metrics */
.metric-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
padding-bottom: 8px;
.metric-row:last-child {
border-bottom: none;
.metric-label {
font-weight: 500;
color: #555;
.metric-value {
font-weight: 600;
.metric-value.positive {
color: #4caf50;
.metric-value.negative {
color: #f44336;
}
/* Responsive adjustments */
.sidebar {
position: static;
height: auto;
.strategy-canvas {
min-height: 200px;
/* Chart responsiveness */
#tradingViewChart {
width: 100%;
.card {
margin-bottom: 20px;
border: none;
.card-header {
background-color: #f8f9fa;
.card-title {
margin-bottom: 0;
font-weight: 500;
.table-responsive {
margin-bottom: 0;
/* Navbar customization */
.navbar-brand i {
color: #4285f4;
.nav-link.active {
font-weight: 500;