anycoder-bb2a657a / index.html
smalinin's picture
Upload folder using huggingface_hub
3b4d11f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Backtrader Strategy Simulator</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
}
.header {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 1rem 2rem;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: #2c3e50;
font-size: 1.8rem;
font-weight: 700;
}
.header a {
color: #667eea;
text-decoration: none;
font-weight: 600;
transition: color 0.3s ease;
}
.header a:hover {
color: #764ba2;
}
.container {
max-width: 1400px;
margin: 2rem auto;
padding: 0 1rem;
}
.controls-panel {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.controls-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.control-group {
display: flex;
flex-direction: column;
}
.control-group label {
font-weight: 600;
margin-bottom: 0.5rem;
color: #2c3e50;
}
.control-group input {
padding: 0.75rem;
border: 2px solid #e1e8ed;
border-radius: 10px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.control-group input:focus {
outline: none;
border-color: #667eea;
}
.btn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
padding: 1rem 2rem;
border-radius: 50px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.btn:active {
transform: translateY(0);
}
.chart-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
}
.metric-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 1.5rem;
text-align: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease;
}
.metric-card:hover {
transform: translateY(-5px);
}
.metric-value {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.metric-label {
color: #7f8c8d;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.positive {
color: #27ae60;
}
.negative {
color: #e74c3c;
}
.trades-table {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 1rem;
}
th, td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #ecf0f1;
}
th {
background: #f8f9fa;
font-weight: 600;
color: #2c3e50;
}
tr:hover {
background: #f8f9fa;
}
.strategy-info {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.strategy-info h3 {
color: #2c3e50;
margin-bottom: 1rem;
}
.strategy-info p {
line-height: 1.6;
color: #555;
}
@media (max-width: 768px) {
.header {
flex-direction: column;
gap: 1rem;
text-align: center;
}
.controls-grid {
grid-template-columns: 1fr;
}
.metrics-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.loading {
display: none;
text-align: center;
padding: 2rem;
color: #667eea;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="header">
<h1>🚀 Backtrader Strategy Simulator</h1>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a>
</div>
<div class="container">
<div class="strategy-info">
<h3>📈 Moving Average Crossover Strategy</h3>
<p>This simulator implements a classic dual moving average crossover strategy. The strategy generates buy signals when the short-term moving average crosses above the long-term moving average, and sell signals when it crosses below. This approach aims to capture trending movements in the market while filtering out short-term noise.</p>
</div>
<div class="controls-panel">
<h3 style="margin-bottom: 1.5rem; color: #2c3e50;">Strategy Parameters</h3>
<div class="controls-grid">
<div class="control-group">
<label for="shortMA">Short MA Period</label>
<input type="number" id="shortMA" value="10" min="1" max="100">
</div>
<div class="control-group">
<label for="longMA">Long MA Period</label>
<input type="number" id="longMA" value="20" min="1" max="200">
</div>
<div class="control-group">
<label for="initialCapital">Initial Capital ($)</label>
<input type="number" id="initialCapital" value="10000" min="1000" step="100">
</div>
<div class="control-group">
<label for="dataPoints">Data Points</label>
<input type="number" id="dataPoints" value="100" min="50" max="500">
</div>
</div>
<button class="btn" onclick="runBacktest()">▶️ Run Backtest</button>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Running backtest simulation...</p>
</div>
<div class="chart-container">
<canvas id="priceChart"></canvas>
</div>
<div class="metrics-grid" id="metricsGrid" style="display: none;">
<div class="metric-card">
<div class="metric-value" id="totalReturn">-</div>
<div class="metric-label">Total Return</div>
</div>
<div class="metric-card">
<div class="metric-value" id="sharpeRatio">-</div>
<div class="metric-label">Sharpe Ratio</div>
</div>
<div class="metric-card">
<div class="metric-value" id="maxDrawdown">-</div>
<div class="metric-label">Max Drawdown</div>
</div>
<div class="metric-card">
<div class="metric-value" id="winRate">-</div>
<div class="metric-label">Win Rate</div>
</div>
<div class="metric-card">
<div class="metric-value" id="totalTrades">-</div>
<div class="metric-label">Total Trades</div>
</div>
<div class="metric-card">
<div class="metric-value" id="finalValue">-</div>
<div class="metric-label">Final Value</div>
</div>
</div>
<div class="trades-table" id="tradesTable" style="display: none;">
<h3 style="margin-bottom: 1rem; color: #2c3e50;">Trade History</h3>
<table>
<thead>
<tr>
<th>Trade #</th>
<th>Date</th>
<th>Action</th>
<th>Price</th>
<th>Quantity</th>
<th>Portfolio Value</th>
<th>P&L</th>
</tr>
</thead>
<tbody id="tradesTableBody">
</tbody>
</table>
</div>
</div>
<script>
// Global variables
let chart = null;
let currentData = [];
let trades = [];
// Generate sample price data with trend and noise
function generatePriceData(dataPoints) {
const data = [];
let price = 100 + Math.random() * 50; // Start with random price between 100-150
for (let i = 0; i < dataPoints; i++) {
// Add trend component and random walk
const trend = Math.sin(i * 0.1) * 2 + Math.random() * 3 - 1.5;
price += trend;
price = Math.max(price, 50); // Minimum price floor
data.push({
index: i,
price: parseFloat(price.toFixed(2)),
date: new Date(2024, 0, 1 + i).toISOString().split('T')[0]
});
}
return data;
}
// Calculate moving average
function calculateMA(data, period) {
return data.map((item, index) => {
if (index < period - 1) return null;
const sum = data.slice(index - period + 1, index + 1)
.reduce((acc, curr) => acc + curr.price, 0);
return parseFloat((sum / period).toFixed(2));
});
}
// Backtrader Strategy Implementation
class MovingAverageCrossoverStrategy {
constructor(shortMA, longMA, initialCapital) {
this.shortMA = shortMA;
this.longMA = longMA;
this.initialCapital = initialCapital;
this.position = 0; // 0 = no position, 1 = long position
this.cash = initialCapital;
this.shares = 0;
this.entryPrice = 0;
this.trades = [];
this.portfolioHistory = [];
}
next(dataPoint, shortMAValue, longMAValue, index) {
// Skip if we don't have enough data for MAs
if (!shortMAValue || !longMAValue) return;
const portfolioValue = this.cash + (this.shares * dataPoint.price);
this.portfolioHistory.push(portfolioValue);
// Buy signal: Short MA crosses above Long MA
if (shortMAValue > longMAValue && this.position === 0) {
this.buy(dataPoint, index);
}
// Sell signal: Short MA crosses below Long MA
else if (shortMAValue < longMAValue && this.position === 1) {
this.sell(dataPoint, index);
}
}
buy(dataPoint, index) {
this.shares = Math.floor(this.cash / dataPoint.price);
this.cash = this.cash - (this.shares * dataPoint.price);
this.position = 1;
this.entryPrice = dataPoint.price;
const trade = {
id: this.trades.length + 1,
date: dataPoint.date,
action: 'BUY',
price: dataPoint.price,
quantity: this.shares,
portfolioValue: this.cash + (this.shares * dataPoint.price),
pnl: 0
};
this.trades.push(trade);
}
sell(dataPoint, index) {
const pnl = (dataPoint.price - this.entryPrice) * this.shares;
this.cash = this.cash + (this.shares * dataPoint.price);
this.position = 0;
const trade = {
id: this.trades.length + 1,
date: dataPoint.date,
action: 'SELL',
price: dataPoint.price,
quantity: this.shares,
portfolioValue: this.cash,
pnl: parseFloat(pnl.toFixed(2))
};
this.trades.push(trade);
}
getMetrics() {
const finalValue = this.cash + (this.shares * currentData[currentData.length - 1]?.price || 0);
const totalReturn = ((finalValue - this.initialCapital) / this.initialCapital * 100);
// Calculate Sharpe ratio
const returns = [];
for (let i = 1; i < this.portfolioHistory.length; i++) {
const dailyReturn = (this.portfolioHistory[i] - this.portfolioHistory[i-1]) / this.portfolioHistory[i-1];
returns.push(dailyReturn);
}
const avgReturn = returns.reduce((a, b) => a + b, 0) / returns.length;
const stdDev = Math.sqrt(returns.reduce((sum, ret) => sum + Math.pow(ret - avgReturn, 2), 0) / returns.length);
const sharpeRatio = stdDev === 0 ? 0 : (avgReturn / stdDev) * Math.sqrt(252);
// Calculate max drawdown
let peak = this.initialCapital;
let maxDrawdown = 0;
this.portfolioHistory.forEach(value => {
if (value > peak) peak = value;
const drawdown = ((peak - value) / peak) * 100;
maxDrawdown = Math.max(maxDrawdown, drawdown);
});
// Calculate win rate
const profitableTrades = this.trades.filter(trade => trade.pnl > 0).length;
const sellTrades = this.trades.filter(trade => trade.action === 'SELL');
const winRate = sellTrades.length > 0 ? (profitableTrades / sellTrades.length * 100) : 0;
return {
totalReturn: parseFloat(totalReturn.toFixed(2)),
sharpeRatio: parseFloat(sharpeRatio.toFixed(2)),
maxDrawdown: parseFloat(maxDrawdown.toFixed(2)),
winRate: parseFloat(winRate.toFixed(1)),
totalTrades: sellTrades.length,
finalValue: parseFloat(finalValue.toFixed(2))
};
}
}
// Run the backtest
function runBacktest() {
const shortMA = parseInt(document.getElementById('shortMA').value);
const longMA = parseInt(document.getElementById('longMA').value);
const initialCapital = parseInt(document.getElementById('initialCapital').value);
const dataPoints = parseInt(document.getElementById('dataPoints').value);
if (shortMA >= longMA) {
alert('Short MA period must be less than Long MA period!');
return;
}
// Show loading
document.getElementById('loading').style.display = 'block';
setTimeout(() => {
// Generate data
currentData = generatePriceData(dataPoints);
// Calculate moving averages
const shortMAValues = calculateMA(currentData, shortMA);
const longMAValues = calculateMA(currentData, longMA);
// Run strategy
const strategy = new MovingAverageCrossoverStrategy(shortMA, longMA, initialCapital);
currentData.forEach((dataPoint, index) => {
strategy.next(dataPoint, shortMAValues[index], longMAValues[index], index);
});
trades = strategy.trades;
// Display results
displayChart(currentData, shortMAValues, longMAValues);
displayMetrics(strategy.getMetrics());
displayTrades(trades);
// Hide loading
document.getElementById('loading').style.display = 'none';
}, 1000);
}
// Display the price chart
function displayChart(data, shortMAValues, longMAValues) {
const ctx = document.getElementById('priceChart').getContext('2d');
if (chart) {
chart.destroy();
}
chart = new Chart(ctx, {
type: 'line',
data: {
labels: data.map(d => d.date),
datasets: [{
label: 'Price',
data: data.map(d => d.price),
borderColor: '#667eea',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
borderWidth: 2,
fill: true
}, {
label: `Short MA (${document.getElementById('shortMA').value})`,
data: shortMAValues,
borderColor: '#27ae60',
backgroundColor: 'transparent',
borderWidth: 2
}, {
label: `Long MA (${document.getElementById('longMA').value})`,
data: longMAValues,
borderColor: '#e74c3c',
backgroundColor: 'transparent',
borderWidth: 2
}]
},
options: {
responsive: true,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
position: 'top',
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: 'white',
bodyColor: 'white'
}
},
scales: {
y: {
beginAtZero: false,
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
},
x: {
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
}
}
}
});
}
// Display performance metrics
function displayMetrics(metrics) {
const metricsGrid = document.getElementById('metricsGrid');
const metricsElements = {
totalReturn: document.getElementById('totalReturn'),
sharpeRatio: document.getElementById('sharpeRatio'),
maxDrawdown: document.getElementById('maxDrawdown'),
winRate: document.getElementById('winRate'),
totalTrades: document.getElementById('totalTrades'),
finalValue: document.getElementById('finalValue')
};
metricsElements.totalReturn.textContent = `${metrics.totalReturn}%`;
metricsElements.totalReturn.className = `metric-value ${metrics.totalReturn >= 0 ? 'positive' : 'negative'}`;
metricsElements.sharpeRatio.textContent = metrics.sharpeRatio;
metricsElements.sharpeRatio.className = `metric-value ${metrics.sharpeRatio >= 0 ? 'positive' : 'negative'}`;
metricsElements.maxDrawdown.textContent = `${metrics.maxDrawdown}%`;
metricsElements.maxDrawdown.className = 'metric-value negative';
metricsElements.winRate.textContent = `${metrics.winRate}%`;
metricsElements.winRate.className = `metric-value ${metrics.winRate >= 50 ? 'positive' : 'negative'}`;
metricsElements.totalTrades.textContent = metrics.totalTrades;
metricsElements.finalValue.textContent = `$${metrics.finalValue.toLocaleString()}`;
metricsGrid.style.display = 'grid';
}
// Display trades table
function displayTrades(trades) {
const tbody = document.getElementById('tradesTableBody');
tbody.innerHTML = '';
trades.forEach(trade => {
const row = tbody.insertRow();
row.innerHTML = `
<td>${trade.id}</td>
<td>${trade.date}</td>
<td style="font-weight: 600; color: ${trade.action === 'BUY' ? '#27ae60' : '#e74c3c'}">${trade.action}</td>
<td>$${trade.price}</td>
<td>${trade.quantity}</td>
<td>$${trade.portfolioValue.toLocaleString()}</td>
<td style="color: ${trade.pnl >= 0 ? '#27ae60' : '#e74c3c'}">${trade.pnl !== 0 ? '$' + trade.pnl.toFixed(2) : '-'}</td>
`;
});
document.getElementById('tradesTable').style.display = 'block';
}
// Run initial backtest when page loads
window.addEventListener('load', function() {
runBacktest();
});
</script>
</body>
</html>