From 5bb6f2d6865ee902ad911b55f023d55f00f1e144 Mon Sep 17 00:00:00 2001 From: zastian-dev Date: Tue, 10 Feb 2026 16:45:42 +0000 Subject: [PATCH] transaction hystory --- src/alpaca.rs | 35 +++++++++++ src/dashboard.rs | 159 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 183 insertions(+), 11 deletions(-) diff --git a/src/alpaca.rs b/src/alpaca.rs index aeed3ac..e8e6833 100644 --- a/src/alpaca.rs +++ b/src/alpaca.rs @@ -96,6 +96,12 @@ pub struct Order { pub qty: String, pub side: String, pub status: String, + #[serde(default)] + pub filled_avg_price: Option, + #[serde(default)] + pub filled_at: Option, + #[serde(default)] + pub created_at: Option, } impl AlpacaClient { @@ -415,6 +421,35 @@ impl AlpacaClient { response.json().await.context("Failed to parse order") } + /// Get closed/filled orders (transaction history). + pub async fn get_orders(&self, limit: u32) -> Result> { + self.enforce_rate_limit().await; + + let url = format!( + "{}/orders?status=closed&limit={}&direction=desc", + TRADING_BASE_URL, limit + ); + + let response = self + .http_client + .get(&url) + .headers(self.auth_headers()) + .send() + .await + .context("Failed to get orders")?; + + if !response.status().is_success() { + let status = response.status(); + let text = response.text().await.unwrap_or_default(); + anyhow::bail!("API error {}: {}", status, text); + } + + response + .json() + .await + .context("Failed to parse orders response") + } + /// Check if market is open. pub async fn is_market_open(&self) -> Result { let clock = self.get_clock().await?; diff --git a/src/dashboard.rs b/src/dashboard.rs index 85e43e5..286092a 100644 --- a/src/dashboard.rs +++ b/src/dashboard.rs @@ -50,6 +50,16 @@ struct PositionResponse { change_today: f64, } +#[derive(Serialize)] +struct OrderHistoryResponse { + symbol: String, + side: String, + qty: f64, + filled_price: f64, + filled_at: String, + status: String, +} + const HTML_TEMPLATE: &str = r#" @@ -66,7 +76,7 @@ const HTML_TEMPLATE: &str = r#" min-height: 100vh; padding: 20px; } - .container { max-width: 1400px; margin: 0 auto; } + .container { max-width: 1800px; margin: 0 auto; } h1 { text-align: center; margin-bottom: 30px; @@ -93,6 +103,15 @@ const HTML_TEMPLATE: &str = r#" .stat-value { font-size: 1.8rem; font-weight: 700; } .stat-value.positive { color: #00ff88; } .stat-value.negative { color: #ff4757; } + .main-layout { + display: grid; + grid-template-columns: 1fr 380px; + gap: 20px; + margin-bottom: 30px; + } + @media (max-width: 1000px) { + .main-layout { grid-template-columns: 1fr; } + } .chart-container { background: rgba(255, 255, 255, 0.05); border-radius: 16px; @@ -101,6 +120,49 @@ const HTML_TEMPLATE: &str = r#" border: 1px solid rgba(255, 255, 255, 0.1); } .chart-title { font-size: 1.3rem; margin-bottom: 20px; color: #fff; } + .transactions-panel { + background: rgba(255, 255, 255, 0.05); + border-radius: 16px; + padding: 24px; + border: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + flex-direction: column; + max-height: calc(100vh - 220px); + } + .transactions-list { + flex: 1; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: rgba(255,255,255,0.2) transparent; + } + .transactions-list::-webkit-scrollbar { width: 6px; } + .transactions-list::-webkit-scrollbar-track { background: transparent; } + .transactions-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; } + .tx-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 0; + border-bottom: 1px solid rgba(255,255,255,0.06); + } + .tx-row:last-child { border-bottom: none; } + .tx-left { display: flex; align-items: center; gap: 10px; } + .tx-side { + font-size: 0.7rem; + font-weight: 700; + padding: 3px 8px; + border-radius: 4px; + text-transform: uppercase; + min-width: 36px; + text-align: center; + } + .tx-side.buy { background: rgba(0,255,136,0.2); color: #00ff88; } + .tx-side.sell { background: rgba(255,71,87,0.2); color: #ff4757; } + .tx-symbol { font-weight: 600; font-size: 0.95rem; } + .tx-qty { color: #888; font-size: 0.8rem; } + .tx-right { text-align: right; } + .tx-price { font-size: 0.9rem; font-weight: 500; } + .tx-time { font-size: 0.7rem; color: #666; margin-top: 2px; } .positions-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); @@ -170,16 +232,26 @@ const HTML_TEMPLATE: &str = r#"
0
-
-

Portfolio Performance

- +
+
+
+

Portfolio Performance

+ +
+
+

Current Positions

+
Loading...
+
+

+

+
+
+

Transaction History

+
+
Loading...
+
+
-
-

Current Positions

-
Loading...
-
-

-