ALGO1
ALGO1
├── data
│ ├── saved_strategies/
│ └── historical_data.csv
├── static
│ ├── CSS
│ │ └── style.css
│ └── js
│ └── script.js
├── templates
│ └── index.html
├── utils
│ ├── __pycache__/
│ ├── __init__.py
│ ├── backtest_engine.py
│ ├── data_fetcher.py
│ └── strategy_parser.py
├── .env
├── app.py
└── requirements.txt
App.py
import os
import json
import time
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
load_dotenv()
app = Flask(__name__)
ALPACA_API_KEY = os.getenv('APCA_API_KEY_ID')
ALPACA_SECRET_KEY = os.getenv('APCA_API_SECRET_KEY')
app.config['SECRET_KEY'] = SECRET_KEY
# Initialize Alpaca API client
api = tradeapi.REST(
ALPACA_API_KEY,
ALPACA_SECRET_KEY,
base_url='https://paper-api.alpaca.markets'
data_fetcher = DataFetcher(api)
backtest_engine = BacktestEngine(data_fetcher)
os.makedirs('data/saved_strategies', exist_ok=True)
@app.route('/')
def index():
return render_template('index.html')
@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:
assets = api.list_assets(status='active')
symbols = []
symbols.append({
'symbol': asset.symbol,
'name': asset.name
})
return jsonify(symbols[:100])
except Exception as e:
@app.route('/api/search-symbols', methods=['GET'])
def search_symbols():
try:
assets = api.list_assets(status='active')
symbols = [
except Exception as e:
@app.route('/api/historical-data', methods=['GET'])
def get_historical_data():
try:
return jsonify(data)
except Exception as e:
@app.route('/api/account', methods=['GET'])
def get_account():
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 = []
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')
parser = StrategyParser(blocks)
strategy = parser.parse_blocks()
if ind['type'] == 'SMA':
period = ind['parameters']['period']
strategy['entry_rules'] = [
break
period = ind['parameters']['period']
strategy['entry_rules'] = [
]
break
period = ind['parameters']['period']
strategy['entry_rules'] = [
break
exit_rule = rule.copy()
if exit_rule['operator'] == '>':
exit_rule['operator'] = '<'
exit_rule['operator'] = '>'
exit_rule['operator'] = '<='
exit_rule['operator'] = '>='
strategy['exit_rules'].append(exit_rule)
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():
try:
if not request.is_json:
order_data = request.json
order = api.submit_order(
symbol=order_data.get('symbol'),
qty=order_data.get('quantity'),
side=order_data.get('side', 'buy'),
type=order_data.get('orderType', 'market'),
time_in_force=order_data.get('timeInForce', 'day')
return jsonify({
'order_id': order.id,
'client_order_id': order.client_order_id,
'symbol': order.symbol,
'side': order.side,
'qty': order.qty,
'type': order.type,
'status': order.status
})
except Exception as e:
@app.route('/api/save-strategy', methods=['POST'])
def save_strategy():
try:
if not request.is_json:
strategy_data = request.get_json()
save_dir = 'data/saved_strategies'
json.dump(strategy_data, f, indent=4)
return jsonify({
"success": True,
})
except Exception as e:
@app.route('/api/load-strategy', methods=['GET'])
def load_strategy():
try:
strategy_name = request.args.get('name')
if not strategy_name:
strategy_files = os.listdir('data/saved_strategies')
if not os.path.exists(filepath):
strategy_data = json.load(f)
return jsonify(strategy_data)
except Exception as e:
logger.error(f"Error loading strategy: {str(e)}")
@app.route('/api/list-strategies', methods=['GET'])
def list_strategies():
try:
if not os.path.exists('data/saved_strategies'):
strategy_files = os.listdir('data/saved_strategies')
except Exception as e:
if __name__ == '__main__':
app.run(debug=True, port=5000)
strategyparcer.py
import json
import logging
logger = logging.getLogger(__name__)
class StrategyParser:
"""Parser for block-based trading strategies"""
"""
Args:
"""
logger.info("Initializing StrategyParser")
if isinstance(blocks, str):
try:
self.blocks = json.loads(blocks)
except json.JSONDecodeError:
self.blocks = []
else:
self.blocks = blocks
self.indicators = []
self.entry_rules = []
self.exit_rules = []
def parse_blocks(self):
"""
Returns:
"""
if self.blocks:
continue
if block_type == 'indicator':
self._parse_indicator(block)
self._parse_rule(block, is_entry=True)
self._parse_rule(block, is_entry=False)
else:
if not self.indicators:
self.indicators.append({
'type': 'SMA',
'parameters': {
'period': 20,
'price': 'close'
})
if not self.entry_rules:
if ind['type'] == 'SMA':
period = ind['parameters']['period']
indicator_name = f"SMA_{period}"
break
period = ind['parameters']['period']
indicator_name = f"RSI_{period}"
break
# Add default entry rule based on the indicator
if "RSI" in indicator_name:
self.entry_rules.append({
'indicator': indicator_name,
'operator': '>',
'value': '50'
})
else:
self.entry_rules.append({
'indicator': indicator_name,
'operator': '>',
'value': 'close'
})
if not self.exit_rules:
if self.entry_rules:
entry_rule = self.entry_rules[0]
exit_rule = entry_rule.copy()
if exit_rule['operator'] == '>':
exit_rule['operator'] = '<'
exit_rule['operator'] = '>'
elif exit_rule['operator'] == '>=':
exit_rule['operator'] = '<='
exit_rule['operator'] = '>='
self.exit_rules.append(exit_rule)
else:
self.exit_rules.append({
'indicator': 'SMA_20',
'operator': '<',
'value': 'close'
})
strategy = {
'indicators': self.indicators,
'entry_rules': self.entry_rules,
'exit_rules': self.exit_rules
return strategy
"""
Parse an indicator block
Args:
"""
if not indicator_type:
return
if indicator_type == 'SMA':
self.indicators.append({
'type': 'SMA',
'parameters': {
'period': period,
'price': 'close'
})
self.indicators.append({
'type': 'EMA',
'parameters': {
'period': period,
'price': 'close'
})
self.indicators.append({
'type': 'RSI',
'parameters': {
'period': period,
'price': 'close'
})
self.indicators.append({
'type': 'MACD',
'parameters': {
'fast_period': fast_period,
'slow_period': slow_period,
'signal_period': signal_period,
'price': 'close'
})
else:
"""
Args:
is_entry (bool): True if this is an entry rule, False for exit rule
"""
if not conditions:
return
for condition in conditions:
continue
value = condition.get('value', 0)
if not indicator:
continue
rule = {
'indicator': indicator,
'operator': operator,
'value': value
if is_entry:
self.entry_rules.append(rule)
else:
self.exit_rules.append(rule)
import pandas as pd
import numpy as np
import logging
logger = logging.getLogger(__name__)
class DataFetcher:
self.api = api
"""
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
end_date = datetime.now()
if period == '1D':
else: # '5Y'
start = start_date.strftime('%Y-%m-%d')
end = 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,
end=end,
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,
end=end,
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)
# Cache the sample data too
return sample_data
"""
Args:
Returns:
"""
try:
try:
quote = self.api.get_latest_quote(symbol)
return {
'symbol': symbol,
'ask': quote.ap,
'bid': quote.bp,
'size': quote.s
except AttributeError:
# Fall back to bars for paper trading
bar = bars[symbol][0]
return {
'symbol': symbol,
'price': bar.c,
'volume': bar.v
except Exception as e:
return None
end_date = datetime.now()
np.random.seed(seed)
data = []
current_price = base_price
if trend == 'up':
drift = 0.0005
drift = -0.0005
else:
drift = 0.0
close_price = current_price
close_price = current_price
# Random volume
data.append({
'time': date.strftime('%Y-%m-%d'),
'volume': volume
})
return data
backtest.py
import pandas as pd
import numpy as np
import logging
logger = logging.getLogger(__name__)
class BacktestEngine:
"""Engine for backtesting trading strategies with Alpaca data"""
"""
Args:
"""
self.data_fetcher = data_fetcher
"""
Args:
Returns:
"""
historical_data = self.data_fetcher.get_historical_data(
symbol=symbol,
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)
if end_date:
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]
df = self._apply_indicators(df, strategy['indicators'])
cash = initial_capital
shares = 0
trades = []
equity_curve = [initial_capital]
for i in range(len(df)):
continue
date = df.index[i].strftime('%Y-%m-%d')
price = df['close'].iloc[i]
# Get yesterday's row for signal calculation (to avoid lookahead bias)
yesterday_data = df.iloc[i-1]
if yesterday_data.isnull().any():
continue
if shares == 0:
if entry_signal:
trades.append({
'date': date,
'type': 'BUY',
'price': price,
'shares': shares_to_buy,
'value': cost
})
# Update portfolio
cash -= cost
shares = shares_to_buy
if exit_signal:
trades.append({
'date': date,
'type': 'SELL',
'price': price,
'shares': shares,
'value': sale_value
})
# Update portfolio
cash += sale_value
shares = 0
equity_curve.append(current_equity)
initial_equity = equity_curve[0]
final_equity = equity_curve[-1]
sharpe_ratio = 0.0
if len(daily_returns) > 0 and np.std(daily_returns) > 0:
max_drawdown = self._calculate_max_drawdown(equity_curve)
return {
'initial_capital': initial_capital,
'total_trades': len(trades),
'trades': trades,
"""
Args:
"""
try:
if indicator['type'] == 'SMA':
period = indicator['parameters']['period']
df[f'SMA_{period}'] = df['close'].rolling(window=period).mean()
logger.info(f"Calculated SMA_{period}")
period = indicator['parameters']['period']
logger.info(f"Calculated EMA_{period}")
period = indicator['parameters']['period']
delta = df['close'].diff()
rs = gain / loss
rs = rs.fillna(0)
logger.info(f"Calculated RSI_{period}")
elif indicator['type'] == 'MACD':
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()
# Calculate histogram
except Exception as e:
return df
"""
Evaluate if trading conditions are met
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)
else:
except Exception as e:
continue
if not results:
return False
# Return True if all conditions are met
return all(results)
"""
Args:
Returns:
"""
equity = np.array(equity_curve)
running_max = np.maximum.accumulate(equity)
drawdowns = np.nan_to_num(drawdowns)
utilis/init.py
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/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>
<li class="nav-item">
</li>
<li class="nav-item">
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
</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>
<div class="mb-3">
</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>
</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">
</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>
<!-- Trades Table -->
<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">
</div>
</div>
</div>
</div>
<!-- Strategy Load Modal -->
<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-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>
</form>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></scrip
t>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</body>
</html>
Script.js
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
// Block management
let nextBlockId = 1;
oneYearAgo.setFullYear(today.getFullYear() - 1);
startDateInput.value = formatDate(oneYearAgo);
endDateInput.value = formatDate(today);
currentDate.textContent = today.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
initTradingViewChart(symbolSelect.value);
fetchMarketStatus();
setupDragAndDrop();
// Set up button event listeners
runBacktestBtn.addEventListener('click', runBacktest);
paperTradeBtn.addEventListener('click', openPaperTradeModal);
saveStrategyBtn.addEventListener('click', saveStrategy);
loadStrategyBtn.addEventListener('click', openLoadStrategyModal);
clearStrategyBtn.addEventListener('click', confirmClearStrategy);
symbolSelect.addEventListener('change', updateSymbol);
searchSymbolBtn.addEventListener('click', searchSymbols);
symbolSearch.addEventListener('keypress', function(e) {
searchSymbols();
e.preventDefault();
});
saveBlockConfigBtn.addEventListener('click', saveBlockConfig);
tradeTypeSelect.addEventListener('change', function() {
limitPriceGroup.classList.remove('d-none');
} else {
limitPriceGroup.classList.add('d-none');
});
submitTradeBtn.addEventListener('click', submitPaperTrade);
function initTradingViewChart(symbol) {
if (tradingViewWidget) {
document.getElementById('tradingViewChart').innerHTML = '';
container_id: 'tradingViewChart',
symbol: symbol,
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%'
});
function fetchMarketStatus() {
fetch('/api/markets')
.then(data => {
if (data.is_open) {
} else {
}
})
.catch(error => {
});
function updateSymbol() {
initTradingViewChart(symbol);
function searchSymbols() {
fetch(`/api/search-symbols?query=${encodeURIComponent(query)}`)
.then(data => {
if (data.error) {
return;
if (data.length === 0) {
} else {
data.forEach(item => {
option.value = item.symbol;
symbolSelect.appendChild(option);
});
symbolSelect.selectedIndex = 0;
updateSymbol();
})
.catch(error => {
});
}
// Setup drag and drop functionality
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,
}));
this.classList.add('dragging');
this.addEventListener('dragend', function() {
this.classList.remove('dragging');
}, { once: true });
function handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
// Handle drop
function handleDrop(e) {
e.preventDefault();
try {
} catch (err) {
block.style.left = `${x}px`;
block.style.top = `${y}px`;
let blockData = {
id: blockId,
type: type,
x: x,
y: y
};
blockData.indicatorType = indicatorType;
blockIcon = 'bi-graph-up';
blockData.period = 20;
blockIcon = 'bi-graph-up-arrow';
blockData.period = 20;
blockIcon = 'bi-activity';
blockData.period = 14;
blockIcon = 'bi-bar-chart-line';
blockData.fastPeriod = 12;
blockData.slowPeriod = 26;
blockData.signalPeriod = 9;
blockTitle = 'MACD';
} else {
blockIcon = 'bi-question-circle';
blockTitle = indicatorType;
blockSettings = '';
blockIcon = 'bi-arrow-up-circle';
blockData.conditions = [];
blockIcon = 'bi-arrow-down-circle';
blockData.conditions = [];
block.innerHTML = `
<div class="block-header">
</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
}));
});
// Add drag over handler for the canvas
strategyCanvas.addEventListener('dragover', function(e) {
e.preventDefault();
});
strategyCanvas.addEventListener('drop', function(e) {
e.preventDefault();
try {
block.style.left = `${x}px`;
block.style.top = `${y}px`;
blockData.x = x;
blockData.y = y;
} catch (err) {
});
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);
// Remove the placeholder if it exists
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">
<label class="form-label">Fast Period</label>
</div>
<div class="mb-3">
</div>
<div class="mb-3">
</div>
`;
indicatorOptions += `
`;
indicatorId = `${b.indicatorType}_${b.period}`;
indicatorId = `RSI_${b.period}`;
indicatorId = 'MACD';
indicatorOptions += `
`;
if (indicatorId) {
});
content = `
<div class="mb-3">
<label class="form-label">Indicator</label>
${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 class="input-group">
<li><h6 class="dropdown-header">Price</h6></li>
<li><hr class="dropdown-divider"></li>
</ul>
</div>
<div class="form-text">Enter a number, indicator name, or price point (open,
high, low, close)</div>
</div>
`;
blockConfigBody.innerHTML = content;
blockConfigModal.show();
function saveBlockConfig() {
if (!selectedBlock) return;
selectedBlock.period = period;
selectedBlock.fastPeriod = fastPeriod;
selectedBlock.slowPeriod = slowPeriod;
selectedBlock.signalPeriod = signalPeriod;
if (indicator) {
if (selectedBlock.conditions.length === 0) {
} else {
blockConfigModal.hide();
function removeBlock(blockId) {
if (blockElement) {
blockElement.remove();
if (blocks.length === 0) {
if (placeholder) {
placeholder.style.display = 'flex';
function runBacktest() {
if (blocks.length === 0) {
return;
// Make sure we have at least one indicator, one entry rule, and one exit rule
if (indicators.length === 0) {
return;
if (entryRules.length === 0) {
return;
if (exitRules.length === 0) {
return;
entryRules.forEach(rule => {
if (!rule.conditions || rule.conditions.length === 0) {
});
exitRules.forEach(rule => {
});
if (unconfiguredRules.length > 0) {
return;
// Send to backend for processing - FIXED: sending the blocks array directly
fetch('/api/backtest', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
blocks: blocks, // Send the blocks array directly
symbol: symbolSelect.value,
startDate: startDateInput.value,
endDate: endDateInput.value,
capital: initialCapitalInput.value
})
})
.then(results => {
displayBacktestResults(results);
})
.catch(error => {
});
function buildStrategyConfig() {
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) {
} 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;
// Display trades
results.trades.forEach(trade => {
tradesHtml += `
<tr>
<td>${trade.date}</td>
<td>${trade.type}</td>
<td>$${formatNumber(trade.price)}</td>
<td>${trade.shares}</td>
<td>$${formatNumber(trade.value)}</td>
</tr>
`;
});
tradesTableBody.innerHTML = tradesHtml;
} else {
tradesTableBody.innerHTML = '<tr><td colspan="5" class="text-center">No trades
generated</td></tr>';
updateEquityCurveChart(results.equity_curve);
function updateEquityCurveChart(equityCurve) {
if (chartInstance) {
chartInstance.destroy();
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Equity',
data: equityCurve,
borderColor: '#007bff',
fill: true,
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: false,
title: {
display: true,
},
x: {
title: {
display: true,
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
});
function openPaperTradeModal() {
if (blocks.length === 0) {
return;
document.getElementById('tradeSymbol').value = symbolSelect.value;
document.getElementById('tradeQuantity').value = 1;
document.getElementById('tradeSide').value = 'buy';
document.getElementById('tradeType').value = 'market';
limitPriceGroup.classList.add('d-none');
paperTradeModal.show();
}
function submitPaperTrade() {
let orderData = {
symbol: symbol,
quantity: quantity,
side: side,
orderType: orderType
};
return;
orderData.limitPrice = limitPrice;
fetch('/api/paper-trade', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(orderData)
})
.then(result => {
if (result.error) {
alert(`Error: ${result.error}`);
} else {
paperTradeModal.hide();
})
.catch(error => {
});
// Save Strategy
function saveStrategy() {
if (blocks.length === 0) {
return;
}
const strategyName = prompt('Enter a name for this strategy:');
if (!strategyName) return;
fetch('/api/save-strategy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: strategyName,
blocks: blocks,
symbol: symbolSelect.value,
})
})
.then(result => {
if (result.error) {
} else {
})
.catch(error => {
});
}
// Open the Load Strategy modal
function openLoadStrategyModal() {
loadStrategyModal.show();
fetch('/api/list-strategies')
.then(data => {
strategyList.innerHTML = '';
data.strategies.forEach(strategy => {
item.textContent = strategy;
item.addEventListener('click', function() {
loadSpecificStrategy(strategy);
});
strategyList.appendChild(item);
});
} else {
})
.catch(error => {
});
function loadSpecificStrategy(strategyName) {
fetch(`/api/load-strategy?name=${encodeURIComponent(strategyName)}`)
.then(data => {
if (data.error) {
alert(`Error: ${data.error}`);
return;
clearStrategy();
// Load blocks
data.blocks.forEach(block => {
newBlock[key] = block[key];
if (blockElement) {
} else {
}
blockElement.querySelector('.block-settings').textContent = settingsText;
});
if (data.symbol) {
if (matchingOption) {
symbolSelect.value = data.symbol;
updateSymbol();
loadStrategyModal.hide();
})
.catch(error => {
});
function confirmClearStrategy() {
clearStrategy();
// Clear strategy
function clearStrategy() {
blocks.forEach(block => {
if (blockElement) {
blockElement.remove();
});
blocks = [];
// Show placeholder
if (placeholder) {
placeholder.style.display = 'flex';
// 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
});
});
Style.css
/* General Styles */
body {
background-color: #f8f9fa;
/* Sidebar Styles */
.sidebar {
padding: 0;
overflow-y: auto;
.sidebar-heading {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.1rem;
/* Block Styles */
.block-container {
margin-bottom: 20px;
.block-group {
margin-bottom: 15px;
.block-group h6 {
margin-top: 15px;
margin-bottom: 10px;
font-size: 0.9rem;
.block {
margin-bottom: 8px;
border-radius: 6px;
cursor: grab;
font-size: 0.9rem;
user-select: none;
}
.block:hover {
transform: translateY(-2px);
.indicator-block {
background-color: #e3f2fd;
color: #0d47a1;
.entry-block {
background-color: #e8f5e9;
color: #1b5e20;
.exit-block {
background-color: #ffebee;
color: #b71c1c;
/* Strategy Canvas */
.strategy-canvas {
min-height: 300px;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
position: relative;
background-color: #fff;
.canvas-placeholder {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 260px;
color: #adb5bd;
.canvas-placeholder i {
margin-bottom: 10px;
.canvas-block {
position: absolute;
min-width: 180px;
padding: 12px;
border-radius: 6px;
background-color: white;
z-index: 10;
cursor: move;
}
.canvas-block.indicator {
.canvas-block.entry {
.canvas-block.exit {
.block-header {
display: flex;
justify-content: space-between;
font-weight: bold;
margin-bottom: 8px;
padding-bottom: 6px;
.block-content {
font-size: 0.85rem;
color: #555;
}
.block-remove {
color: #dc3545;
cursor: pointer;
font-size: 1rem;
padding: 2px;
.block-remove:hover {
color: #bd2130;
/* Metrics Display */
.metric-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
.metric-label {
font-weight: 500;
color: #495057;
.metric-value {
font-weight: 600;
}
.metric-value.positive {
color: #28a745;
.metric-value.negative {
color: #dc3545;
/* Card Styles */
.card {
border: none;
margin-bottom: 1.5rem;
.card-header {
.card-title {
margin-bottom: 0;
font-size: 1rem;
font-weight: 500;
/* Table Styles */
.table th {
font-weight: 500;
border-top: none;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.05rem;
color: #495057;
.table td {
vertical-align: middle;
font-size: 0.9rem;
/* Modal Styles */
.modal-title {
font-weight: 500;
color: #343a40;
.modal-body {
padding: 1.5rem;
/* Strategy List */
.list-group-item {
cursor: pointer;
.list-group-item:hover {
background-color: #f8f9fa;
.list-group-item.active {
background-color: #007bff;
border-color: #007bff;
/* Form Controls */
.form-label {
font-weight: 500;
font-size: 0.9rem;
margin-bottom: 0.3rem;
color: #495057;
/* Responsive Adjustments */
.sidebar {
height: auto;
max-height: 300px;
overflow-y: auto;
.canvas-block {
width: 90%;
/* Animations */
.animate-pulse {
@keyframes pulse {
0%, 100% {
opacity: 1;
50% {
opacity: 0.5;
/* Status Indicators */
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 5px;
.status-indicator.green {
background-color: #28a745;
.status-indicator.red {
background-color: #dc3545;
/* Utility Classes */
.cursor-pointer {
cursor: pointer;
.text-sm {
font-size: 0.85rem;
.bg-light-subtle {