Skip to content

Commit 0c8ddb9

Browse files
committed
Update: performance chart to properly getting holding stock detail, display rebalance button in analysis action tab
1 parent d3a129a commit 0c8ddb9

File tree

9 files changed

+178
-108
lines changed

9 files changed

+178
-108
lines changed

index.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
<meta name="description"
1010
content="Advanced LLM-powered analytical workflow tool with 15 specialized AI agents for comprehensive market analysis, research synthesis, and trading decision support. Open-source trading intelligence platform." />
1111
<meta name="author" content="TradingGoose" />
12-
<meta name="keywords" content="LLM trading analysis, AI market research, multi-agent workflow, trading intelligence, financial analysis AI, market analysis tool, trading decision support, AI research synthesis, open source trading" />
12+
<meta name="keywords"
13+
content="LLM trading analysis, AI market research, multi-agent workflow, trading intelligence, financial analysis AI, market analysis tool, trading decision support, AI research synthesis, open source trading" />
1314
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
1415
<link rel="canonical" href="https://trading-goose.github.io/" />
1516

@@ -28,7 +29,8 @@
2829
<meta name="twitter:site" content="@tradinggoose" />
2930
<meta name="twitter:creator" content="@tradinggoose" />
3031
<meta name="twitter:title" content="TradingGoose - LLM Trading Analysis Workflow" />
31-
<meta name="twitter:description" content="Advanced LLM-powered analytical workflow tool with 15 specialized AI agents for market research and trading intelligence." />
32+
<meta name="twitter:description"
33+
content="Advanced LLM-powered analytical workflow tool with 15 specialized AI agents for market research and trading intelligence." />
3234
<meta name="twitter:image" content="https://trading-goose.github.io/Social-Preview.png" />
3335
<meta name="twitter:image:alt" content="TradingGoose - Multi-LLM-Agents Trading Workflow" />
3436

@@ -153,6 +155,9 @@
153155
})();
154156
</script>
155157

158+
<!-- Microsoft Bing Site Verification -->
159+
<meta name="msvalidate.01" content="CA673B5FD31F6456CAA9E0F4BC93B302" />
160+
156161
<!-- Google AdSense -->
157162
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-9651663494022087"
158163
crossorigin="anonymous"></script>
@@ -252,4 +257,4 @@
252257
</script>
253258
</body>
254259

255-
</html>
260+
</html>

public/InviteLink.png

117 KB
Loading

public/TradingGoosx -x-Discord.png

150 KB
Loading

public/Welcome.png

171 KB
Loading

src/components/PerformanceChart.tsx

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
3838
const navigate = useNavigate();
3939
const [selectedPeriod, setSelectedPeriod] = useState<TimePeriod>("1D");
4040
const [loading, setLoading] = useState(false);
41+
const [positionsLoading, setPositionsLoading] = useState(true); // Track positions loading separately
4142
const [error, setError] = useState<string | null>(null);
4243
const [metrics, setMetrics] = useState<any>(null);
4344
const [portfolioData, setPortfolioData] = useState<PortfolioData | null>(null);
@@ -111,6 +112,7 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
111112

112113
setMetrics(metricsData);
113114
setPositions(positionsData || []);
115+
setPositionsLoading(false); // Mark positions as loaded
114116

115117
// If a stock is selected, fetch its data (uses Alpaca API)
116118
if (selectedStock) {
@@ -228,15 +230,19 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
228230
};
229231
}
230232

231-
const shares = parseFloat(position.qty);
232-
const avgCost = parseFloat(position.avg_entry_price);
233-
const currentPrice = parseFloat(position.current_price || 'Loading...');
234-
const lastdayPrice = parseFloat(position.lastday_price || 'Loading...');
235-
const marketValue = parseFloat(position.market_value);
236-
const unrealizedPL = parseFloat(position.unrealized_pl);
237-
const unrealizedPLPercent = parseFloat(position.unrealized_plpc) * 100;
238-
const todayPL = parseFloat(position.unrealized_intraday_pl || 'Loading...');
239-
const todayPLPercent = parseFloat(position.unrealized_intraday_plpc || 'Loading...') * 100;
233+
// Handle both raw Alpaca format and transformed metrics format
234+
const shares = position.shares !== undefined ? position.shares : (parseFloat(position.qty || '0') || 0);
235+
const avgCost = position.avgCost !== undefined ? position.avgCost : (parseFloat(position.avg_entry_price || '0') || 0);
236+
const currentPrice = position.currentPrice !== undefined ? position.currentPrice : (parseFloat(position.current_price || '0') || 0);
237+
const lastdayPrice = position.lastdayPrice !== undefined ? position.lastdayPrice : (parseFloat(position.lastday_price || '0') || 0);
238+
const marketValue = position.marketValue !== undefined ? position.marketValue : (parseFloat(position.market_value || '0') || 0);
239+
const unrealizedPL = position.unrealizedPL !== undefined ? position.unrealizedPL : (parseFloat(position.unrealized_pl || '0') || 0);
240+
const unrealizedPLPercent = position.unrealizedPLPct !== undefined ? position.unrealizedPLPct : ((parseFloat(position.unrealized_plpc || '0') || 0) * 100);
241+
242+
// For intraday P/L, calculate from day change if not directly available
243+
const dayChange = position.dayChange !== undefined ? position.dayChange : 0;
244+
const todayPL = position.unrealized_intraday_pl !== undefined ? parseFloat(position.unrealized_intraday_pl || '0') : (dayChange * shares * currentPrice / 100);
245+
const todayPLPercent = position.unrealized_intraday_plpc !== undefined ? (parseFloat(position.unrealized_intraday_plpc || '0') * 100) : dayChange;
240246

