initial commit
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block title %}Portfolio — IBKR Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Portfolio</h1>
|
||||
|
||||
<div class="portfolio-summary" id="pnlCards">
|
||||
<div class="summary-card">
|
||||
<div class="card-label">Net Liquidation</div>
|
||||
<div class="card-value" id="netLiq">—</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="card-label">Total Cash</div>
|
||||
<div class="card-value" id="totalCash">—</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="card-label">Unrealized P&L</div>
|
||||
<div class="card-value" id="unrealizedPnl">—</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="card-label">Realized P&L</div>
|
||||
<div class="card-value" id="realizedPnl">—</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-container" style="margin-top: 24px;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Symbol</th>
|
||||
<th>Position</th>
|
||||
<th>Avg Cost</th>
|
||||
<th>Chart</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="positionsBody">
|
||||
<tr><td colspan="4" class="empty-state">Loading positions...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
function fmt(val) {
|
||||
if (val === undefined || val === null) return '—';
|
||||
return '$' + parseFloat(val).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function colorClass(val) {
|
||||
const n = parseFloat(val);
|
||||
if (isNaN(n)) return '';
|
||||
return n >= 0 ? 'positive' : 'negative';
|
||||
}
|
||||
|
||||
async function loadPnl() {
|
||||
try {
|
||||
const res = await fetch('/portfolio/pnl');
|
||||
const d = await res.json();
|
||||
document.getElementById('netLiq').textContent = fmt(d.NetLiquidation);
|
||||
document.getElementById('totalCash').textContent = fmt(d.TotalCashValue);
|
||||
|
||||
const upEl = document.getElementById('unrealizedPnl');
|
||||
upEl.textContent = fmt(d.UnrealizedPnL);
|
||||
upEl.className = 'card-value ' + colorClass(d.UnrealizedPnL);
|
||||
|
||||
const rpEl = document.getElementById('realizedPnl');
|
||||
rpEl.textContent = fmt(d.RealizedPnL);
|
||||
rpEl.className = 'card-value ' + colorClass(d.RealizedPnL);
|
||||
} catch (e) {
|
||||
console.error('PnL fetch error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPositions() {
|
||||
try {
|
||||
const res = await fetch('/portfolio/data');
|
||||
const data = await res.json();
|
||||
const tbody = document.getElementById('positionsBody');
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="empty-state">No open positions.</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = data.map(p => `
|
||||
<tr>
|
||||
<td><a class="symbol-link" href="/?symbol=${p.symbol}">${p.symbol}</a></td>
|
||||
<td>${p.position}</td>
|
||||
<td class="price-cell">${fmt(p.avg_cost)}</td>
|
||||
<td><a class="symbol-link" href="/?symbol=${p.symbol}">View Chart</a></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
} catch (e) {
|
||||
console.error('Positions fetch error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
loadPnl();
|
||||
loadPositions();
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
refresh();
|
||||
setInterval(refresh, 30000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user