209 lines
6.1 KiB
HTML
209 lines
6.1 KiB
HTML
|
|
<!doctype html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8" />
|
||
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
|
|
<title>IB Dashboard</title>
|
||
|
|
<style>
|
||
|
|
:root { --sort-row-height: 43px; --filter-row-height: 38px; }
|
||
|
|
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial; margin: 20px; }
|
||
|
|
input, button { padding: 6px 8px; margin-right: 6px }
|
||
|
|
.table-wrap {
|
||
|
|
position: relative;
|
||
|
|
max-height: calc(100vh - 140px);
|
||
|
|
overflow: auto;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
margin-top: 12px;
|
||
|
|
background: #fff;
|
||
|
|
}
|
||
|
|
table {
|
||
|
|
border-collapse: separate;
|
||
|
|
border-spacing: 0;
|
||
|
|
width: max-content;
|
||
|
|
margin-top: 0;
|
||
|
|
background: #fff;
|
||
|
|
table-layout: auto;
|
||
|
|
}
|
||
|
|
th, td {
|
||
|
|
padding: 8px;
|
||
|
|
text-align: left;
|
||
|
|
border-right: 1px solid #ddd;
|
||
|
|
border-bottom: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
th:first-child, td:first-child { border-left: 1px solid #ddd }
|
||
|
|
.section-head { display: flex; align-items: center; gap: 10px }
|
||
|
|
.help-wrap { position: relative; display: inline-flex; align-items: center }
|
||
|
|
.aggregate-toggle { display: inline-flex; align-items: center; gap: 8px; font-weight: 600 }
|
||
|
|
.aggregate-toggle input { margin: 0 }
|
||
|
|
.refresh-button {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 8px;
|
||
|
|
padding: 8px 12px;
|
||
|
|
border: 1px solid #bfc7d1;
|
||
|
|
border-radius: 8px;
|
||
|
|
background: #f7f9fb;
|
||
|
|
color: #19324d;
|
||
|
|
font-size: 14px;
|
||
|
|
font-weight: 700;
|
||
|
|
cursor: pointer;
|
||
|
|
margin-right: 0;
|
||
|
|
}
|
||
|
|
.refresh-button:hover { background: #eef3f8 }
|
||
|
|
.refresh-button:disabled {
|
||
|
|
cursor: wait;
|
||
|
|
opacity: 0.7;
|
||
|
|
}
|
||
|
|
.refresh-icon {
|
||
|
|
font-size: 16px;
|
||
|
|
line-height: 1;
|
||
|
|
}
|
||
|
|
.help-badge {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
width: 28px;
|
||
|
|
height: 28px;
|
||
|
|
border-radius: 999px;
|
||
|
|
background: #1f9d3a;
|
||
|
|
color: #fff;
|
||
|
|
font-size: 22px;
|
||
|
|
font-weight: 700;
|
||
|
|
line-height: 1;
|
||
|
|
cursor: help;
|
||
|
|
}
|
||
|
|
.help-pane {
|
||
|
|
display: none;
|
||
|
|
position: absolute;
|
||
|
|
top: 50%;
|
||
|
|
left: calc(100% + 12px);
|
||
|
|
right: auto;
|
||
|
|
transform: translateY(-50%);
|
||
|
|
width: 340px;
|
||
|
|
padding: 12px 14px;
|
||
|
|
border: 1px solid #cfd8cf;
|
||
|
|
background: #fbfffb;
|
||
|
|
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12);
|
||
|
|
z-index: 5;
|
||
|
|
font-size: 13px;
|
||
|
|
line-height: 1.45;
|
||
|
|
}
|
||
|
|
.help-wrap:hover .help-pane { display: block }
|
||
|
|
.help-pane p { margin: 0 0 8px 0 }
|
||
|
|
.help-pane p:last-child { margin-bottom: 0 }
|
||
|
|
th {
|
||
|
|
background: #f4f4f4;
|
||
|
|
position: sticky;
|
||
|
|
top: 0;
|
||
|
|
z-index: 4;
|
||
|
|
cursor: pointer;
|
||
|
|
user-select: none;
|
||
|
|
white-space: nowrap;
|
||
|
|
}
|
||
|
|
th.dragging { opacity: 0.55 }
|
||
|
|
thead tr.sort-row th {
|
||
|
|
top: 0;
|
||
|
|
height: var(--sort-row-height);
|
||
|
|
min-height: var(--sort-row-height);
|
||
|
|
box-sizing: border-box;
|
||
|
|
box-shadow: inset 0 -1px 0 #d9d9d9;
|
||
|
|
}
|
||
|
|
th:hover { background: #e9e9e9 }
|
||
|
|
th .sort-meta { color: #666; font-size: 12px; margin-left: 6px }
|
||
|
|
thead { position: relative; z-index: 2 }
|
||
|
|
thead tr.filter-row th {
|
||
|
|
top: var(--sort-row-height);
|
||
|
|
background: #fafafa;
|
||
|
|
cursor: default;
|
||
|
|
z-index: 3;
|
||
|
|
padding: 6px;
|
||
|
|
height: var(--filter-row-height);
|
||
|
|
min-height: var(--filter-row-height);
|
||
|
|
box-sizing: border-box;
|
||
|
|
box-shadow: inset 0 -1px 0 #d9d9d9;
|
||
|
|
}
|
||
|
|
thead tr.filter-row th:hover { background: #fafafa }
|
||
|
|
.filter-input {
|
||
|
|
width: 100%;
|
||
|
|
min-width: 0;
|
||
|
|
margin: 0;
|
||
|
|
padding: 5px 6px;
|
||
|
|
box-sizing: border-box;
|
||
|
|
font-size: 12px;
|
||
|
|
border: 1px solid #cfd3d7;
|
||
|
|
background: #fff;
|
||
|
|
}
|
||
|
|
.filter-input.filter-text { width: 9ch }
|
||
|
|
.filter-input.filter-select { width: auto; min-width: 5.5ch; max-width: 100% }
|
||
|
|
.filter-input.filter-active {
|
||
|
|
background: #f9e1e8;
|
||
|
|
border-color: #e5b8c7;
|
||
|
|
}
|
||
|
|
tbody tr:nth-child(odd) { background: #ffffff }
|
||
|
|
tbody tr:nth-child(even) { background: #f3f3f3 }
|
||
|
|
td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap }
|
||
|
|
td.icon-cell { text-align: center }
|
||
|
|
.ltcg-icon {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
width: 22px;
|
||
|
|
height: 22px;
|
||
|
|
border-radius: 4px;
|
||
|
|
font-size: 14px;
|
||
|
|
font-weight: 700;
|
||
|
|
line-height: 1;
|
||
|
|
}
|
||
|
|
.ltcg-icon.good {
|
||
|
|
background: #e5f7e9;
|
||
|
|
color: #188038;
|
||
|
|
border: 1px solid #b7e0c1;
|
||
|
|
}
|
||
|
|
.ltcg-icon.bad {
|
||
|
|
background: #fdeaea;
|
||
|
|
color: #c5221f;
|
||
|
|
border: 1px solid #efb7b7;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<h1>Interactive Brokers Dashboard</h1>
|
||
|
|
|
||
|
|
<section>
|
||
|
|
<div class="section-head">
|
||
|
|
<h2>Open Lots</h2>
|
||
|
|
<div class="help-wrap">
|
||
|
|
<span class="help-badge">?</span>
|
||
|
|
<div class="help-pane">
|
||
|
|
<p><strong>Sorting:</strong> click a column header to sort ascending, click again for descending, click a third time to remove it. Hold <code>Shift</code> while clicking to add second and third sort levels.</p>
|
||
|
|
<p><strong>Text filters:</strong> use wildcards with <code>*</code> and <code>?</code>. Example: <code>AM*</code> or <code>*2027*</code>.</p>
|
||
|
|
<p><strong>Numeric filters:</strong> use values like <code>>100</code>, <code><=0</code>, <code>50</code>.</p>
|
||
|
|
<p><strong>Date filters:</strong> use either wildcards like <code>2025-07*</code> or comparisons like <code>>2025-01-01</code>.</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<label class="aggregate-toggle">
|
||
|
|
<input id="aggregate-same-day" type="checkbox" checked />
|
||
|
|
<span>Aggregate same-day lots</span>
|
||
|
|
</label>
|
||
|
|
<button id="refresh-lots" class="refresh-button" type="button" title="Refresh lots">
|
||
|
|
<span class="refresh-icon">↻</span>
|
||
|
|
<span>Refresh</span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<div class="table-wrap">
|
||
|
|
<table id="positions-table">
|
||
|
|
<thead>
|
||
|
|
<tr class="sort-row">
|
||
|
|
</tr>
|
||
|
|
<tr class="filter-row">
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody></tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<script src="/static/app.js"></script>
|
||
|
|
</body>
|
||
|
|
</html>
|