241247
// Calculate stock's daily price change (not position P&L)
242248
const stockDailyChange = currentPrice - lastdayPrice;
@@ -420,7 +426,7 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
420426
{periods.map((period) => (
421427
<TabsContent key={period.value} value={period.value} className="space-y-4">
422428
<div className="h-48">
423-
{loading && !portfolioData ? (
429+
{loading ? (
424430
<div className="h-full flex items-center justify-center">
425431
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
426432
</div>
@@ -499,7 +505,7 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
499505
</ResponsiveContainer>
500506
) : (
501507
<div className="h-full flex items-center justify-center text-muted-foreground">
502-
{error ? error : (hasAlpacaConfig ? "No data available for this period" : "Configure Alpaca API to view performance data")}
508+
{error ? error : (hasAlpacaConfig ? "Loading chart data..." : "Configure Alpaca API to view performance data")}
503509
</div>
504510
)}
505511
</div>
@@ -608,41 +614,57 @@ const PerformanceChart = React.memo(({ selectedStock, onClearSelection }: Perfor
608614
<div className="grid grid-cols-3 gap-4 pt-4 border-t">
609615
<div>
610616
<p className="text-xs text-muted-foreground">Position P&L Today</p>
611-
<p className={`text-sm font-medium ${getStockMetrics(selectedStock).dailyReturn >= 0 ? 'text-success' : 'text-danger'
617+
<p className={`text-sm font-medium ${positionsLoading ? '' : getStockMetrics(selectedStock).dailyReturn >= 0 ? 'text-success' : 'text-danger'
612618
}`}>
613-
{getStockMetrics(selectedStock).dailyReturn >= 0 ? '+' : ''}
614-
${getStockMetrics(selectedStock).dailyReturn.toFixed(2)}
615-
({getStockMetrics(selectedStock).dailyReturnPercent >= 0 ? '+' : ''}
616-
{getStockMetrics(selectedStock).dailyReturnPercent.toFixed(2)}%)
619+
{positionsLoading ? 'Loading...' : (
620+
<>
621+
{getStockMetrics(selectedStock).dailyReturn >= 0 ? '+' : ''}
622+
${getStockMetrics(selectedStock).dailyReturn.toFixed(2)}
623+
({getStockMetrics(selectedStock).dailyReturnPercent >= 0 ? '+' : ''}
624+
{getStockMetrics(selectedStock).dailyReturnPercent.toFixed(2)}%)
625+
</>
626+
)}
617627
</p>
618628
</div>
619629
<div>
620630
<p className="text-xs text-muted-foreground">Total Position P&L</p>
621-
<p className={`text-sm font-medium ${getStockMetrics(selectedStock).totalReturn >= 0 ? 'text-success' : 'text-danger'
631+
<p className={`text-sm font-medium ${positionsLoading ? '' : getStockMetrics(selectedStock).totalReturn >= 0 ? 'text-success' : 'text-danger'
622632
}`}>
623-
{getStockMetrics(selectedStock).totalReturn >= 0 ? '+' : ''}
624-
${getStockMetrics(selectedStock).totalReturn.toFixed(2)}
625-
({getStockMetrics(selectedStock).totalReturnPercent >= 0 ? '+' : ''}
626-
{getStockMetrics(selectedStock).totalReturnPercent.toFixed(2)}%)
633+
{positionsLoading ? 'Loading...' : (
634+
<>
635+
{getStockMetrics(selectedStock).totalReturn >= 0 ? '+' : ''}
636+
${getStockMetrics(selectedStock).totalReturn.toFixed(2)}
637+
({getStockMetrics(selectedStock).totalReturnPercent >= 0 ? '+' : ''}
638+
{getStockMetrics(selectedStock).totalReturnPercent.toFixed(2)}%)
639+
</>
640+
)}
627641
</p>
628642
</div>
629643
<div>
630644
<p className="text-xs text-muted-foreground">Shares Owned</p>
631-
<p className="text-sm font-medium">{getStockMetrics(selectedStock).shares}</p>
645+
<p className="text-sm font-medium">
646+
{positionsLoading ? 'Loading...' : getStockMetrics(selectedStock).shares}
647+
</p>
632648
</div>
633649
</div>
634650
<div className="grid grid-cols-3 gap-4 pt-4 border-t">
635651
<div>
636652
<p className="text-xs text-muted-foreground">Avg Cost</p>
637-
<p className="text-sm font-medium">${getStockMetrics(selectedStock).avgCost.toFixed(2)}</p>
653+
<p className="text-sm font-medium">
654+
{positionsLoading ? 'Loading...' : `$${getStockMetrics(selectedStock).avgCost.toFixed(2)}`}
655+
</p>
638656
</div>
639657
<div>
640658
<p className="text-xs text-muted-foreground">Total Position</p>
641-
<p className="text-sm font-medium">${getStockMetrics(selectedStock).positionValue.toLocaleString()}</p>
659+
<p className="text-sm font-medium">
660+
{positionsLoading ? 'Loading...' : `$${getStockMetrics(selectedStock).positionValue.toLocaleString()}`}
661+
</p>
642662
</div>
643663
<div>
644664
<p className="text-xs text-muted-foreground">% of Portfolio</p>
645-
<p className="text-sm font-medium">{getStockMetrics(selectedStock).portfolioPercent.toFixed(1)}%</p>
665+
<p className="text-sm font-medium">
666+
{positionsLoading ? 'Loading...' : `${getStockMetrics(selectedStock).portfolioPercent.toFixed(1)}%`}
667+
</p>
646668
</div>
647669
</div>
648670
</div>

src/components/RecentTrades.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -470,12 +470,10 @@ function RecentTrades() {
470470
<span></span>
471471
</>
472472
)}
473-
<span>{decision.timestamp}</span>
474-
{decision.executedAt && (
475-
<>
476-
<span></span>
477-
<span>Executed {decision.executedAt}</span>
478-
</>
473+
{decision.executedAt ? (
474+
<span>Executed {decision.executedAt}</span>
475+
) : (
476+
<span>{decision.timestamp}</span>
479477
)}
480478
{decision.alpacaFilledPrice && decision.alpacaFilledQty && (
481479
<>

src/components/analysis-detail/AnalysisActionsTab.tsx

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
import { useState } from "react";
12
import {
23
Activity,
34
ArrowRight,
45
Shield,
56
TrendingDown,
6-
TrendingUp
7+
TrendingUp,
8+
RefreshCw
79
} from "lucide-react";
810
import { Card } from "@/components/ui/card";
11+
import { Button } from "@/components/ui/button";
912
import TradeOrderCard from "./TradeOrderCard";
13+
import RebalanceDetailModal from "@/components/RebalanceDetailModal";
1014

1115
interface AnalysisActionsTabProps {
1216
analysisData: any;
@@ -25,17 +29,36 @@ export default function AnalysisActionsTab({
2529
isExecuting = false,
2630
getConfidenceColor
2731
}: AnalysisActionsTabProps) {
32+
const [detailModalOpen, setDetailModalOpen] = useState(false);
33+
2834
// For rebalance analyses, show a message that actions are handled at rebalance level
2935
if (analysisData.rebalance_request_id) {
3036
return (
31-
<div className="rounded-lg border bg-muted/20 p-6 text-center">
32-
<Shield className="w-12 h-12 mx-auto mb-4 text-muted-foreground opacity-50" />
33-
<h3 className="text-lg font-semibold mb-2">Part of Rebalance Workflow</h3>
34-
<p className="text-sm text-muted-foreground">
35-
This analysis is part of a portfolio rebalance. Trade orders will be generated
36-
after all stock analyses complete and are managed in the Rebalance view.
37-
</p>
38-
</div>
37+
<>
38+
<div className="rounded-lg border bg-muted/20 p-6 text-center">
39+
<Shield className="w-12 h-12 mx-auto mb-4 text-muted-foreground opacity-50" />
40+
<h3 className="text-lg font-semibold mb-2">Part of Rebalance Workflow</h3>
41+
<p className="text-sm text-muted-foreground mb-4">
42+
This analysis is part of a portfolio rebalance. Trade orders will be generated
43+
after all stock analyses complete and are managed in the Rebalance view.
44+
</p>
45+
<Button
46+
onClick={() => setDetailModalOpen(true)}
47+
variant="outline"
48+
className="gap-2"
49+
>
50+
<RefreshCw className="h-4 w-4" />
51+
View Rebalance Details
52+
</Button>
53+
</div>
54+
55+
{/* Rebalance Detail Modal */}
56+
<RebalanceDetailModal
57+
rebalanceId={analysisData.rebalance_request_id}
58+
isOpen={detailModalOpen}
59+
onClose={() => setDetailModalOpen(false)}
60+
/>
61+
</>
3962
);
4063
}
4164

src/components/rebalance-detail/RebalanceActionsTab.tsx

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
TrendingDown,
2121
Shield,
2222
XCircle,
23+
ExternalLink,
2324
} from "lucide-react";
2425
import { supabase } from "@/lib/supabase";
2526
import { useToast } from "@/hooks/use-toast";
@@ -148,49 +149,67 @@ function RebalancePositionCard({ position, onApprove, onReject, isExecuted, orde
148149

149150
{/* Action buttons and details */}
150151
<div className="flex flex-col gap-1">
151-
{/* Alpaca Order Status Badge */}
152-
{orderStatus?.alpacaOrderId && orderStatus?.alpacaStatus && (
153-
<div className="flex items-center justify-center">
154-
{(() => {
155-
const status = (orderStatus.alpacaStatus || '').toLowerCase();
156-
let variant: any = "outline";
157-
let icon = null;
158-
let displayText = orderStatus.alpacaStatus;
159-
let customClasses = "";
160-
161-
if (status === 'filled') {
162-
variant = "success";
163-
icon = <CheckCircle className="h-3 w-3 mr-1" />;
164-
displayText = "filled";
165-
} else if (status === 'partially_filled') {
166-
variant = "default";
167-
icon = <Clock className="h-3 w-3 mr-1" />;
168-
displayText = "partial filled";
169-
customClasses = "bg-blue-500 text-white border-blue-500";
170-
} else if (['new', 'pending_new', 'accepted'].includes(status)) {
171-
variant = "warning";
172-
icon = <Clock className="h-3 w-3 mr-1" />;
173-
displayText = "placed";
174-
} else if (['canceled', 'cancelled'].includes(status)) {
175-
variant = "destructive";
176-
icon = <XCircle className="h-3 w-3 mr-1" />;
177-
displayText = "failed";
178-
} else if (status === 'rejected') {
179-
variant = "destructive";
180-
icon = <XCircle className="h-3 w-3 mr-1" />;
181-
displayText = "rejected";
182-
}
152+
{/* Alpaca Order Link and Status Badge - Horizontal Layout */}
153+
{orderStatus?.alpacaOrderId && (
154+
<div className="flex items-center gap-1">
155+
<Button
156+
size="sm"
157+
variant="outline"
158+
className="h-7 px-2 text-xs border-slate-700"
159+
onClick={() => {
160+
const baseUrl = 'https://app.alpaca.markets';
161+
window.open(`${baseUrl}/dashboard/order/${orderStatus.alpacaOrderId}`, '_blank');
162+
}}
163+
>
164+
<ExternalLink className="h-3 w-3 mr-1" />
165+
Alpaca
166+
</Button>
183167

184-
return (
185-
<Badge
186-
variant={variant}
187-
className={`text-xs ${customClasses}`}
188-
>
189-
{icon}
190-
{displayText}
191-
</Badge>
192-
);
193-
})()}
168+
{/* Alpaca Order Status Badge */}
169+
{orderStatus?.alpacaStatus && (
170+
<div className="flex items-center justify-center">
171+
{(() => {
172+
const status = (orderStatus.alpacaStatus || '').toLowerCase();
173+
let variant: any = "outline";
174+
let icon = null;
175+
let displayText = orderStatus.alpacaStatus;
176+
let customClasses = "";
177+
178+
if (status === 'filled') {
179+
variant = "success";
180+
icon = <CheckCircle className="h-3 w-3 mr-1" />;
181+
displayText = "filled";
182+
} else if (status === 'partially_filled') {
183+
variant = "default";
184+
icon = <Clock className="h-3 w-3 mr-1" />;
185+
displayText = "partial filled";
186+
customClasses = "bg-blue-500 text-white border-blue-500";
187+
} else if (['new', 'pending_new', 'accepted'].includes(status)) {
188+
variant = "warning";
189+
icon = <Clock className="h-3 w-3 mr-1" />;
190+
displayText = "placed";
191+
} else if (['canceled', 'cancelled'].includes(status)) {
192+
variant = "destructive";
193+
icon = <XCircle className="h-3 w-3 mr-1" />;
194+
displayText = "failed";
195+
} else if (status === 'rejected') {
196+
variant = "destructive";
197+
icon = <XCircle className="h-3 w-3 mr-1" />;
198+
displayText = "rejected";
199+
}
200+
201+
return (
202+
<Badge
203+
variant={variant}
204+
className={`text-xs ${customClasses}`}
205+
>
206+
{icon}
207+
{displayText}
208+
</Badge>
209+
);
210+
})()}
211+
</div>
212+
)}
194213
</div>
195214
)}
196215

0 commit comments

Comments
 (0)