Scribbler310 commited on
Commit
dbc70ee
·
0 Parent(s):

feat: portfolio dashboard v1.0

Browse files
.dockerignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ node_modules
2
+ dist
3
+ .git
4
+ .env
5
+ .DS_Store
6
+ *.log
.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
2
+ *.jpg filter=lfs diff=lfs merge=lfs -text
3
+ *.svg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
25
+
26
+ .env
.hfignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ node_modules/
2
+ .git/
3
+ dist/
4
+ .env
5
+ package-lock.json
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stage 1: Build the React application
2
+ FROM node:20-alpine as build-stage
3
+
4
+ WORKDIR /app
5
+
6
+ # Copy package files and install dependencies
7
+ COPY package*.json ./
8
+ RUN npm install
9
+
10
+ # Copy the rest of the application code
11
+ COPY . .
12
+
13
+ # Build the app for production
14
+ # Note: VITE_ environment variables must be available at build time
15
+ RUN npm run build
16
+
17
+ # Stage 2: Serve the app with Nginx
18
+ FROM nginx:stable-alpine
19
+
20
+ # Copy the built assets from the build stage to Nginx's html directory
21
+ COPY --from=build-stage /app/dist /usr/share/nginx/html
22
+
23
+ # Copy a custom Nginx configuration to handle React Router (SPA) routing
24
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
25
+
26
+ # Expose port 7860 (default for Hugging Face Spaces)
27
+ EXPOSE 7860
28
+
29
+ # Adjust Nginx to run on port 7860
30
+ RUN sed -i 's/listen\(.*\)80;/listen 7860;/' /etc/nginx/conf.d/default.conf
31
+
32
+ CMD ["nginx", "-g", "daemon off;"]
README.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: GS Portfolio Dashboard
3
+ emoji: 📊
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # Goldman Sachs Style Portfolio Dashboard
11
+
12
+ A premium, jargon-free investment portfolio management platform built with React, Tailwind CSS, and Framer Motion.
13
+
14
+ ## Features
15
+ - **Radical Transparency**: Clear explanations of health scores and risk levels.
16
+ - **Dynamic Heatmap**: Visualize allocation and performance at a glance.
17
+ - **AI Investment Committee**: Real-time analysis of stock holdings.
18
+ - **Scenario Planning**: Macroeconomic rebalancing engine.
19
+
20
+ ## Deployment
21
+ This app is dockerized and served via Nginx on port 7860 for Hugging Face Spaces.
Stock Portfolio - Sheet1.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Stock Name ,Unit Bought,Price When Purchased,Purchase Cost
2
+ OKTA,5,98.26,491.3
3
+ AMZN,5,181.96,909.8
4
+ NVDA,250,138,34500
5
+ SEDG,10,25.05,250.5
6
+ TSLA,5,244.5,1222.5
Stocks data for tableau - Stock Prices.csv ADDED
@@ -0,0 +1,1262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Stock,Date,Close,Daily%change
2
+ OKTA ,5/2/2025 16:00:00,112.9,
3
+ OKTA ,5/5/2025 16:00:00,115.71,0.02488928255
4
+ OKTA ,5/6/2025 16:00:00,117.07,0.01175352174
5
+ OKTA ,5/7/2025 16:00:00,118.03,0.008200222089
6
+ OKTA ,5/8/2025 16:00:00,120.75,0.02304498856
7
+ OKTA ,5/9/2025 16:00:00,119.45,-0.01076604555
8
+ OKTA ,5/12/2025 16:00:00,124.17,0.03951444119
9
+ OKTA ,5/13/2025 16:00:00,124.05,-0.0009664170089
10
+ OKTA ,5/14/2025 16:00:00,123.34,-0.005723498589
11
+ OKTA ,5/15/2025 16:00:00,124.39,0.008513053348
12
+ OKTA ,5/16/2025 16:00:00,127.3,0.02339416352
13
+ OKTA ,5/19/2025 16:00:00,126.44,-0.006755695208
14
+ OKTA ,5/20/2025 16:00:00,125.54,-0.007118000633
15
+ OKTA ,5/21/2025 16:00:00,122.06,-0.02772024853
16
+ OKTA ,5/22/2025 16:00:00,122.06,0
17
+ OKTA ,5/23/2025 16:00:00,123.72,0.01359986892
18
+ OKTA ,5/27/2025 16:00:00,125.5,0.01438732622
19
+ OKTA ,5/28/2025 16:00:00,105.23,-0.1615139442
20
+ OKTA ,5/29/2025 16:00:00,106.63,0.01330419082
21
+ OKTA ,5/30/2025 16:00:00,103.17,-0.03244865422
22
+ OKTA ,6/2/2025 16:00:00,104.73,0.01512067461
23
+ OKTA ,6/3/2025 16:00:00,103.58,-0.01098061682
24
+ OKTA ,6/4/2025 16:00:00,105.6,0.01950183433
25
+ OKTA ,6/5/2025 16:00:00,104.18,-0.0134469697
26
+ OKTA ,6/6/2025 16:00:00,105.08,0.008638894222
27
+ OKTA ,6/9/2025 16:00:00,101.2,-0.03692424819
28
+ OKTA ,6/10/2025 16:00:00,100.78,-0.004150197628
29
+ OKTA ,6/11/2025 16:00:00,100.44,-0.003373685255
30
+ OKTA ,6/12/2025 16:00:00,100.18,-0.002588610115
31
+ OKTA ,6/13/2025 16:00:00,97.48,-0.02695148732
32
+ OKTA ,6/16/2025 16:00:00,99.28,0.01846532622
33
+ OKTA ,6/17/2025 16:00:00,98.67,-0.006144238517
34
+ OKTA ,6/18/2025 16:00:00,99,0.003344481605
35
+ OKTA ,6/20/2025 16:00:00,99.42,0.004242424242
36
+ OKTA ,6/23/2025 16:00:00,98.66,-0.007644337156
37
+ OKTA ,6/24/2025 16:00:00,98.53,-0.001317656598
38
+ OKTA ,6/25/2025 16:00:00,98.21,-0.003247741805
39
+ OKTA ,6/26/2025 16:00:00,98.13,-0.0008145809999
40
+ OKTA ,6/27/2025 16:00:00,98.43,0.003057169061
41
+ OKTA ,6/30/2025 16:00:00,99.97,0.01564563649
42
+ OKTA ,7/1/2025 16:00:00,98.55,-0.01420426128
43
+ OKTA ,7/2/2025 16:00:00,98.14,-0.004160324708
44
+ OKTA ,7/3/2025 13:05:00,99.11,0.009883839413
45
+ OKTA ,7/7/2025 16:00:00,97.4,-0.01725355665
46
+ OKTA ,7/8/2025 16:00:00,97.53,0.001334702259
47
+ OKTA ,7/9/2025 16:00:00,99.15,0.01661027376
48
+ OKTA ,7/10/2025 16:00:00,94.41,-0.04780635401
49
+ OKTA ,7/11/2025 16:00:00,91.56,-0.03018748014
50
+ OKTA ,7/14/2025 16:00:00,91.97,0.004477937964
51
+ OKTA ,7/15/2025 16:00:00,91.1,-0.009459606393
52
+ OKTA ,7/16/2025 16:00:00,91.07,-0.0003293084523
53
+ OKTA ,7/17/2025 16:00:00,92.1,0.01130998133
54
+ OKTA ,7/18/2025 16:00:00,95.43,0.03615635179
55
+ OKTA ,7/21/2025 16:00:00,95.86,0.00450592057
56
+ OKTA ,7/22/2025 16:00:00,95.65,-0.002190694763
57
+ OKTA ,7/23/2025 16:00:00,95.63,-0.0002090956613
58
+ OKTA ,7/24/2025 16:00:00,97.89,0.02363275123
59
+ OKTA ,7/25/2025 16:00:00,101.1,0.03279190929
60
+ OKTA ,7/28/2025 16:00:00,97.84,-0.03224530168
61
+ OKTA ,7/29/2025 16:00:00,98.99,0.01175388389
62
+ OKTA ,7/30/2025 16:00:00,99.77,0.007879583796
63
+ OKTA ,7/31/2025 16:00:00,97.8,-0.01974541445
64
+ OKTA ,8/1/2025 16:00:00,95.13,-0.0273006135
65
+ OKTA ,8/4/2025 16:00:00,97.72,0.0272259014
66
+ OKTA ,8/5/2025 16:00:00,95.98,-0.01780597626
67
+ OKTA ,8/6/2025 16:00:00,97.75,0.01844134195
68
+ OKTA ,8/7/2025 16:00:00,93.58,-0.04265984655
69
+ OKTA ,8/8/2025 16:00:00,91.55,-0.02169266937
70
+ OKTA ,8/11/2025 16:00:00,88.51,-0.03320589842
71
+ OKTA ,8/12/2025 16:00:00,89.33,0.009264489888
72
+ OKTA ,8/13/2025 16:00:00,90.98,0.01847083846
73
+ OKTA ,8/14/2025 16:00:00,88.61,-0.02604968125
74
+ OKTA ,8/15/2025 16:00:00,92.02,0.03848324117
75
+ OKTA ,8/18/2025 16:00:00,91.38,-0.00695500978
76
+ OKTA ,8/19/2025 16:00:00,91.15,-0.002516962136
77
+ OKTA ,8/20/2025 16:00:00,91.03,-0.001316511245
78
+ OKTA ,8/21/2025 16:00:00,89.78,-0.01373173679
79
+ OKTA ,8/22/2025 16:00:00,92.05,0.02528402762
80
+ OKTA ,8/25/2025 16:00:00,91.35,-0.007604562738
81
+ OKTA ,8/26/2025 16:00:00,91.56,0.002298850575
82
+ OKTA ,8/27/2025 16:00:00,93.03,0.01605504587
83
+ OKTA ,8/28/2025 16:00:00,92.59,-0.0047296571
84
+ OKTA ,8/29/2025 16:00:00,92.77,0.001944054434
85
+ OKTA ,9/2/2025 16:00:00,89.5,-0.03524846394
86
+ OKTA ,9/3/2025 16:00:00,89.82,0.003575418994
87
+ OKTA ,9/4/2025 16:00:00,89.74,-0.0008906702293
88
+ OKTA ,9/5/2025 16:00:00,91.48,0.019389347
89
+ OKTA ,9/8/2025 16:00:00,92.68,0.01311762134
90
+ OKTA ,9/9/2025 16:00:00,93.85,0.01262408287
91
+ OKTA ,9/10/2025 16:00:00,90.21,-0.03878529568
92
+ OKTA ,9/11/2025 16:00:00,91.96,0.01939917969
93
+ OKTA ,9/12/2025 16:00:00,90.34,-0.01761635494
94
+ OKTA ,9/15/2025 16:00:00,90.91,0.006309497454
95
+ OKTA ,9/16/2025 16:00:00,89.92,-0.0108898911
96
+ OKTA ,9/17/2025 16:00:00,90,0.0008896797153
97
+ OKTA ,9/18/2025 16:00:00,93.6,0.04
98
+ OKTA ,9/19/2025 16:00:00,93.37,-0.002457264957
99
+ OKTA ,9/22/2025 16:00:00,92.38,-0.0106029774
100
+ OKTA ,9/23/2025 16:00:00,92.2,-0.001948473696
101
+ OKTA ,9/24/2025 16:00:00,92.2,0
102
+ OKTA ,9/25/2025 16:00:00,91.19,-0.01095444685
103
+ OKTA ,9/26/2025 16:00:00,91.16,-0.0003289834412
104
+ OKTA ,9/29/2025 16:00:00,93.86,0.02961825362
105
+ OKTA ,9/30/2025 16:00:00,91.7,-0.02301299808
106
+ OKTA ,10/1/2025 16:00:00,91.69,-0.0001090512541
107
+ OKTA ,10/2/2025 16:00:00,94.92,0.03522739666
108
+ OKTA ,10/3/2025 16:00:00,93.3,-0.01706700379
109
+ OKTA ,10/6/2025 16:00:00,93.71,0.004394426581
110
+ OKTA ,10/7/2025 16:00:00,90.89,-0.03009283961
111
+ OKTA ,10/8/2025 16:00:00,92.63,0.01914402024
112
+ OKTA ,10/9/2025 16:00:00,93.64,0.01090359495
113
+ OKTA ,10/10/2025 16:00:00,89.35,-0.04581375481
114
+ OKTA ,10/13/2025 16:00:00,90.14,0.008841634024
115
+ OKTA ,10/14/2025 16:00:00,89.08,-0.01175948525
116
+ OKTA ,10/15/2025 16:00:00,88.35,-0.008194881006
117
+ OKTA ,10/16/2025 16:00:00,87.71,-0.007243916242
118
+ OKTA ,10/17/2025 16:00:00,87.43,-0.003192338388
119
+ OKTA ,10/20/2025 16:00:00,88.32,0.01017957223
120
+ OKTA ,10/21/2025 16:00:00,89.45,0.01279438406
121
+ OKTA ,10/22/2025 16:00:00,87.04,-0.02694242594
122
+ OKTA ,10/23/2025 16:00:00,88.55,0.01734834559
123
+ OKTA ,10/24/2025 16:00:00,89.07,0.005872388481
124
+ OKTA ,10/27/2025 16:00:00,90.01,0.01055349725
125
+ OKTA ,10/28/2025 16:00:00,89.31,-0.007776913676
126
+ OKTA ,10/29/2025 16:00:00,87.65,-0.01858694435
127
+ OKTA ,10/30/2025 16:00:00,87.91,0.002966343411
128
+ OKTA ,10/31/2025 16:00:00,91.53,0.04117847799
129
+ OKTA ,11/3/2025 16:00:00,91.08,-0.004916420846
130
+ OKTA ,11/4/2025 16:00:00,86.97,-0.04512516469
131
+ OKTA ,11/5/2025 16:00:00,87.13,0.001839714844
132
+ OKTA ,11/6/2025 16:00:00,85.87,-0.01446115001
133
+ OKTA ,11/7/2025 16:00:00,85.21,-0.007686037033
134
+ OKTA ,11/10/2025 16:00:00,85.74,0.006219927239
135
+ OKTA ,11/11/2025 16:00:00,85.58,-0.001866106835
136
+ OKTA ,11/12/2025 16:00:00,84.69,-0.01039962608
137
+ OKTA ,11/13/2025 16:00:00,83.76,-0.01098122565
138
+ OKTA ,11/14/2025 16:00:00,83.94,0.002148997135
139
+ OKTA ,11/17/2025 16:00:00,81.07,-0.03419108887
140
+ OKTA ,11/18/2025 16:00:00,81.03,-0.0004934007648
141
+ OKTA ,11/19/2025 16:00:00,80.09,-0.01160064174
142
+ OKTA ,11/20/2025 16:00:00,78.32,-0.02210013735
143
+ OKTA ,11/21/2025 16:00:00,78.68,0.004596527068
144
+ OKTA ,11/24/2025 16:00:00,79.15,0.005973563803
145
+ OKTA ,11/25/2025 16:00:00,81.16,0.02539481996
146
+ OKTA ,11/26/2025 16:00:00,80.56,-0.007392804337
147
+ OKTA ,11/28/2025 13:05:00,80.33,-0.002855014896
148
+ OKTA ,12/1/2025 16:00:00,80.64,0.00385908129
149
+ OKTA ,12/2/2025 16:00:00,81.87,0.01525297619
150
+ OKTA ,12/3/2025 16:00:00,86.34,0.05459875412
151
+ OKTA ,12/4/2025 16:00:00,85.9,-0.005096131573
152
+ OKTA ,12/5/2025 16:00:00,85.89,-0.0001164144354
153
+ OKTA ,12/8/2025 16:00:00,87.29,0.0162999185
154
+ OKTA ,12/9/2025 16:00:00,87.79,0.005728032993
155
+ OKTA ,12/10/2025 16:00:00,89.84,0.02335117895
156
+ OKTA ,12/11/2025 16:00:00,90.59,0.008348174533
157
+ OKTA ,12/12/2025 16:00:00,90.18,-0.004525885859
158
+ OKTA ,12/15/2025 16:00:00,88.2,-0.02195608782
159
+ OKTA ,12/16/2025 16:00:00,90.59,0.02709750567
160
+ OKTA ,12/17/2025 16:00:00,88.42,-0.02395407882
161
+ OKTA ,12/18/2025 16:00:00,90.23,0.02047048179
162
+ OKTA ,12/19/2025 16:00:00,90.21,-0.0002216557686
163
+ OKTA ,12/22/2025 16:00:00,90.94,0.008092229243
164
+ OKTA ,12/23/2025 16:00:00,89.06,-0.02067297119
165
+ OKTA ,12/24/2025 13:05:00,88.39,-0.00752301819
166
+ OKTA ,12/26/2025 16:00:00,88.61,0.00248896934
167
+ OKTA ,12/29/2025 16:00:00,88.08,-0.005981266223
168
+ OKTA ,12/30/2025 16:00:00,87.43,-0.007379654859
169
+ OKTA ,12/31/2025 16:00:00,86.47,-0.01098021274
170
+ OKTA ,1/2/2026 16:00:00,83.64,-0.0327281138
171
+ OKTA ,1/5/2026 16:00:00,87.71,0.04866092779
172
+ OKTA ,1/6/2026 16:00:00,90.36,0.0302132026
173
+ OKTA ,1/7/2026 16:00:00,93.84,0.0385126162
174
+ OKTA ,1/8/2026 16:00:00,93.93,0.0009590792839
175
+ OKTA ,1/9/2026 16:00:00,92.23,-0.01809858405
176
+ OKTA ,1/12/2026 16:00:00,93.57,0.01452889515
177
+ OKTA ,1/13/2026 16:00:00,94.07,0.005343593032
178
+ OKTA ,1/14/2026 16:00:00,93.35,-0.007653874774
179
+ OKTA ,1/15/2026 16:00:00,91.94,-0.01510444563
180
+ OKTA ,1/16/2026 16:00:00,89.55,-0.02599521427
181
+ OKTA ,1/20/2026 16:00:00,87.71,-0.02054718035
182
+ OKTA ,1/21/2026 16:00:00,88.94,0.01402348649
183
+ OKTA ,1/22/2026 16:00:00,91.46,0.02833370812
184
+ OKTA ,1/23/2026 16:00:00,90.76,-0.007653619068
185
+ OKTA ,1/26/2026 16:00:00,91.29,0.005839576906
186
+ OKTA ,1/27/2026 16:00:00,91.46,0.001862197393
187
+ OKTA ,1/28/2026 16:00:00,90.74,-0.007872293899
188
+ OKTA ,1/29/2026 16:00:00,85.69,-0.05565351554
189
+ OKTA ,1/30/2026 16:00:00,84.48,-0.01412066752
190
+ OKTA ,2/2/2026 16:00:00,88.13,0.04320549242
191
+ OKTA ,2/3/2026 16:00:00,82.31,-0.06603880631
192
+ OKTA ,2/4/2026 16:00:00,83.42,0.01348560321
193
+ OKTA ,2/5/2026 16:00:00,82.15,-0.01522416687
194
+ OKTA ,2/6/2026 16:00:00,86.74,0.05587340231
195
+ OKTA ,2/9/2026 16:00:00,88.18,0.01660133733
196
+ OKTA ,2/10/2026 16:00:00,88.45,0.003061918802
197
+ OKTA ,2/11/2026 16:00:00,88.18,-0.003052572075
198
+ OKTA ,2/12/2026 16:00:00,84.91,-0.03708323883
199
+ OKTA ,2/13/2026 16:00:00,87.26,0.02767636321
200
+ OKTA ,2/17/2026 16:00:00,82.46,-0.055008022
201
+ OKTA ,2/18/2026 16:00:00,82.93,0.005699733204
202
+ OKTA ,2/19/2026 16:00:00,81.8,-0.0136259496
203
+ OKTA ,2/20/2026 16:00:00,74.29,-0.09180929095
204
+ OKTA ,2/23/2026 16:00:00,69.51,-0.06434244178
205
+ OKTA ,2/24/2026 16:00:00,71.14,0.02344986333
206
+ OKTA ,2/25/2026 16:00:00,73.01,0.02628619623
207
+ OKTA ,2/26/2026 16:00:00,75.25,0.03068072867
208
+ OKTA ,2/27/2026 16:00:00,72.5,-0.0365448505
209
+ OKTA ,3/2/2026 16:00:00,73.97,0.02027586207
210
+ OKTA ,3/3/2026 16:00:00,72.52,-0.01960254157
211
+ OKTA ,3/4/2026 16:00:00,71.74,-0.01075565361
212
+ OKTA ,3/5/2026 16:00:00,79.65,0.1102592696
213
+ OKTA ,3/6/2026 16:00:00,80.72,0.01343377276
214
+ OKTA ,3/9/2026 16:00:00,79.71,-0.0125123885
215
+ OKTA ,3/10/2026 16:00:00,79.61,-0.001254547736
216
+ OKTA ,3/11/2026 16:00:00,80.85,0.01557593267
217
+ OKTA ,3/12/2026 16:00:00,78.95,-0.02350030921
218
+ OKTA ,3/13/2026 16:00:00,79.16,0.002659911336
219
+ OKTA ,3/16/2026 16:00:00,77.16,-0.0252652855
220
+ OKTA ,3/17/2026 16:00:00,78.53,0.01775531363
221
+ OKTA ,3/18/2026 16:00:00,78.43,-0.001273398701
222
+ OKTA ,3/19/2026 16:00:00,80.76,0.02970801989
223
+ OKTA ,3/20/2026 16:00:00,78.41,-0.02909856365
224
+ OKTA ,3/23/2026 16:00:00,81.1,0.03430684862
225
+ OKTA ,3/24/2026 16:00:00,76.76,-0.05351418002
226
+ OKTA ,3/25/2026 16:00:00,78.12,0.01771756123
227
+ OKTA ,3/26/2026 16:00:00,79.38,0.01612903226
228
+ OKTA ,3/27/2026 16:00:00,73.23,-0.07747543462
229
+ OKTA ,3/30/2026 16:00:00,75.47,0.0305885566
230
+ OKTA ,3/31/2026 16:00:00,78.71,0.04293096595
231
+ OKTA ,4/1/2026 16:00:00,79.15,0.005590141024
232
+ OKTA ,4/2/2026 16:00:00,80.19,0.01313960834
233
+ OKTA ,4/6/2026 16:00:00,80.56,0.004614041651
234
+ OKTA ,4/7/2026 16:00:00,79.34,-0.01514399206
235
+ OKTA ,4/8/2026 16:00:00,76.04,-0.04159314343
236
+ OKTA ,4/9/2026 16:00:00,67.76,-0.1088900579
237
+ OKTA ,4/10/2026 16:00:00,62.93,-0.07128099174
238
+ OKTA ,4/13/2026 16:00:00,65.46,0.0402034006
239
+ OKTA ,4/14/2026 16:00:00,64.09,-0.02092881149
240
+ OKTA ,4/15/2026 16:00:00,67.35,0.05086596973
241
+ OKTA ,4/16/2026 16:00:00,72.01,0.06919079436
242
+ OKTA ,4/17/2026 16:00:00,72.25,0.003332870435
243
+ OKTA ,4/20/2026 16:00:00,75.76,0.04858131488
244
+ OKTA ,4/21/2026 16:00:00,77.64,0.02481520591
245
+ OKTA ,4/22/2026 16:00:00,78.7,0.01365275631
246
+ OKTA ,4/23/2026 16:00:00,76.04,-0.03379923761
247
+ OKTA ,4/24/2026 16:00:00,75.98,-0.0007890583903
248
+ OKTA ,4/27/2026 16:00:00,76.14,0.00210581732
249
+ OKTA ,4/28/2026 16:00:00,76.2,0.0007880220646
250
+ OKTA ,4/29/2026 16:00:00,76.16,-0.0005249343832
251
+ OKTA ,4/30/2026 16:00:00,73.65,-0.03295693277
252
+ OKTA ,5/1/2026 16:00:00,75.78,-0.004989495798
253
+ ,,,
254
+ stock ,Date,Close,
255
+ SEDG,5/2/2025 16:00:00,13.1,
256
+ SEDG,5/5/2025 16:00:00,12.92,-0.01374045802
257
+ SEDG,5/6/2025 16:00:00,14.37,0.1122291022
258
+ SEDG,5/7/2025 16:00:00,14.77,0.02783576896
259
+ SEDG,5/8/2025 16:00:00,18.29,0.2383209208
260
+ SEDG,5/9/2025 16:00:00,19.84,0.08474576271
261
+ SEDG,5/12/2025 16:00:00,19.16,-0.03427419355
262
+ SEDG,5/13/2025 16:00:00,18.15,-0.05271398747
263
+ SEDG,5/14/2025 16:00:00,17.93,-0.01212121212
264
+ SEDG,5/15/2025 16:00:00,20.84,0.1622978249
265
+ SEDG,5/16/2025 16:00:00,22.02,0.056621881
266
+ SEDG,5/19/2025 16:00:00,20.76,-0.05722070845
267
+ SEDG,5/20/2025 16:00:00,20.65,-0.005298651252
268
+ SEDG,5/21/2025 16:00:00,19.84,-0.0392251816
269
+ SEDG,5/22/2025 16:00:00,19.84,0
270
+ SEDG,5/23/2025 16:00:00,16.7,-0.158266129
271
+ SEDG,5/27/2025 16:00:00,17.22,0.03113772455
272
+ SEDG,5/28/2025 16:00:00,16.92,-0.01742160279
273
+ SEDG,5/29/2025 16:00:00,16.6,-0.01891252955
274
+ SEDG,5/30/2025 16:00:00,17.86,0.07590361446
275
+ SEDG,6/2/2025 16:00:00,17.1,-0.04255319149
276
+ SEDG,6/3/2025 16:00:00,18.11,0.05906432749
277
+ SEDG,6/4/2025 16:00:00,17.47,-0.03533959139
278
+ SEDG,6/5/2025 16:00:00,17.74,0.01545506583
279
+ SEDG,6/6/2025 16:00:00,18.19,0.02536640361
280
+ SEDG,6/9/2025 16:00:00,18.72,0.0291368884
281
+ SEDG,6/10/2025 16:00:00,20.93,0.1180555556
282
+ SEDG,6/11/2025 16:00:00,20.83,-0.004777830865
283
+ SEDG,6/12/2025 16:00:00,21.02,0.009121459434
284
+ SEDG,6/13/2025 16:00:00,23.3,0.1084681256
285
+ SEDG,6/16/2025 16:00:00,23.98,0.02918454936
286
+ SEDG,6/17/2025 16:00:00,15.96,-0.3344453711
287
+ SEDG,6/18/2025 16:00:00,16.98,0.06390977444
288
+ SEDG,6/20/2025 16:00:00,16.52,-0.02709069494
289
+ SEDG,6/23/2025 16:00:00,16.06,-0.02784503632
290
+ SEDG,6/24/2025 16:00:00,18.93,0.1787048568
291
+ SEDG,6/25/2025 16:00:00,19.09,0.008452192287
292
+ SEDG,6/26/2025 16:00:00,20.07,0.05133577789
293
+ SEDG,6/27/2025 16:00:00,19.8,-0.0134529148
294
+ SEDG,6/30/2025 16:00:00,20.4,0.0303030303
295
+ SEDG,7/1/2025 16:00:00,21.86,0.07156862745
296
+ SEDG,7/2/2025 16:00:00,23.6,0.07959743824
297
+ SEDG,7/3/2025 13:05:00,27.54,0.1669491525
298
+ SEDG,7/7/2025 16:00:00,26.43,-0.04030501089
299
+ SEDG,7/8/2025 16:00:00,26.15,-0.01059402194
300
+ SEDG,7/9/2025 16:00:00,27.09,0.03594646272
301
+ SEDG,7/10/2025 16:00:00,27.57,0.01771871539
302
+ SEDG,7/11/2025 16:00:00,25.62,-0.07072905332
303
+ SEDG,7/14/2025 16:00:00,26.72,0.04293520687
304
+ SEDG,7/15/2025 16:00:00,27.37,0.02432634731
305
+ SEDG,7/16/2025 16:00:00,24.96,-0.08805261235
306
+ SEDG,7/17/2025 16:00:00,25.26,0.01201923077
307
+ SEDG,7/18/2025 16:00:00,26.62,0.05384006334
308
+ SEDG,7/21/2025 16:00:00,28.83,0.0830202855
309
+ SEDG,7/22/2025 16:00:00,31.86,0.1050988554
310
+ SEDG,7/23/2025 16:00:00,29.07,-0.08757062147
311
+ SEDG,7/24/2025 16:00:00,28.47,-0.02063983488
312
+ SEDG,7/25/2025 16:00:00,27.23,-0.0435546189
313
+ SEDG,7/28/2025 16:00:00,27.06,-0.006243114212
314
+ SEDG,7/29/2025 16:00:00,24.95,-0.07797487066
315
+ SEDG,7/30/2025 16:00:00,25.81,0.03446893788
316
+ SEDG,7/31/2025 16:00:00,25.66,-0.005811700891
317
+ SEDG,8/1/2025 16:00:00,25.55,-0.004286827747
318
+ SEDG,8/4/2025 16:00:00,25.63,0.00313111546
319
+ SEDG,8/5/2025 16:00:00,26.12,0.01911822083
320
+ SEDG,8/6/2025 16:00:00,25.79,-0.01263399694
321
+ SEDG,8/7/2025 16:00:00,24.42,-0.05312136487
322
+ SEDG,8/8/2025 16:00:00,24.94,0.02129402129
323
+ SEDG,8/11/2025 16:00:00,24.9,-0.001603849238
324
+ SEDG,8/12/2025 16:00:00,25.1,0.008032128514
325
+ SEDG,8/13/2025 16:00:00,26.46,0.05418326693
326
+ SEDG,8/14/2025 16:00:00,25.67,-0.029856387
327
+ SEDG,8/15/2025 16:00:00,30.06,0.1710167511
328
+ SEDG,8/18/2025 16:00:00,31.16,0.03659347971
329
+ SEDG,8/19/2025 16:00:00,31.99,0.02663671374
330
+ SEDG,8/20/2025 16:00:00,32.06,0.002188183807
331
+ SEDG,8/21/2025 16:00:00,30.21,-0.05770430443
332
+ SEDG,8/22/2025 16:00:00,34.3,0.1353856339
333
+ SEDG,8/25/2025 16:00:00,31.99,-0.06734693878
334
+ SEDG,8/26/2025 16:00:00,32.25,0.008127539856
335
+ SEDG,8/27/2025 16:00:00,33.09,0.02604651163
336
+ SEDG,8/28/2025 16:00:00,33.25,0.004835297673
337
+ SEDG,8/29/2025 16:00:00,33.82,0.01714285714
338
+ SEDG,9/2/2025 16:00:00,31.53,-0.06771141336
339
+ SEDG,9/3/2025 16:00:00,33.21,0.05328258801
340
+ SEDG,9/4/2025 16:00:00,34.16,0.02860584161
341
+ SEDG,9/5/2025 16:00:00,34.42,0.007611241218
342
+ SEDG,9/8/2025 16:00:00,33.44,-0.02847181871
343
+ SEDG,9/9/2025 16:00:00,30.04,-0.1016746411
344
+ SEDG,9/10/2025 16:00:00,29.42,-0.0206391478
345
+ SEDG,9/11/2025 16:00:00,29.49,0.002379333787
346
+ SEDG,9/12/2025 16:00:00,28.97,-0.01763309596
347
+ SEDG,9/15/2025 16:00:00,30.59,0.05591991716
348
+ SEDG,9/16/2025 16:00:00,33.11,0.0823798627
349
+ SEDG,9/17/2025 16:00:00,34.11,0.03020235578
350
+ SEDG,9/18/2025 16:00:00,34.71,0.01759014952
351
+ SEDG,9/19/2025 16:00:00,35.45,0.02131950447
352
+ SEDG,9/22/2025 16:00:00,38.58,0.08829337094
353
+ SEDG,9/23/2025 16:00:00,35.93,-0.06868843961
354
+ SEDG,9/24/2025 16:00:00,37.47,0.04286111884
355
+ SEDG,9/25/2025 16:00:00,37.9,0.01147584734
356
+ SEDG,9/26/2025 16:00:00,39.45,0.04089709763
357
+ SEDG,9/29/2025 16:00:00,37.68,-0.04486692015
358
+ SEDG,9/30/2025 16:00:00,37,-0.01804670913
359
+ SEDG,10/1/2025 16:00:00,38.62,0.04378378378
360
+ SEDG,10/2/2025 16:00:00,37.96,-0.01708959089
361
+ SEDG,10/3/2025 16:00:00,36.24,-0.04531085353
362
+ SEDG,10/6/2025 16:00:00,37.09,0.02345474614
363
+ SEDG,10/7/2025 16:00:00,35.55,-0.04152062551
364
+ SEDG,10/8/2025 16:00:00,35.66,0.003094233474
365
+ SEDG,10/9/2025 16:00:00,38.61,0.08272574313
366
+ SEDG,10/10/2025 16:00:00,35.06,-0.09194509195
367
+ SEDG,10/13/2025 16:00:00,36.46,0.03993154592
368
+ SEDG,10/14/2025 16:00:00,37.73,0.03483269336
369
+ SEDG,10/15/2025 16:00:00,40.53,0.07421150278
370
+ SEDG,10/16/2025 16:00:00,40.11,-0.0103626943
371
+ SEDG,10/17/2025 16:00:00,37.02,-0.0770381451
372
+ SEDG,10/20/2025 16:00:00,40,0.08049702863
373
+ SEDG,10/21/2025 16:00:00,38.77,-0.03075
374
+ SEDG,10/22/2025 16:00:00,37.5,-0.03275728656
375
+ SEDG,10/23/2025 16:00:00,37.82,0.008533333333
376
+ SEDG,10/24/2025 16:00:00,39.7,0.0497091486
377
+ SEDG,10/27/2025 16:00:00,39.74,0.001007556675
378
+ SEDG,10/28/2025 16:00:00,37.84,-0.04781077001
379
+ SEDG,10/29/2025 16:00:00,36.3,-0.04069767442
380
+ SEDG,10/30/2025 16:00:00,34.34,-0.05399449036
381
+ SEDG,10/31/2025 16:00:00,35.09,0.02184041934
382
+ SEDG,11/3/2025 16:00:00,32.87,-0.06326588772
383
+ SEDG,11/4/2025 16:00:00,31.82,-0.0319440219
384
+ SEDG,11/5/2025 16:00:00,41.02,0.2891263356
385
+ SEDG,11/6/2025 16:00:00,38.88,-0.05216967333
386
+ SEDG,11/7/2025 16:00:00,40,0.02880658436
387
+ SEDG,11/10/2025 16:00:00,45.38,0.1345
388
+ SEDG,11/11/2025 16:00:00,44.7,-0.0149845747
389
+ SEDG,11/12/2025 16:00:00,42.51,-0.04899328859
390
+ SEDG,11/13/2025 16:00:00,36.42,-0.1432604093
391
+ SEDG,11/14/2025 16:00:00,36.18,-0.006589785832
392
+ SEDG,11/17/2025 16:00:00,34.27,-0.05279159757
393
+ SEDG,11/18/2025 16:00:00,34.8,0.01546542165
394
+ SEDG,11/19/2025 16:00:00,33.46,-0.03850574713
395
+ SEDG,11/20/2025 16:00:00,33.09,-0.01105797968
396
+ SEDG,11/21/2025 16:00:00,34.45,0.04110003022
397
+ SEDG,11/24/2025 16:00:00,34.45,0
398
+ SEDG,11/25/2025 16:00:00,34.99,0.01567489115
399
+ SEDG,11/26/2025 16:00:00,35.47,0.0137182052
400
+ SEDG,11/28/2025 13:05:00,36.53,0.02988440936
401
+ SEDG,12/1/2025 16:00:00,32.92,-0.0988228853
402
+ SEDG,12/2/2025 16:00:00,32.66,-0.007897934386
403
+ SEDG,12/3/2025 16:00:00,31.61,-0.03214941825
404
+ SEDG,12/4/2025 16:00:00,31.94,0.01043973426
405
+ SEDG,12/5/2025 16:00:00,29.52,-0.07576706324
406
+ SEDG,12/8/2025 16:00:00,30.43,0.03082655827
407
+ SEDG,12/9/2025 16:00:00,30.26,-0.005586592179
408
+ SEDG,12/10/2025 16:00:00,31.56,0.04296100463
409
+ SEDG,12/11/2025 16:00:00,32.02,0.01457541191
410
+ SEDG,12/12/2025 16:00:00,29.53,-0.07776389756
411
+ SEDG,12/15/2025 16:00:00,28.54,-0.03352522858
412
+ SEDG,12/16/2025 16:00:00,29.48,0.03293622985
413
+ SEDG,12/17/2025 16:00:00,28.92,-0.01899592944
414
+ SEDG,12/18/2025 16:00:00,28.47,-0.01556016598
415
+ SEDG,12/19/2025 16:00:00,29.06,0.02072356867
416
+ SEDG,12/22/2025 16:00:00,30.91,0.06366139023
417
+ SEDG,12/23/2025 16:00:00,30.48,-0.01391135555
418
+ SEDG,12/24/2025 13:05:00,30.74,0.008530183727
419
+ SEDG,12/26/2025 16:00:00,30.45,-0.009433962264
420
+ SEDG,12/29/2025 16:00:00,29.19,-0.04137931034
421
+ SEDG,12/30/2025 16:00:00,29.02,-0.005823912299
422
+ SEDG,12/31/2025 16:00:00,28.85,-0.005858028946
423
+ SEDG,1/2/2026 16:00:00,31.36,0.0870017331
424
+ SEDG,1/5/2026 16:00:00,31.26,-0.00318877551
425
+ SEDG,1/6/2026 16:00:00,30.78,-0.01535508637
426
+ SEDG,1/7/2026 16:00:00,30.52,-0.008447043535
427
+ SEDG,1/8/2026 16:00:00,30.26,-0.008519003932
428
+ SEDG,1/9/2026 16:00:00,32.89,0.08691341705
429
+ SEDG,1/12/2026 16:00:00,35.31,0.07357859532
430
+ SEDG,1/13/2026 16:00:00,34.31,-0.02832058907
431
+ SEDG,1/14/2026 16:00:00,34.78,0.01369863014
432
+ SEDG,1/15/2026 16:00:00,33.84,-0.02702702703
433
+ SEDG,1/16/2026 16:00:00,33.91,0.00206855792
434
+ SEDG,1/20/2026 16:00:00,32.49,-0.04187555293
435
+ SEDG,1/21/2026 16:00:00,33.34,0.02616189597
436
+ SEDG,1/22/2026 16:00:00,34.54,0.03599280144
437
+ SEDG,1/23/2026 16:00:00,34.6,0.001737116387
438
+ SEDG,1/26/2026 16:00:00,34.41,-0.00549132948
439
+ SEDG,1/27/2026 16:00:00,34.81,0.01162452775
440
+ SEDG,1/28/2026 16:00:00,35.8,0.02844010342
441
+ SEDG,1/29/2026 16:00:00,34.04,-0.04916201117
442
+ SEDG,1/30/2026 16:00:00,30.95,-0.09077555817
443
+ SEDG,2/2/2026 16:00:00,30.64,-0.01001615509
444
+ SEDG,2/3/2026 16:00:00,30.97,0.01077023499
445
+ SEDG,2/4/2026 16:00:00,35.04,0.1314175008
446
+ SEDG,2/5/2026 16:00:00,33.28,-0.0502283105
447
+ SEDG,2/6/2026 16:00:00,35.92,0.07932692308
448
+ SEDG,2/9/2026 16:00:00,36.8,0.02449888641
449
+ SEDG,2/10/2026 16:00:00,36.79,-0.0002717391304
450
+ SEDG,2/11/2026 16:00:00,36.33,-0.01250339766
451
+ SEDG,2/12/2026 16:00:00,34.41,-0.05284888522
452
+ SEDG,2/13/2026 16:00:00,35.53,0.03254867771
453
+ SEDG,2/17/2026 16:00:00,37.13,0.04503236701
454
+ SEDG,2/18/2026 16:00:00,35.1,-0.05467277134
455
+ SEDG,2/19/2026 16:00:00,34.96,-0.003988603989
456
+ SEDG,2/20/2026 16:00:00,37.9,0.08409610984
457
+ SEDG,2/23/2026 16:00:00,39.38,0.03905013193
458
+ SEDG,2/24/2026 16:00:00,43.07,0.093702387
459
+ SEDG,2/25/2026 16:00:00,42.45,-0.01439517065
460
+ SEDG,2/26/2026 16:00:00,40.4,-0.04829210836
461
+ SEDG,2/27/2026 16:00:00,35.4,-0.1237623762
462
+ SEDG,3/2/2026 16:00:00,40.6,0.1468926554
463
+ SEDG,3/3/2026 16:00:00,37.81,-0.06871921182
464
+ SEDG,3/4/2026 16:00:00,37.94,0.003438243851
465
+ SEDG,3/5/2026 16:00:00,35.24,-0.07116499736
466
+ SEDG,3/6/2026 16:00:00,33.41,-0.05192962543
467
+ SEDG,3/9/2026 16:00:00,34.59,0.03531876684
468
+ SEDG,3/10/2026 16:00:00,38.11,0.1017635155
469
+ SEDG,3/11/2026 16:00:00,36.09,-0.05300446077
470
+ SEDG,3/12/2026 16:00:00,35.19,-0.02493765586
471
+ SEDG,3/13/2026 16:00:00,37.44,0.06393861893
472
+ SEDG,3/16/2026 16:00:00,40.71,0.08733974359
473
+ SEDG,3/17/2026 16:00:00,42.88,0.05330385655
474
+ SEDG,3/18/2026 16:00:00,44.88,0.04664179104
475
+ SEDG,3/19/2026 16:00:00,45.66,0.01737967914
476
+ SEDG,3/20/2026 16:00:00,51.73,0.1329391152
477
+ SEDG,3/23/2026 16:00:00,46.73,-0.09665571235
478
+ SEDG,3/24/2026 16:00:00,47.67,0.02011555746
479
+ SEDG,3/25/2026 16:00:00,51.28,0.07572897
480
+ SEDG,3/26/2026 16:00:00,50.27,-0.01969578783
481
+ SEDG,3/27/2026 16:00:00,51.76,0.0296399443
482
+ SEDG,3/30/2026 16:00:00,47.37,-0.08481452859
483
+ SEDG,3/31/2026 16:00:00,51.05,0.07768629935
484
+ SEDG,4/1/2026 16:00:00,51.87,0.01606268364
485
+ SEDG,4/2/2026 16:00:00,48.75,-0.06015037594
486
+ SEDG,4/6/2026 16:00:00,45.09,-0.07507692308
487
+ SEDG,4/7/2026 16:00:00,43.85,-0.02750055445
488
+ SEDG,4/8/2026 16:00:00,43.52,-0.007525655644
489
+ SEDG,4/9/2026 16:00:00,41.84,-0.03860294118
490
+ SEDG,4/10/2026 16:00:00,41.76,-0.001912045889
491
+ SEDG,4/13/2026 16:00:00,43.21,0.03472222222
492
+ SEDG,4/14/2026 16:00:00,42.98,-0.005322841935
493
+ SEDG,4/15/2026 16:00:00,37.83,-0.1198231736
494
+ SEDG,4/16/2026 16:00:00,38.91,0.02854877082
495
+ SEDG,4/17/2026 16:00:00,38.3,-0.0156772038
496
+ SEDG,4/20/2026 16:00:00,39.82,0.03968668407
497
+ SEDG,4/21/2026 16:00:00,40.57,0.0188347564
498
+ SEDG,4/22/2026 16:00:00,42.6,0.05003697313
499
+ SEDG,4/23/2026 16:00:00,47.36,0.1117370892
500
+ SEDG,4/24/2026 16:00:00,45.83,-0.03230574324
501
+ SEDG,4/27/2026 16:00:00,47.38,0.0338206415
502
+ SEDG,4/28/2026 16:00:00,44.29,-0.0652173913
503
+ SEDG,4/29/2026 16:00:00,41.58,-0.061187627
504
+ SEDG,4/30/2026 16:00:00,42.86,0.03078403078
505
+ SDEG,5/1/2026 16:00:00,42.91,0.001166588894
506
+ ,,,
507
+ Stock,Date,Close,
508
+ AMZN,5/2/2025 16:00:00,189.98,
509
+ AMZN,5/5/2025 16:00:00,186.35,-0.01910727445
510
+ AMZN,5/6/2025 16:00:00,185.01,-0.007190770056
511
+ AMZN,5/7/2025 16:00:00,188.71,0.01999891898
512
+ AMZN,5/8/2025 16:00:00,192.08,0.01785808913
513
+ AMZN,5/9/2025 16:00:00,193.06,0.005102040816
514
+ AMZN,5/12/2025 16:00:00,208.64,0.08070030042
515
+ AMZN,5/13/2025 16:00:00,211.37,0.01308473926
516
+ AMZN,5/14/2025 16:00:00,210.25,-0.005298765198
517
+ AMZN,5/15/2025 16:00:00,205.17,-0.02416171225
518
+ AMZN,5/16/2025 16:00:00,205.59,0.002047082907
519
+ AMZN,5/19/2025 16:00:00,206.16,0.00277250839
520
+ AMZN,5/20/2025 16:00:00,204.07,-0.01013775708
521
+ AMZN,5/21/2025 16:00:00,201.12,-0.01445582398
522
+ AMZN,5/22/2025 16:00:00,201.12,0
523
+ AMZN,5/23/2025 16:00:00,200.99,-0.0006463802705
524
+ AMZN,5/27/2025 16:00:00,206.02,0.0250261207
525
+ AMZN,5/28/2025 16:00:00,204.72,-0.006310066984
526
+ AMZN,5/29/2025 16:00:00,205.7,0.004787026182
527
+ AMZN,5/30/2025 16:00:00,205.01,-0.003354399611
528
+ AMZN,6/2/2025 16:00:00,206.65,0.007999609775
529
+ AMZN,6/3/2025 16:00:00,205.71,-0.004548753932
530
+ AMZN,6/4/2025 16:00:00,207.23,0.007389042827
531
+ AMZN,6/5/2025 16:00:00,207.91,0.003281378179
532
+ AMZN,6/6/2025 16:00:00,213.57,0.02722331778
533
+ AMZN,6/9/2025 16:00:00,216.98,0.01596666198
534
+ AMZN,6/10/2025 16:00:00,217.61,0.00290349341
535
+ AMZN,6/11/2025 16:00:00,213.2,-0.02026561279
536
+ AMZN,6/12/2025 16:00:00,213.24,0.0001876172608
537
+ AMZN,6/13/2025 16:00:00,212.1,-0.005346088914
538
+ AMZN,6/16/2025 16:00:00,216.1,0.01885902876
539
+ AMZN,6/17/2025 16:00:00,214.82,-0.005923183711
540
+ AMZN,6/18/2025 16:00:00,212.52,-0.01070663812
541
+ AMZN,6/20/2025 16:00:00,209.69,-0.01331639375
542
+ AMZN,6/23/2025 16:00:00,208.47,-0.005818112452
543
+ AMZN,6/24/2025 16:00:00,212.77,0.02062646904
544
+ AMZN,6/25/2025 16:00:00,211.99,-0.003665930347
545
+ AMZN,6/26/2025 16:00:00,217.12,0.02419925468
546
+ AMZN,6/27/2025 16:00:00,223.3,0.02846352248
547
+ AMZN,6/30/2025 16:00:00,219.39,-0.01751007613
548
+ AMZN,7/1/2025 16:00:00,220.46,0.004877159397
549
+ AMZN,7/2/2025 16:00:00,219.92,-0.002449423932
550
+ AMZN,7/3/2025 13:05:00,223.41,0.01586940706
551
+ AMZN,7/7/2025 16:00:00,223.47,0.0002685645226
552
+ AMZN,7/8/2025 16:00:00,219.36,-0.01839173043
553
+ AMZN,7/9/2025 16:00:00,222.54,0.01449671772
554
+ AMZN,7/10/2025 16:00:00,222.26,-0.001258200773
555
+ AMZN,7/11/2025 16:00:00,225.02,0.01241788896
556
+ AMZN,7/14/2025 16:00:00,225.69,0.00297751311
557
+ AMZN,7/15/2025 16:00:00,226.35,0.00292436528
558
+ AMZN,7/16/2025 16:00:00,223.19,-0.01396068036
559
+ AMZN,7/17/2025 16:00:00,223.88,0.003091536359
560
+ AMZN,7/18/2025 16:00:00,226.13,0.0100500268
561
+ AMZN,7/21/2025 16:00:00,229.3,0.01401848494
562
+ AMZN,7/22/2025 16:00:00,227.47,-0.007980811164
563
+ AMZN,7/23/2025 16:00:00,228.29,0.003604870972
564
+ AMZN,7/24/2025 16:00:00,232.23,0.01725874984
565
+ AMZN,7/25/2025 16:00:00,231.44,-0.00340179994
566
+ AMZN,7/28/2025 16:00:00,232.79,0.005833045282
567
+ AMZN,7/29/2025 16:00:00,231.01,-0.007646376563
568
+ AMZN,7/30/2025 16:00:00,230.19,-0.003549629886
569
+ AMZN,7/31/2025 16:00:00,234.11,0.01702941049
570
+ AMZN,8/1/2025 16:00:00,214.75,-0.08269616847
571
+ AMZN,8/4/2025 16:00:00,211.65,-0.01443538999
572
+ AMZN,8/5/2025 16:00:00,213.75,0.009922041106
573
+ AMZN,8/6/2025 16:00:00,222.31,0.04004678363
574
+ AMZN,8/7/2025 16:00:00,223.13,0.003688543026
575
+ AMZN,8/8/2025 16:00:00,222.69,-0.001971944606
576
+ AMZN,8/11/2025 16:00:00,221.3,-0.006241860883
577
+ AMZN,8/12/2025 16:00:00,221.47,0.0007681879801
578
+ AMZN,8/13/2025 16:00:00,224.56,0.01395222829
579
+ AMZN,8/14/2025 16:00:00,230.98,0.02858924118
580
+ AMZN,8/15/2025 16:00:00,231.03,0.0002164689584
581
+ AMZN,8/18/2025 16:00:00,231.49,0.001991083409
582
+ AMZN,8/19/2025 16:00:00,228.01,-0.01503304678
583
+ AMZN,8/20/2025 16:00:00,223.81,-0.01842024473
584
+ AMZN,8/21/2025 16:00:00,221.95,-0.008310620616
585
+ AMZN,8/22/2025 16:00:00,228.84,0.03104302771
586
+ AMZN,8/25/2025 16:00:00,227.94,-0.003932878867
587
+ AMZN,8/26/2025 16:00:00,228.71,0.003378081951
588
+ AMZN,8/27/2025 16:00:00,229.12,0.001792663198
589
+ AMZN,8/28/2025 16:00:00,231.6,0.01082402235
590
+ AMZN,8/29/2025 16:00:00,229,-0.01122625216
591
+ AMZN,9/2/2025 16:00:00,225.34,-0.01598253275
592
+ AMZN,9/3/2025 16:00:00,225.99,0.002884530043
593
+ AMZN,9/4/2025 16:00:00,235.68,0.04287800345
594
+ AMZN,9/5/2025 16:00:00,232.33,-0.01421418873
595
+ AMZN,9/8/2025 16:00:00,235.84,0.01510782077
596
+ AMZN,9/9/2025 16:00:00,238.24,0.01017639077
597
+ AMZN,9/10/2025 16:00:00,230.33,-0.0332018133
598
+ AMZN,9/11/2025 16:00:00,229.95,-0.001649806799
599
+ AMZN,9/12/2025 16:00:00,228.15,-0.00782778865
600
+ AMZN,9/15/2025 16:00:00,231.43,0.01437650668
601
+ AMZN,9/16/2025 16:00:00,234.05,0.01132091777
602
+ AMZN,9/17/2025 16:00:00,231.62,-0.01038239692
603
+ AMZN,9/18/2025 16:00:00,231.23,-0.001683792419
604
+ AMZN,9/19/2025 16:00:00,231.48,0.001081174588
605
+ AMZN,9/22/2025 16:00:00,227.63,-0.01663210645
606
+ AMZN,9/23/2025 16:00:00,220.71,-0.03040021087
607
+ AMZN,9/24/2025 16:00:00,220.21,-0.002265416157
608
+ AMZN,9/25/2025 16:00:00,218.15,-0.009354706871
609
+ AMZN,9/26/2025 16:00:00,219.78,0.007471922989
610
+ AMZN,9/29/2025 16:00:00,222.17,0.01087451087
611
+ AMZN,9/30/2025 16:00:00,219.57,-0.01170275015
612
+ AMZN,10/1/2025 16:00:00,220.63,0.004827617616
613
+ AMZN,10/2/2025 16:00:00,222.41,0.008067805829
614
+ AMZN,10/3/2025 16:00:00,219.51,-0.01303898206
615
+ AMZN,10/6/2025 16:00:00,220.9,0.006332285545
616
+ AMZN,10/7/2025 16:00:00,221.78,0.003983703033
617
+ AMZN,10/8/2025 16:00:00,225.22,0.01551086662
618
+ AMZN,10/9/2025 16:00:00,227.74,0.01118905959
619
+ AMZN,10/10/2025 16:00:00,216.37,-0.04992535347
620
+ AMZN,10/13/2025 16:00:00,220.07,0.01710033739
621
+ AMZN,10/14/2025 16:00:00,216.39,-0.01672195211
622
+ AMZN,10/15/2025 16:00:00,215.57,-0.003789454226
623
+ AMZN,10/16/2025 16:00:00,214.47,-0.005102750847
624
+ AMZN,10/17/2025 16:00:00,213.04,-0.006667599198
625
+ AMZN,10/20/2025 16:00:00,216.48,0.0161472024
626
+ AMZN,10/21/2025 16:00:00,222.03,0.02563747228
627
+ AMZN,10/22/2025 16:00:00,217.95,-0.01837589515
628
+ AMZN,10/23/2025 16:00:00,221.09,0.01440697408
629
+ AMZN,10/24/2025 16:00:00,224.21,0.01411190013
630
+ AMZN,10/27/2025 16:00:00,226.97,0.01230988805
631
+ AMZN,10/28/2025 16:00:00,229.25,0.01004538045
632
+ AMZN,10/29/2025 16:00:00,230.3,0.004580152672
633
+ AMZN,10/30/2025 16:00:00,222.86,-0.03230568823
634
+ AMZN,10/31/2025 16:00:00,244.22,0.09584492507
635
+ AMZN,11/3/2025 16:00:00,254,0.04004586029
636
+ AMZN,11/4/2025 16:00:00,249.32,-0.01842519685
637
+ AMZN,11/5/2025 16:00:00,250.2,0.003529600513
638
+ AMZN,11/6/2025 16:00:00,243.04,-0.02861710631
639
+ AMZN,11/7/2025 16:00:00,244.41,0.005636932192
640
+ AMZN,11/10/2025 16:00:00,248.4,0.01632502762
641
+ AMZN,11/11/2025 16:00:00,249.1,0.002818035427
642
+ AMZN,11/12/2025 16:00:00,244.2,-0.01967081493
643
+ AMZN,11/13/2025 16:00:00,237.58,-0.02710892711
644
+ AMZN,11/14/2025 16:00:00,234.69,-0.0121643236
645
+ AMZN,11/17/2025 16:00:00,232.87,-0.007754910733
646
+ AMZN,11/18/2025 16:00:00,222.55,-0.04431657148
647
+ AMZN,11/19/2025 16:00:00,222.69,0.0006290721186
648
+ AMZN,11/20/2025 16:00:00,217.14,-0.02492253806
649
+ AMZN,11/21/2025 16:00:00,220.69,0.01634889933
650
+ AMZN,11/24/2025 16:00:00,226.28,0.02532964792
651
+ AMZN,11/25/2025 16:00:00,229.67,0.01498143893
652
+ AMZN,11/26/2025 16:00:00,229.16,-0.00222057735
653
+ AMZN,11/28/2025 13:05:00,233.22,0.01771687904
654
+ AMZN,12/1/2025 16:00:00,233.88,0.002829945974
655
+ AMZN,12/2/2025 16:00:00,234.42,0.002308876347
656
+ AMZN,12/3/2025 16:00:00,232.38,-0.008702329153
657
+ AMZN,12/4/2025 16:00:00,229.11,-0.01407177898
658
+ AMZN,12/5/2025 16:00:00,229.53,0.001833180568
659
+ AMZN,12/8/2025 16:00:00,226.89,-0.01150176448
660
+ AMZN,12/9/2025 16:00:00,227.92,0.004539644762
661
+ AMZN,12/10/2025 16:00:00,231.78,0.01693576694
662
+ AMZN,12/11/2025 16:00:00,230.28,-0.006471654155
663
+ AMZN,12/12/2025 16:00:00,226.19,-0.01776098662
664
+ AMZN,12/15/2025 16:00:00,222.54,-0.01613687608
665
+ AMZN,12/16/2025 16:00:00,222.56,0.00008987148378
666
+ AMZN,12/17/2025 16:00:00,221.27,-0.005796189792
667
+ AMZN,12/18/2025 16:00:00,226.76,0.02481131649
668
+ AMZN,12/19/2025 16:00:00,227.35,0.002601869818
669
+ AMZN,12/22/2025 16:00:00,228.43,0.004750384869
670
+ AMZN,12/23/2025 16:00:00,232.14,0.0162412993
671
+ AMZN,12/24/2025 13:05:00,232.38,0.001033858878
672
+ AMZN,12/26/2025 16:00:00,232.52,0.0006024614855
673
+ AMZN,12/29/2025 16:00:00,232.07,-0.001935317392
674
+ AMZN,12/30/2025 16:00:00,232.53,0.001982160555
675
+ AMZN,12/31/2025 16:00:00,230.82,-0.007353889821
676
+ AMZN,1/2/2026 16:00:00,226.5,-0.01871588251
677
+ AMZN,1/5/2026 16:00:00,233.06,0.02896247241
678
+ AMZN,1/6/2026 16:00:00,240.93,0.03376812838
679
+ AMZN,1/7/2026 16:00:00,241.56,0.002614867389
680
+ AMZN,1/8/2026 16:00:00,246.29,0.01958105647
681
+ AMZN,1/9/2026 16:00:00,247.38,0.004425677047
682
+ AMZN,1/12/2026 16:00:00,246.47,-0.003678551217
683
+ AMZN,1/13/2026 16:00:00,242.6,-0.01570170812
684
+ AMZN,1/14/2026 16:00:00,236.65,-0.02452596867
685
+ AMZN,1/15/2026 16:00:00,238.18,0.006465244031
686
+ AMZN,1/16/2026 16:00:00,239.12,0.003946595012
687
+ AMZN,1/20/2026 16:00:00,231,-0.03395784543
688
+ AMZN,1/21/2026 16:00:00,231.31,0.001341991342
689
+ AMZN,1/22/2026 16:00:00,234.34,0.01309930396
690
+ AMZN,1/23/2026 16:00:00,239.16,0.02056840488
691
+ AMZN,1/26/2026 16:00:00,238.42,-0.003094162903
692
+ AMZN,1/27/2026 16:00:00,244.68,0.02625618656
693
+ AMZN,1/28/2026 16:00:00,243.01,-0.006825241131
694
+ AMZN,1/29/2026 16:00:00,241.73,-0.005267272952
695
+ AMZN,1/30/2026 16:00:00,239.3,-0.01005253796
696
+ AMZN,2/2/2026 16:00:00,242.96,0.01529460928
697
+ AMZN,2/3/2026 16:00:00,238.62,-0.01786302272
698
+ AMZN,2/4/2026 16:00:00,232.99,-0.02359399883
699
+ AMZN,2/5/2026 16:00:00,222.69,-0.04420790592
700
+ AMZN,2/6/2026 16:00:00,210.32,-0.05554807131
701
+ AMZN,2/9/2026 16:00:00,208.72,-0.007607455306
702
+ AMZN,2/10/2026 16:00:00,206.96,-0.008432349559
703
+ AMZN,2/11/2026 16:00:00,204.08,-0.01391573251
704
+ AMZN,2/12/2026 16:00:00,199.6,-0.02195217562
705
+ AMZN,2/13/2026 16:00:00,198.79,-0.004058116232
706
+ AMZN,2/17/2026 16:00:00,201.15,0.01187182454
707
+ AMZN,2/18/2026 16:00:00,204.79,0.0180959483
708
+ AMZN,2/19/2026 16:00:00,204.86,0.0003418135651
709
+ AMZN,2/20/2026 16:00:00,210.11,0.02562725764
710
+ AMZN,2/23/2026 16:00:00,205.27,-0.02303555281
711
+ AMZN,2/24/2026 16:00:00,208.56,0.01602767087
712
+ AMZN,2/25/2026 16:00:00,210.64,0.009973149214
713
+ AMZN,2/26/2026 16:00:00,207.92,-0.01291302697
714
+ AMZN,2/27/2026 16:00:00,210,0.01000384763
715
+ AMZN,3/2/2026 16:00:00,208.39,-0.007666666667
716
+ AMZN,3/3/2026 16:00:00,208.73,0.001631556217
717
+ AMZN,3/4/2026 16:00:00,216.82,0.03875820438
718
+ AMZN,3/5/2026 16:00:00,218.94,0.009777695785
719
+ AMZN,3/6/2026 16:00:00,213.21,-0.02617155385
720
+ AMZN,3/9/2026 16:00:00,213.49,0.001313259228
721
+ AMZN,3/10/2026 16:00:00,214.33,0.00393461052
722
+ AMZN,3/11/2026 16:00:00,212.65,-0.007838380068
723
+ AMZN,3/12/2026 16:00:00,209.53,-0.01467199624
724
+ AMZN,3/13/2026 16:00:00,207.67,-0.008877010452
725
+ AMZN,3/16/2026 16:00:00,211.74,0.01959840131
726
+ AMZN,3/17/2026 16:00:00,215.2,0.01634079532
727
+ AMZN,3/18/2026 16:00:00,209.87,-0.02476765799
728
+ AMZN,3/19/2026 16:00:00,208.76,-0.005288988421
729
+ AMZN,3/20/2026 16:00:00,205.37,-0.01623874305
730
+ AMZN,3/23/2026 16:00:00,210.14,0.02322637191
731
+ AMZN,3/24/2026 16:00:00,207.24,-0.01380032359
732
+ AMZN,3/25/2026 16:00:00,211.71,0.02156919514
733
+ AMZN,3/26/2026 16:00:00,207.54,-0.019696755
734
+ AMZN,3/27/2026 16:00:00,199.34,-0.03951045582
735
+ AMZN,3/30/2026 16:00:00,200.95,0.008076652955
736
+ AMZN,3/31/2026 16:00:00,208.27,0.03642697188
737
+ AMZN,4/1/2026 16:00:00,210.57,0.01104335718
738
+ AMZN,4/2/2026 16:00:00,209.77,-0.003799211664
739
+ AMZN,4/6/2026 16:00:00,212.79,0.01439672022
740
+ AMZN,4/7/2026 16:00:00,213.77,0.004605479581
741
+ AMZN,4/8/2026 16:00:00,221.25,0.03499087805
742
+ AMZN,4/9/2026 16:00:00,233.65,0.05604519774
743
+ AMZN,4/10/2026 16:00:00,238.38,0.02024395463
744
+ AMZN,4/13/2026 16:00:00,239.89,0.006334424029
745
+ AMZN,4/14/2026 16:00:00,249.02,0.03805911043
746
+ AMZN,4/15/2026 16:00:00,248.5,-0.002088185688
747
+ AMZN,4/16/2026 16:00:00,249.7,0.004828973843
748
+ AMZN,4/17/2026 16:00:00,250.56,0.00344413296
749
+ AMZN,4/20/2026 16:00:00,248.28,-0.009099616858
750
+ AMZN,4/21/2026 16:00:00,249.91,0.006565168358
751
+ AMZN,4/22/2026 16:00:00,255.36,0.02180785083
752
+ AMZN,4/23/2026 16:00:00,255.08,-0.001096491228
753
+ AMZN,4/24/2026 16:00:00,263.99,0.03493021797
754
+ AMZN,4/27/2026 16:00:00,261.12,-0.01087162393
755
+ AMZN,4/28/2026 16:00:00,259.7,-0.005438112745
756
+ AMZN,4/29/2026 16:00:00,263.04,0.01286099345
757
+ AMZN,4/30/2026 16:00:00,265.06,0.007679440389
758
+ AMZN,5/1/2026 16:00:00,268.26,0.01984489051
759
+ Stock,Date,Close,
760
+ TSLA,5/2/2025 16:00:00,287.21,
761
+ TSLA,5/5/2025 16:00:00,280.26,-0.02419832179
762
+ TSLA,5/6/2025 16:00:00,275.35,-0.01751944623
763
+ TSLA,5/7/2025 16:00:00,276.22,0.003159615035
764
+ TSLA,5/8/2025 16:00:00,284.82,0.03113460285
765
+ TSLA,5/9/2025 16:00:00,298.26,0.04718769749
766
+ TSLA,5/12/2025 16:00:00,318.38,0.06745792262
767
+ TSLA,5/13/2025 16:00:00,334.07,0.04928073371
768
+ TSLA,5/14/2025 16:00:00,347.68,0.04073996468
769
+ TSLA,5/15/2025 16:00:00,342.82,-0.01397837092
770
+ TSLA,5/16/2025 16:00:00,349.98,0.02088559594
771
+ TSLA,5/19/2025 16:00:00,342.09,-0.02254414538
772
+ TSLA,5/20/2025 16:00:00,343.82,0.005057148704
773
+ TSLA,5/21/2025 16:00:00,334.62,-0.02675818742
774
+ TSLA,5/22/2025 16:00:00,334.62,0
775
+ TSLA,5/23/2025 16:00:00,339.34,0.01410555257
776
+ TSLA,5/27/2025 16:00:00,362.89,0.06939942241
777
+ TSLA,5/28/2025 16:00:00,356.9,-0.01650637934
778
+ TSLA,5/29/2025 16:00:00,358.43,0.004286915102
779
+ TSLA,5/30/2025 16:00:00,346.46,-0.03339564211
780
+ TSLA,6/2/2025 16:00:00,342.69,-0.01088148704
781
+ TSLA,6/3/2025 16:00:00,344.27,0.004610580992
782
+ TSLA,6/4/2025 16:00:00,332.05,-0.03549539606
783
+ TSLA,6/5/2025 16:00:00,284.7,-0.1425990062
784
+ TSLA,6/6/2025 16:00:00,295.14,0.03667017914
785
+ TSLA,6/9/2025 16:00:00,308.58,0.04553771092
786
+ TSLA,6/10/2025 16:00:00,326.09,0.05674379415
787
+ TSLA,6/11/2025 16:00:00,326.43,0.001042656935
788
+ TSLA,6/12/2025 16:00:00,319.11,-0.02242440952
789
+ TSLA,6/13/2025 16:00:00,325.31,0.01942903701
790
+ TSLA,6/16/2025 16:00:00,329.13,0.01174264548
791
+ TSLA,6/17/2025 16:00:00,316.35,-0.03882964178
792
+ TSLA,6/18/2025 16:00:00,322.05,0.01801801802
793
+ TSLA,6/20/2025 16:00:00,322.16,0.0003415618693
794
+ TSLA,6/23/2025 16:00:00,348.68,0.08231934443
795
+ TSLA,6/24/2025 16:00:00,340.47,-0.02354594471
796
+ TSLA,6/25/2025 16:00:00,327.55,-0.0379475431
797
+ TSLA,6/26/2025 16:00:00,325.78,-0.005403755152
798
+ TSLA,6/27/2025 16:00:00,323.63,-0.006599545706
799
+ TSLA,6/30/2025 16:00:00,317.66,-0.01844699194
800
+ TSLA,7/1/2025 16:00:00,300.71,-0.05335893723
801
+ TSLA,7/2/2025 16:00:00,315.65,0.04968241828
802
+ TSLA,7/3/2025 13:05:00,315.35,-0.0009504197687
803
+ TSLA,7/7/2025 16:00:00,293.94,-0.0678928175
804
+ TSLA,7/8/2025 16:00:00,297.81,0.01316595224
805
+ TSLA,7/9/2025 16:00:00,295.88,-0.00648064202
806
+ TSLA,7/10/2025 16:00:00,309.87,0.04728268217
807
+ TSLA,7/11/2025 16:00:00,313.51,0.01174686159
808
+ TSLA,7/14/2025 16:00:00,316.9,0.01081305222
809
+ TSLA,7/15/2025 16:00:00,310.78,-0.01931208583
810
+ TSLA,7/16/2025 16:00:00,321.67,0.03504086492
811
+ TSLA,7/17/2025 16:00:00,319.41,-0.007025833929
812
+ TSLA,7/18/2025 16:00:00,329.65,0.03205910898
813
+ TSLA,7/21/2025 16:00:00,328.49,-0.003518883664
814
+ TSLA,7/22/2025 16:00:00,332.11,0.01102012238
815
+ TSLA,7/23/2025 16:00:00,332.56,0.00135497275
816
+ TSLA,7/24/2025 16:00:00,305.3,-0.0819701708
817
+ TSLA,7/25/2025 16:00:00,316.06,0.03524402227
818
+ TSLA,7/28/2025 16:00:00,325.59,0.03015250269
819
+ TSLA,7/29/2025 16:00:00,321.2,-0.01348321509
820
+ TSLA,7/30/2025 16:00:00,319.04,-0.006724782067
821
+ TSLA,7/31/2025 16:00:00,308.27,-0.03375752257
822
+ TSLA,8/1/2025 16:00:00,302.63,-0.01829564992
823
+ TSLA,8/4/2025 16:00:00,309.26,0.02190794039
824
+ TSLA,8/5/2025 16:00:00,308.72,-0.001746103602
825
+ TSLA,8/6/2025 16:00:00,319.91,0.0362464369
826
+ TSLA,8/7/2025 16:00:00,322.27,0.007377074802
827
+ TSLA,8/8/2025 16:00:00,329.65,0.02290005275
828
+ TSLA,8/11/2025 16:00:00,339.03,0.02845442136
829
+ TSLA,8/12/2025 16:00:00,340.84,0.005338760582
830
+ TSLA,8/13/2025 16:00:00,339.38,-0.004283534796
831
+ TSLA,8/14/2025 16:00:00,335.58,-0.01119688844
832
+ TSLA,8/15/2025 16:00:00,330.56,-0.01495917516
833
+ TSLA,8/18/2025 16:00:00,335.16,0.01391577928
834
+ TSLA,8/19/2025 16:00:00,329.31,-0.01745435016
835
+ TSLA,8/20/2025 16:00:00,323.9,-0.01642828945
836
+ TSLA,8/21/2025 16:00:00,320.11,-0.01170114233
837
+ TSLA,8/22/2025 16:00:00,340.01,0.06216613039
838
+ TSLA,8/25/2025 16:00:00,346.6,0.01938178289
839
+ TSLA,8/26/2025 16:00:00,351.67,0.01462781304
840
+ TSLA,8/27/2025 16:00:00,349.6,-0.005886200131
841
+ TSLA,8/28/2025 16:00:00,345.98,-0.01035469108
842
+ TSLA,8/29/2025 16:00:00,333.87,-0.03500202324
843
+ TSLA,9/2/2025 16:00:00,329.36,-0.01350825171
844
+ TSLA,9/3/2025 16:00:00,334.09,0.01436118533
845
+ TSLA,9/4/2025 16:00:00,338.53,0.01328983208
846
+ TSLA,9/5/2025 16:00:00,350.84,0.03636309928
847
+ TSLA,9/8/2025 16:00:00,346.4,-0.01265534147
848
+ TSLA,9/9/2025 16:00:00,346.97,0.001645496536
849
+ TSLA,9/10/2025 16:00:00,347.79,0.002363316713
850
+ TSLA,9/11/2025 16:00:00,368.81,0.06043877052
851
+ TSLA,9/12/2025 16:00:00,395.94,0.07356091212
852
+ TSLA,9/15/2025 16:00:00,410.04,0.03561145628
853
+ TSLA,9/16/2025 16:00:00,421.62,0.02824114721
854
+ TSLA,9/17/2025 16:00:00,425.86,0.01005644894
855
+ TSLA,9/18/2025 16:00:00,416.85,-0.02115718781
856
+ TSLA,9/19/2025 16:00:00,426.07,0.02211826796
857
+ TSLA,9/22/2025 16:00:00,434.21,0.01910484193
858
+ TSLA,9/23/2025 16:00:00,425.85,-0.01925335667
859
+ TSLA,9/24/2025 16:00:00,442.79,0.039779265
860
+ TSLA,9/25/2025 16:00:00,423.39,-0.04381309424
861
+ TSLA,9/26/2025 16:00:00,440.4,0.04017572451
862
+ TSLA,9/29/2025 16:00:00,443.21,0.006380563124
863
+ TSLA,9/30/2025 16:00:00,444.72,0.003406962839
864
+ TSLA,10/1/2025 16:00:00,459.46,0.03314445044
865
+ TSLA,10/2/2025 16:00:00,436,-0.05105993993
866
+ TSLA,10/3/2025 16:00:00,429.83,-0.01415137615
867
+ TSLA,10/6/2025 16:00:00,453.25,0.05448665752
868
+ TSLA,10/7/2025 16:00:00,433.09,-0.04447876448
869
+ TSLA,10/8/2025 16:00:00,438.69,0.01293033781
870
+ TSLA,10/9/2025 16:00:00,435.54,-0.007180469124
871
+ TSLA,10/10/2025 16:00:00,413.49,-0.0506268081
872
+ TSLA,10/13/2025 16:00:00,435.9,0.05419719945
873
+ TSLA,10/14/2025 16:00:00,429.24,-0.01527873365
874
+ TSLA,10/15/2025 16:00:00,435.15,0.01376852111
875
+ TSLA,10/16/2025 16:00:00,428.75,-0.0147075721
876
+ TSLA,10/17/2025 16:00:00,439.31,0.02462973761
877
+ TSLA,10/20/2025 16:00:00,447.43,0.01848353099
878
+ TSLA,10/21/2025 16:00:00,442.6,-0.01079498469
879
+ TSLA,10/22/2025 16:00:00,438.97,-0.008201536376
880
+ TSLA,10/23/2025 16:00:00,448.98,0.02280338064
881
+ TSLA,10/24/2025 16:00:00,433.72,-0.03398815092
882
+ TSLA,10/27/2025 16:00:00,452.42,0.04311537397
883
+ TSLA,10/28/2025 16:00:00,460.55,0.01797002785
884
+ TSLA,10/29/2025 16:00:00,461.51,0.002084464228
885
+ TSLA,10/30/2025 16:00:00,440.1,-0.04639119412
886
+ TSLA,10/31/2025 16:00:00,456.56,0.03740059077
887
+ TSLA,11/3/2025 16:00:00,468.37,0.02586735588
888
+ TSLA,11/4/2025 16:00:00,444.26,-0.05147639687
889
+ TSLA,11/5/2025 16:00:00,462.07,0.04008913699
890
+ TSLA,11/6/2025 16:00:00,445.91,-0.03497305603
891
+ TSLA,11/7/2025 16:00:00,429.52,-0.03675629611
892
+ TSLA,11/10/2025 16:00:00,445.23,0.03657571242
893
+ TSLA,11/11/2025 16:00:00,439.62,-0.0126002291
894
+ TSLA,11/12/2025 16:00:00,430.6,-0.02051771985
895
+ TSLA,11/13/2025 16:00:00,401.99,-0.06644217371
896
+ TSLA,11/14/2025 16:00:00,404.35,0.005870792806
897
+ TSLA,11/17/2025 16:00:00,408.92,0.01130208977
898
+ TSLA,11/18/2025 16:00:00,401.25,-0.01875672503
899
+ TSLA,11/19/2025 16:00:00,403.99,0.006828660436
900
+ TSLA,11/20/2025 16:00:00,395.23,-0.02168370504
901
+ TSLA,11/21/2025 16:00:00,391.09,-0.01047491334
902
+ TSLA,11/24/2025 16:00:00,417.78,0.06824516096
903
+ TSLA,11/25/2025 16:00:00,419.4,0.003877638949
904
+ TSLA,11/26/2025 16:00:00,426.58,0.0171196948
905
+ TSLA,11/28/2025 13:05:00,430.17,0.008415771954
906
+ TSLA,12/1/2025 16:00:00,430.14,-0.00006973987028
907
+ TSLA,12/2/2025 16:00:00,429.24,-0.002092342028
908
+ TSLA,12/3/2025 16:00:00,446.74,0.04076973255
909
+ TSLA,12/4/2025 16:00:00,454.53,0.01743743564
910
+ TSLA,12/5/2025 16:00:00,455,0.001034035157
911
+ TSLA,12/8/2025 16:00:00,439.58,-0.03389010989
912
+ TSLA,12/9/2025 16:00:00,445.17,0.01271668411
913
+ TSLA,12/10/2025 16:00:00,451.45,0.01410697037
914
+ TSLA,12/11/2025 16:00:00,446.89,-0.01010078636
915
+ TSLA,12/12/2025 16:00:00,458.96,0.02700888362
916
+ TSLA,12/15/2025 16:00:00,475.31,0.03562401952
917
+ TSLA,12/16/2025 16:00:00,489.88,0.03065367865
918
+ TSLA,12/17/2025 16:00:00,467.26,-0.04617457336
919
+ TSLA,12/18/2025 16:00:00,483.37,0.03447759277
920
+ TSLA,12/19/2025 16:00:00,481.2,-0.004489314604
921
+ TSLA,12/22/2025 16:00:00,488.73,0.01564837905
922
+ TSLA,12/23/2025 16:00:00,485.56,-0.006486198924
923
+ TSLA,12/24/2025 13:05:00,485.4,-0.0003295164346
924
+ TSLA,12/26/2025 16:00:00,475.19,-0.0210341986
925
+ TSLA,12/29/2025 16:00:00,459.64,-0.0327237526
926
+ TSLA,12/30/2025 16:00:00,454.43,-0.01133495779
927
+ TSLA,12/31/2025 16:00:00,449.72,-0.01036463262
928
+ TSLA,1/2/2026 16:00:00,438.07,-0.02590500756
929
+ TSLA,1/5/2026 16:00:00,451.67,0.03104526674
930
+ TSLA,1/6/2026 16:00:00,432.96,-0.04142404853
931
+ TSLA,1/7/2026 16:00:00,431.41,-0.003580007391
932
+ TSLA,1/8/2026 16:00:00,435.8,0.01017593473
933
+ TSLA,1/9/2026 16:00:00,445.01,0.0211335475
934
+ TSLA,1/12/2026 16:00:00,448.96,0.008876205029
935
+ TSLA,1/13/2026 16:00:00,447.2,-0.003920171062
936
+ TSLA,1/14/2026 16:00:00,439.2,-0.01788908766
937
+ TSLA,1/15/2026 16:00:00,438.57,-0.00143442623
938
+ TSLA,1/16/2026 16:00:00,437.5,-0.002439747361
939
+ TSLA,1/20/2026 16:00:00,419.25,-0.04171428571
940
+ TSLA,1/21/2026 16:00:00,431.44,0.02907573047
941
+ TSLA,1/22/2026 16:00:00,449.36,0.04153532357
942
+ TSLA,1/23/2026 16:00:00,449.06,-0.0006676161652
943
+ TSLA,1/26/2026 16:00:00,435.2,-0.03086447245
944
+ TSLA,1/27/2026 16:00:00,430.9,-0.009880514706
945
+ TSLA,1/28/2026 16:00:00,431.46,0.001299605477
946
+ TSLA,1/29/2026 16:00:00,416.56,-0.03453390813
947
+ TSLA,1/30/2026 16:00:00,430.41,0.03324851162
948
+ TSLA,2/2/2026 16:00:00,421.81,-0.0199809484
949
+ TSLA,2/3/2026 16:00:00,421.96,0.0003556103459
950
+ TSLA,2/4/2026 16:00:00,406.01,-0.03779979145
951
+ TSLA,2/5/2026 16:00:00,397.21,-0.021674343
952
+ TSLA,2/6/2026 16:00:00,411.11,0.03499408373
953
+ TSLA,2/9/2026 16:00:00,417.32,0.01510544623
954
+ TSLA,2/10/2026 16:00:00,425.21,0.01890635484
955
+ TSLA,2/11/2026 16:00:00,428.27,0.00719644411
956
+ TSLA,2/12/2026 16:00:00,417.07,-0.02615172671
957
+ TSLA,2/13/2026 16:00:00,417.44,0.0008871412473
958
+ TSLA,2/17/2026 16:00:00,410.63,-0.01631372173
959
+ TSLA,2/18/2026 16:00:00,411.32,0.001680344836
960
+ TSLA,2/19/2026 16:00:00,411.71,0.0009481668774
961
+ TSLA,2/20/2026 16:00:00,411.82,0.0002671783537
962
+ TSLA,2/23/2026 16:00:00,399.83,-0.02911466175
963
+ TSLA,2/24/2026 16:00:00,409.38,0.02388515119
964
+ TSLA,2/25/2026 16:00:00,417.4,0.01959060042
965
+ TSLA,2/26/2026 16:00:00,408.58,-0.02113080977
966
+ TSLA,2/27/2026 16:00:00,402.51,-0.01485633169
967
+ TSLA,3/2/2026 16:00:00,403.32,0.002012372363
968
+ TSLA,3/3/2026 16:00:00,392.43,-0.02700089259
969
+ TSLA,3/4/2026 16:00:00,405.94,0.03442652193
970
+ TSLA,3/5/2026 16:00:00,405.55,-0.0009607331133
971
+ TSLA,3/6/2026 16:00:00,396.73,-0.02174824313
972
+ TSLA,3/9/2026 16:00:00,398.68,0.00491518161
973
+ TSLA,3/10/2026 16:00:00,399.24,0.001404635296
974
+ TSLA,3/11/2026 16:00:00,407.82,0.02149083258
975
+ TSLA,3/12/2026 16:00:00,395.01,-0.03141091658
976
+ TSLA,3/13/2026 16:00:00,391.2,-0.009645325435
977
+ TSLA,3/16/2026 16:00:00,395.56,0.01114519427
978
+ TSLA,3/17/2026 16:00:00,399.27,0.0093791081
979
+ TSLA,3/18/2026 16:00:00,392.78,-0.01625466476
980
+ TSLA,3/19/2026 16:00:00,380.3,-0.03177351189
981
+ TSLA,3/20/2026 16:00:00,367.96,-0.03244806732
982
+ TSLA,3/23/2026 16:00:00,380.85,0.03503098163
983
+ TSLA,3/24/2026 16:00:00,383.03,0.005724038335
984
+ TSLA,3/25/2026 16:00:00,385.95,0.007623423753
985
+ TSLA,3/26/2026 16:00:00,372.11,-0.0358595673
986
+ TSLA,3/27/2026 16:00:00,361.83,-0.02762623955
987
+ TSLA,3/30/2026 16:00:00,355.28,-0.01810242379
988
+ TSLA,3/31/2026 16:00:00,371.75,0.0463578023
989
+ TSLA,4/1/2026 16:00:00,381.26,0.02558170814
990
+ TSLA,4/2/2026 16:00:00,360.59,-0.05421497141
991
+ TSLA,4/6/2026 16:00:00,352.82,-0.02154801853
992
+ TSLA,4/7/2026 16:00:00,346.65,-0.01748767077
993
+ TSLA,4/8/2026 16:00:00,343.25,-0.009808163854
994
+ TSLA,4/9/2026 16:00:00,345.62,0.006904588492
995
+ TSLA,4/10/2026 16:00:00,348.95,0.009634859094
996
+ TSLA,4/13/2026 16:00:00,352.42,0.009944118068
997
+ TSLA,4/14/2026 16:00:00,364.2,0.03342602576
998
+ TSLA,4/15/2026 16:00:00,391.95,0.07619439868
999
+ TSLA,4/16/2026 16:00:00,388.9,-0.007781604797
1000
+ TSLA,4/17/2026 16:00:00,400.62,0.03013628182
1001
+ TSLA,4/20/2026 16:00:00,392.5,-0.0202685837
1002
+ TSLA,4/21/2026 16:00:00,386.42,-0.01549044586
1003
+ TSLA,4/22/2026 16:00:00,387.51,0.002820764971
1004
+ TSLA,4/23/2026 16:00:00,373.72,-0.03558617842
1005
+ TSLA,4/24/2026 16:00:00,376.3,0.006903564166
1006
+ TSLA,4/27/2026 16:00:00,378.67,0.006298166357
1007
+ TSLA,4/28/2026 16:00:00,376.02,-0.006998177833
1008
+ TSLA,4/29/2026 16:00:00,372.8,-0.008563374289
1009
+ TSLA,4/30/2026 16:00:00,381.63,0.02368562232
1010
+ TSLA,5/1/2026 16:00:00,390.82,0.04833690987
1011
+ Stock,Date,Close,
1012
+ NVDA,5/2/2025 16:00:00,114.5,
1013
+ NVDA,5/5/2025 16:00:00,113.82,-0.005938864629
1014
+ NVDA,5/6/2025 16:00:00,113.54,-0.0024600246
1015
+ NVDA,5/7/2025 16:00:00,117.06,0.03100228994
1016
+ NVDA,5/8/2025 16:00:00,117.37,0.002648214591
1017
+ NVDA,5/9/2025 16:00:00,116.65,-0.006134446622
1018
+ NVDA,5/12/2025 16:00:00,123,0.05443634805
1019
+ NVDA,5/13/2025 16:00:00,129.93,0.05634146341
1020
+ NVDA,5/14/2025 16:00:00,135.34,0.04163780497
1021
+ NVDA,5/15/2025 16:00:00,134.83,-0.003768287276
1022
+ NVDA,5/16/2025 16:00:00,135.4,0.004227545798
1023
+ NVDA,5/19/2025 16:00:00,135.57,0.001255539143
1024
+ NVDA,5/20/2025 16:00:00,134.38,-0.00877775319
1025
+ NVDA,5/21/2025 16:00:00,131.8,-0.01919928561
1026
+ NVDA,5/22/2025 16:00:00,131.8,0
1027
+ NVDA,5/23/2025 16:00:00,131.29,-0.003869499241
1028
+ NVDA,5/27/2025 16:00:00,135.5,0.03206641785
1029
+ NVDA,5/28/2025 16:00:00,134.81,-0.005092250923
1030
+ NVDA,5/29/2025 16:00:00,139.19,0.03249017135
1031
+ NVDA,5/30/2025 16:00:00,135.13,-0.02916876212
1032
+ NVDA,6/2/2025 16:00:00,137.38,0.01665063272
1033
+ NVDA,6/3/2025 16:00:00,141.22,0.02795166691
1034
+ NVDA,6/4/2025 16:00:00,141.92,0.004956804985
1035
+ NVDA,6/5/2025 16:00:00,139.99,-0.01359921082
1036
+ NVDA,6/6/2025 16:00:00,141.72,0.01235802557
1037
+ NVDA,6/9/2025 16:00:00,142.63,0.006421112052
1038
+ NVDA,6/10/2025 16:00:00,143.96,0.009324826474
1039
+ NVDA,6/11/2025 16:00:00,142.83,-0.007849402612
1040
+ NVDA,6/12/2025 16:00:00,145,0.01519288665
1041
+ NVDA,6/13/2025 16:00:00,141.97,-0.02089655172
1042
+ NVDA,6/16/2025 16:00:00,144.69,0.01915897725
1043
+ NVDA,6/17/2025 16:00:00,144.12,-0.00393945677
1044
+ NVDA,6/18/2025 16:00:00,145.48,0.009436580627
1045
+ NVDA,6/20/2025 16:00:00,143.85,-0.01120428925
1046
+ NVDA,6/23/2025 16:00:00,144.17,0.002224539451
1047
+ NVDA,6/24/2025 16:00:00,147.9,0.02587223417
1048
+ NVDA,6/25/2025 16:00:00,154.31,0.04334009466
1049
+ NVDA,6/26/2025 16:00:00,155.02,0.0046011276
1050
+ NVDA,6/27/2025 16:00:00,157.75,0.01761063089
1051
+ NVDA,6/30/2025 16:00:00,157.99,0.001521394612
1052
+ NVDA,7/1/2025 16:00:00,153.3,-0.02968542313
1053
+ NVDA,7/2/2025 16:00:00,157.25,0.02576647097
1054
+ NVDA,7/3/2025 13:05:00,159.34,0.013290938
1055
+ NVDA,7/7/2025 16:00:00,158.24,-0.006903476842
1056
+ NVDA,7/8/2025 16:00:00,160,0.0111223458
1057
+ NVDA,7/9/2025 16:00:00,162.88,0.018
1058
+ NVDA,7/10/2025 16:00:00,164.1,0.007490176817
1059
+ NVDA,7/11/2025 16:00:00,164.92,0.004996953077
1060
+ NVDA,7/14/2025 16:00:00,164.07,-0.005154014067
1061
+ NVDA,7/15/2025 16:00:00,170.7,0.04040958128
1062
+ NVDA,7/16/2025 16:00:00,171.37,0.003925014646
1063
+ NVDA,7/17/2025 16:00:00,173,0.009511583124
1064
+ NVDA,7/18/2025 16:00:00,172.41,-0.003410404624
1065
+ NVDA,7/21/2025 16:00:00,171.38,-0.005974131431
1066
+ NVDA,7/22/2025 16:00:00,167.03,-0.02538219162
1067
+ NVDA,7/23/2025 16:00:00,170.78,0.0224510567
1068
+ NVDA,7/24/2025 16:00:00,173.74,0.01733224031
1069
+ NVDA,7/25/2025 16:00:00,173.5,-0.001381374468
1070
+ NVDA,7/28/2025 16:00:00,176.75,0.01873198847
1071
+ NVDA,7/29/2025 16:00:00,175.51,-0.007015558699
1072
+ NVDA,7/30/2025 16:00:00,179.27,0.02142328072
1073
+ NVDA,7/31/2025 16:00:00,177.87,-0.007809449434
1074
+ NVDA,8/1/2025 16:00:00,173.72,-0.02333164671
1075
+ NVDA,8/4/2025 16:00:00,180,0.03615012664
1076
+ NVDA,8/5/2025 16:00:00,178.26,-0.009666666667
1077
+ NVDA,8/6/2025 16:00:00,179.42,0.006507348816
1078
+ NVDA,8/7/2025 16:00:00,180.77,0.007524244789
1079
+ NVDA,8/8/2025 16:00:00,182.7,0.01067655031
1080
+ NVDA,8/11/2025 16:00:00,182.06,-0.0035030104
1081
+ NVDA,8/12/2025 16:00:00,183.16,0.006041964188
1082
+ NVDA,8/13/2025 16:00:00,181.59,-0.008571740555
1083
+ NVDA,8/14/2025 16:00:00,182.02,0.002367971805
1084
+ NVDA,8/15/2025 16:00:00,180.45,-0.008625425777
1085
+ NVDA,8/18/2025 16:00:00,182.01,0.008645054032
1086
+ NVDA,8/19/2025 16:00:00,175.64,-0.03499807703
1087
+ NVDA,8/20/2025 16:00:00,175.4,-0.001366431337
1088
+ NVDA,8/21/2025 16:00:00,174.98,-0.002394526796
1089
+ NVDA,8/22/2025 16:00:00,177.99,0.01720196594
1090
+ NVDA,8/25/2025 16:00:00,179.81,0.01022529356
1091
+ NVDA,8/26/2025 16:00:00,181.77,0.01090039486
1092
+ NVDA,8/27/2025 16:00:00,181.6,-0.0009352478407
1093
+ NVDA,8/28/2025 16:00:00,180.17,-0.007874449339
1094
+ NVDA,8/29/2025 16:00:00,174.18,-0.03324637842
1095
+ NVDA,9/2/2025 16:00:00,170.78,-0.01952003674
1096
+ NVDA,9/3/2025 16:00:00,170.62,-0.0009368778545
1097
+ NVDA,9/4/2025 16:00:00,171.66,0.006095416716
1098
+ NVDA,9/5/2025 16:00:00,167.02,-0.02703017593
1099
+ NVDA,9/8/2025 16:00:00,168.31,0.007723625913
1100
+ NVDA,9/9/2025 16:00:00,170.76,0.01455647317
1101
+ NVDA,9/10/2025 16:00:00,177.33,0.03847505271
1102
+ NVDA,9/11/2025 16:00:00,177.17,-0.0009022725991
1103
+ NVDA,9/12/2025 16:00:00,177.82,0.003668792685
1104
+ NVDA,9/15/2025 16:00:00,177.75,-0.0003936565066
1105
+ NVDA,9/16/2025 16:00:00,174.88,-0.01614627286
1106
+ NVDA,9/17/2025 16:00:00,170.29,-0.02624656908
1107
+ NVDA,9/18/2025 16:00:00,176.24,0.0349403958
1108
+ NVDA,9/19/2025 16:00:00,176.67,0.002439854744
1109
+ NVDA,9/22/2025 16:00:00,183.61,0.03928227769
1110
+ NVDA,9/23/2025 16:00:00,178.43,-0.02821197103
1111
+ NVDA,9/24/2025 16:00:00,176.97,-0.008182480525
1112
+ NVDA,9/25/2025 16:00:00,177.69,0.004068486184
1113
+ NVDA,9/26/2025 16:00:00,178.19,0.002813889358
1114
+ NVDA,9/29/2025 16:00:00,181.85,0.02053987317
1115
+ NVDA,9/30/2025 16:00:00,186.58,0.02601044817
1116
+ NVDA,10/1/2025 16:00:00,187.24,0.00353735663
1117
+ NVDA,10/2/2025 16:00:00,188.89,0.008812219611
1118
+ NVDA,10/3/2025 16:00:00,187.62,-0.006723489862
1119
+ NVDA,10/6/2025 16:00:00,185.54,-0.01108623814
1120
+ NVDA,10/7/2025 16:00:00,185.04,-0.002694836693
1121
+ NVDA,10/8/2025 16:00:00,189.11,0.02199524427
1122
+ NVDA,10/9/2025 16:00:00,192.57,0.01829622971
1123
+ NVDA,10/10/2025 16:00:00,183.16,-0.04886534767
1124
+ NVDA,10/13/2025 16:00:00,188.32,0.02817208998
1125
+ NVDA,10/14/2025 16:00:00,180.03,-0.04402081563
1126
+ NVDA,10/15/2025 16:00:00,179.83,-0.001110925957
1127
+ NVDA,10/16/2025 16:00:00,181.81,0.01101039871
1128
+ NVDA,10/17/2025 16:00:00,183.22,0.007755348991
1129
+ NVDA,10/20/2025 16:00:00,182.64,-0.003165593276
1130
+ NVDA,10/21/2025 16:00:00,181.16,-0.008103372755
1131
+ NVDA,10/22/2025 16:00:00,180.28,-0.004857584456
1132
+ NVDA,10/23/2025 16:00:00,182.16,0.01042822276
1133
+ NVDA,10/24/2025 16:00:00,186.26,0.02250768555
1134
+ NVDA,10/27/2025 16:00:00,191.49,0.02807902931
1135
+ NVDA,10/28/2025 16:00:00,201.03,0.04981983393
1136
+ NVDA,10/29/2025 16:00:00,207.04,0.02989603542
1137
+ NVDA,10/30/2025 16:00:00,202.89,-0.02004443586
1138
+ NVDA,10/31/2025 16:00:00,202.49,-0.001971511657
1139
+ NVDA,11/3/2025 16:00:00,206.88,0.02168008297
1140
+ NVDA,11/4/2025 16:00:00,198.69,-0.03958816705
1141
+ NVDA,11/5/2025 16:00:00,195.21,-0.01751472143
1142
+ NVDA,11/6/2025 16:00:00,188.08,-0.0365247682
1143
+ NVDA,11/7/2025 16:00:00,188.15,0.0003721820502
1144
+ NVDA,11/10/2025 16:00:00,199.05,0.05793250066
1145
+ NVDA,11/11/2025 16:00:00,193.16,-0.02959055514
1146
+ NVDA,11/12/2025 16:00:00,193.8,0.003313315386
1147
+ NVDA,11/13/2025 16:00:00,186.86,-0.03581011352
1148
+ NVDA,11/14/2025 16:00:00,190.17,0.01771379643
1149
+ NVDA,11/17/2025 16:00:00,186.6,-0.01877267708
1150
+ NVDA,11/18/2025 16:00:00,181.36,-0.02808145766
1151
+ NVDA,11/19/2025 16:00:00,186.52,0.02845169828
1152
+ NVDA,11/20/2025 16:00:00,180.64,-0.03152476946
1153
+ NVDA,11/21/2025 16:00:00,178.88,-0.009743135518
1154
+ NVDA,11/24/2025 16:00:00,182.55,0.02051654741
1155
+ NVDA,11/25/2025 16:00:00,177.82,-0.02591070939
1156
+ NVDA,11/26/2025 16:00:00,180.26,0.01372174109
1157
+ NVDA,11/28/2025 13:05:00,177,-0.01808498835
1158
+ NVDA,12/1/2025 16:00:00,179.92,0.01649717514
1159
+ NVDA,12/2/2025 16:00:00,181.46,0.008559359715
1160
+ NVDA,12/3/2025 16:00:00,179.59,-0.01030530144
1161
+ NVDA,12/4/2025 16:00:00,183.38,0.02110362492
1162
+ NVDA,12/5/2025 16:00:00,182.41,-0.005289562657
1163
+ NVDA,12/8/2025 16:00:00,185.55,0.01721396853
1164
+ NVDA,12/9/2025 16:00:00,184.97,-0.003125842091
1165
+ NVDA,12/10/2025 16:00:00,183.78,-0.006433475699
1166
+ NVDA,12/11/2025 16:00:00,180.93,-0.01550767222
1167
+ NVDA,12/12/2025 16:00:00,175.02,-0.03266456641
1168
+ NVDA,12/15/2025 16:00:00,176.29,0.007256313564
1169
+ NVDA,12/16/2025 16:00:00,177.72,0.008111634239
1170
+ NVDA,12/17/2025 16:00:00,170.94,-0.03814989872
1171
+ NVDA,12/18/2025 16:00:00,174.14,0.01872001872
1172
+ NVDA,12/19/2025 16:00:00,180.99,0.0393361663
1173
+ NVDA,12/22/2025 16:00:00,183.69,0.01491795127
1174
+ NVDA,12/23/2025 16:00:00,189.21,0.03005062878
1175
+ NVDA,12/24/2025 13:05:00,188.61,-0.003171079753
1176
+ NVDA,12/26/2025 16:00:00,190.53,0.01017973596
1177
+ NVDA,12/29/2025 16:00:00,188.22,-0.01212407495
1178
+ NVDA,12/30/2025 16:00:00,187.54,-0.003612793539
1179
+ NVDA,12/31/2025 16:00:00,186.5,-0.00554548363
1180
+ NVDA,1/2/2026 16:00:00,188.85,0.01260053619
1181
+ NVDA,1/5/2026 16:00:00,188.12,-0.003865501721
1182
+ NVDA,1/6/2026 16:00:00,187.24,-0.004677865192
1183
+ NVDA,1/7/2026 16:00:00,189.11,0.009987182226
1184
+ NVDA,1/8/2026 16:00:00,185.04,-0.02152186558
1185
+ NVDA,1/9/2026 16:00:00,184.86,-0.0009727626459
1186
+ NVDA,1/12/2026 16:00:00,184.94,0.0004327599264
1187
+ NVDA,1/13/2026 16:00:00,185.81,0.004704228398
1188
+ NVDA,1/14/2026 16:00:00,183.14,-0.01436951725
1189
+ NVDA,1/15/2026 16:00:00,187.05,0.02134978705
1190
+ NVDA,1/16/2026 16:00:00,186.23,-0.004383854584
1191
+ NVDA,1/20/2026 16:00:00,178.07,-0.0438167857
1192
+ NVDA,1/21/2026 16:00:00,183.32,0.02948278767
1193
+ NVDA,1/22/2026 16:00:00,184.84,0.00829151211
1194
+ NVDA,1/23/2026 16:00:00,187.67,0.01531053884
1195
+ NVDA,1/26/2026 16:00:00,186.47,-0.00639420259
1196
+ NVDA,1/27/2026 16:00:00,188.52,0.01099372553
1197
+ NVDA,1/28/2026 16:00:00,191.52,0.01591343094
1198
+ NVDA,1/29/2026 16:00:00,192.51,0.005169172932
1199
+ NVDA,1/30/2026 16:00:00,191.13,-0.007168458781
1200
+ NVDA,2/2/2026 16:00:00,185.61,-0.02888086643
1201
+ NVDA,2/3/2026 16:00:00,180.34,-0.02839286676
1202
+ NVDA,2/4/2026 16:00:00,174.19,-0.0341022513
1203
+ NVDA,2/5/2026 16:00:00,171.88,-0.01326138125
1204
+ NVDA,2/6/2026 16:00:00,185.41,0.07871771003
1205
+ NVDA,2/9/2026 16:00:00,190.04,0.02497168438
1206
+ NVDA,2/10/2026 16:00:00,188.54,-0.007893075142
1207
+ NVDA,2/11/2026 16:00:00,190.05,0.008008910576
1208
+ NVDA,2/12/2026 16:00:00,186.94,-0.01636411471
1209
+ NVDA,2/13/2026 16:00:00,182.81,-0.02209265005
1210
+ NVDA,2/17/2026 16:00:00,184.97,0.0118155462
1211
+ NVDA,2/18/2026 16:00:00,187.98,0.01627290912
1212
+ NVDA,2/19/2026 16:00:00,187.9,-0.0004255771891
1213
+ NVDA,2/20/2026 16:00:00,189.82,0.01021820117
1214
+ NVDA,2/23/2026 16:00:00,191.55,0.009113897376
1215
+ NVDA,2/24/2026 16:00:00,192.85,0.006786739755
1216
+ NVDA,2/25/2026 16:00:00,195.56,0.01405237231
1217
+ NVDA,2/26/2026 16:00:00,184.89,-0.05456125997
1218
+ NVDA,2/27/2026 16:00:00,177.19,-0.04164638434
1219
+ NVDA,3/2/2026 16:00:00,182.48,0.02985495795
1220
+ NVDA,3/3/2026 16:00:00,180.05,-0.01331652784
1221
+ NVDA,3/4/2026 16:00:00,183.04,0.01660649819
1222
+ NVDA,3/5/2026 16:00:00,183.34,0.001638986014
1223
+ NVDA,3/6/2026 16:00:00,177.82,-0.03010799607
1224
+ NVDA,3/9/2026 16:00:00,182.65,0.02716229895
1225
+ NVDA,3/10/2026 16:00:00,184.77,0.01160689844
1226
+ NVDA,3/11/2026 16:00:00,186.03,0.006819288846
1227
+ NVDA,3/12/2026 16:00:00,183.14,-0.01553512874
1228
+ NVDA,3/13/2026 16:00:00,180.25,-0.01578027738
1229
+ NVDA,3/16/2026 16:00:00,183.22,0.01647711512
1230
+ NVDA,3/17/2026 16:00:00,181.93,-0.007040716079
1231
+ NVDA,3/18/2026 16:00:00,180.4,-0.008409827956
1232
+ NVDA,3/19/2026 16:00:00,178.56,-0.01019955654
1233
+ NVDA,3/20/2026 16:00:00,172.7,-0.03281810036
1234
+ NVDA,3/23/2026 16:00:00,175.64,0.01702374059
1235
+ NVDA,3/24/2026 16:00:00,175.2,-0.002505124118
1236
+ NVDA,3/25/2026 16:00:00,178.68,0.0198630137
1237
+ NVDA,3/26/2026 16:00:00,171.24,-0.04163868368
1238
+ NVDA,3/27/2026 16:00:00,167.52,-0.02172389629
1239
+ NVDA,3/30/2026 16:00:00,165.17,-0.01402817574
1240
+ NVDA,3/31/2026 16:00:00,174.4,0.05588181873
1241
+ NVDA,4/1/2026 16:00:00,175.75,0.007740825688
1242
+ NVDA,4/2/2026 16:00:00,177.39,0.0093314367
1243
+ NVDA,4/6/2026 16:00:00,177.64,0.001409324088
1244
+ NVDA,4/7/2026 16:00:00,178.1,0.002589506868
1245
+ NVDA,4/8/2026 16:00:00,182.08,0.02234699607
1246
+ NVDA,4/9/2026 16:00:00,183.91,0.01005052724
1247
+ NVDA,4/10/2026 16:00:00,188.63,0.02566472731
1248
+ NVDA,4/13/2026 16:00:00,189.31,0.00360494089
1249
+ NVDA,4/14/2026 16:00:00,196.51,0.03803285616
1250
+ NVDA,4/15/2026 16:00:00,198.87,0.01200956694
1251
+ NVDA,4/16/2026 16:00:00,198.35,-0.00261477347
1252
+ NVDA,4/17/2026 16:00:00,201.68,0.01678850517
1253
+ NVDA,4/20/2026 16:00:00,202.06,0.001884172947
1254
+ NVDA,4/21/2026 16:00:00,199.88,-0.01078887459
1255
+ NVDA,4/22/2026 16:00:00,202.5,0.01310786472
1256
+ NVDA,4/23/2026 16:00:00,199.64,-0.01412345679
1257
+ NVDA,4/24/2026 16:00:00,208.27,0.04322781006
1258
+ NVDA,4/27/2026 16:00:00,216.61,0.04004417343
1259
+ NVDA,4/28/2026 16:00:00,213.17,-0.01588107659
1260
+ NVDA,4/29/2026 16:00:00,209.25,-0.01838907914
1261
+ NVDA,4/30/2026 16:00:00,199.57,-0.046260454
1262
+ NVDA,5/1/2026 16:00:00,198.45,-0.005612065942
debug.js ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import puppeteer from 'puppeteer';
2
+
3
+ (async () => {
4
+ const browser = await puppeteer.launch();
5
+ const page = await browser.newPage();
6
+
7
+ page.on('console', msg => console.log('BROWSER LOG:', msg.text()));
8
+ page.on('pageerror', error => console.log('BROWSER ERROR:', error.message));
9
+ page.on('requestfailed', request => console.log('REQUEST FAILED:', request.url(), request.failure().errorText));
10
+
11
+ try {
12
+ await page.goto('http://localhost:5174/');
13
+ // Wait for landing page and click the first option
14
+ await page.waitForSelector('button');
15
+ const buttons = await page.$$('button');
16
+ await buttons[0].click(); // Select General wealth building
17
+ await new Promise(r => setTimeout(r, 500));
18
+
19
+ const buttons2 = await page.$$('button');
20
+ await buttons2[0].click(); // Select I'd see it as a buying opportunity
21
+ await new Promise(r => setTimeout(r, 500));
22
+
23
+ const buttons3 = await page.$$('button');
24
+ await buttons3[0].click(); // Select Cautious
25
+ await new Promise(r => setTimeout(r, 2000));
26
+
27
+ // Now on dashboard, click a stock to open popup
28
+ const qqqBtn = await page.$x("//span[contains(text(), 'QQQ')]");
29
+ if (qqqBtn.length > 0) {
30
+ await qqqBtn[0].click();
31
+ console.log("Clicked QQQ, waiting for crash...");
32
+ } else {
33
+ console.log("QQQ button not found, trying SPY");
34
+ const spyBtn = await page.$x("//span[contains(text(), 'SPY')]");
35
+ if (spyBtn.length > 0) {
36
+ await spyBtn[0].click();
37
+ console.log("Clicked SPY, waiting for crash...");
38
+ }
39
+ }
40
+
41
+ await new Promise(r => setTimeout(r, 5000));
42
+ console.log("Done waiting.");
43
+ } catch (e) {
44
+ console.log("Script error:", e);
45
+ } finally {
46
+ await browser.close();
47
+ }
48
+ })();
eslint.config.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import { defineConfig, globalIgnores } from 'eslint/config'
6
+
7
+ export default defineConfig([
8
+ globalIgnores(['dist']),
9
+ {
10
+ files: ['**/*.{js,jsx}'],
11
+ extends: [
12
+ js.configs.recommended,
13
+ reactHooks.configs.flat.recommended,
14
+ reactRefresh.configs.vite,
15
+ ],
16
+ languageOptions: {
17
+ globals: globals.browser,
18
+ parserOptions: { ecmaFeatures: { jsx: true } },
19
+ },
20
+ },
21
+ ])
index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>gsp</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
nginx.conf ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ server {
2
+ listen 7860;
3
+ server_name localhost;
4
+
5
+ location / {
6
+ root /usr/share/nginx/html;
7
+ index index.html index.htm;
8
+ try_files $uri $uri/ /index.html;
9
+ }
10
+
11
+ # Error handling
12
+ error_page 500 502 503 504 /50x.html;
13
+ location = /50x.html {
14
+ root /usr/share/nginx/html;
15
+ }
16
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "gsp",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@langchain/core": "^1.1.43",
14
+ "@langchain/google-genai": "^2.1.30",
15
+ "@langchain/openai": "^1.4.5",
16
+ "autoprefixer": "^10.5.0",
17
+ "clsx": "^2.1.1",
18
+ "framer-motion": "^12.38.0",
19
+ "lucide-react": "^1.14.0",
20
+ "postcss": "^8.5.13",
21
+ "puppeteer": "^24.42.0",
22
+ "react": "^19.2.5",
23
+ "react-dom": "^19.2.5",
24
+ "react-router-dom": "^7.14.2",
25
+ "recharts": "^3.8.1",
26
+ "tailwind-merge": "^3.5.0",
27
+ "tailwindcss": "^3.4.19"
28
+ },
29
+ "devDependencies": {
30
+ "@eslint/js": "^10.0.1",
31
+ "@types/react": "^19.2.14",
32
+ "@types/react-dom": "^19.2.3",
33
+ "@vitejs/plugin-react": "^4.7.0",
34
+ "eslint": "^10.2.1",
35
+ "eslint-plugin-react-hooks": "^7.1.1",
36
+ "eslint-plugin-react-refresh": "^0.5.2",
37
+ "globals": "^17.5.0",
38
+ "vite": "^5.4.21"
39
+ }
40
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
public/favicon.svg ADDED

Git LFS Details

  • SHA256: a55daf30d9739cb34e800e7a3c0fd753cde64045df8d80f7bb55caf43b5eb1bc
  • Pointer size: 129 Bytes
  • Size of remote file: 9.71 kB
public/icons.svg ADDED

Git LFS Details

  • SHA256: 3ebf567a60bb49551ec54d3f0c5fd398d5b62b8f313d6793b1d6e22f3016cf29
  • Pointer size: 129 Bytes
  • Size of remote file: 5.21 kB
public/port-satellite.png ADDED

Git LFS Details

  • SHA256: 55c060f0a5c82fadfcc4e95b8cad023ccef0049916b2ce1f5928d7e2e679e743
  • Pointer size: 132 Bytes
  • Size of remote file: 1.12 MB
src/App.css ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .counter {
2
+ font-size: 16px;
3
+ padding: 5px 10px;
4
+ border-radius: 5px;
5
+ color: var(--accent);
6
+ background: var(--accent-bg);
7
+ border: 2px solid transparent;
8
+ transition: border-color 0.3s;
9
+ margin-bottom: 24px;
10
+
11
+ &:hover {
12
+ border-color: var(--accent-border);
13
+ }
14
+ &:focus-visible {
15
+ outline: 2px solid var(--accent);
16
+ outline-offset: 2px;
17
+ }
18
+ }
19
+
20
+ .hero {
21
+ position: relative;
22
+
23
+ .base,
24
+ .framework,
25
+ .vite {
26
+ inset-inline: 0;
27
+ margin: 0 auto;
28
+ }
29
+
30
+ .base {
31
+ width: 170px;
32
+ position: relative;
33
+ z-index: 0;
34
+ }
35
+
36
+ .framework,
37
+ .vite {
38
+ position: absolute;
39
+ }
40
+
41
+ .framework {
42
+ z-index: 1;
43
+ top: 34px;
44
+ height: 28px;
45
+ transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
46
+ scale(1.4);
47
+ }
48
+
49
+ .vite {
50
+ z-index: 0;
51
+ top: 107px;
52
+ height: 26px;
53
+ width: auto;
54
+ transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
55
+ scale(0.8);
56
+ }
57
+ }
58
+
59
+ #center {
60
+ display: flex;
61
+ flex-direction: column;
62
+ gap: 25px;
63
+ place-content: center;
64
+ place-items: center;
65
+ flex-grow: 1;
66
+
67
+ @media (max-width: 1024px) {
68
+ padding: 32px 20px 24px;
69
+ gap: 18px;
70
+ }
71
+ }
72
+
73
+ #next-steps {
74
+ display: flex;
75
+ border-top: 1px solid var(--border);
76
+ text-align: left;
77
+
78
+ & > div {
79
+ flex: 1 1 0;
80
+ padding: 32px;
81
+ @media (max-width: 1024px) {
82
+ padding: 24px 20px;
83
+ }
84
+ }
85
+
86
+ .icon {
87
+ margin-bottom: 16px;
88
+ width: 22px;
89
+ height: 22px;
90
+ }
91
+
92
+ @media (max-width: 1024px) {
93
+ flex-direction: column;
94
+ text-align: center;
95
+ }
96
+ }
97
+
98
+ #docs {
99
+ border-right: 1px solid var(--border);
100
+
101
+ @media (max-width: 1024px) {
102
+ border-right: none;
103
+ border-bottom: 1px solid var(--border);
104
+ }
105
+ }
106
+
107
+ #next-steps ul {
108
+ list-style: none;
109
+ padding: 0;
110
+ display: flex;
111
+ gap: 8px;
112
+ margin: 32px 0 0;
113
+
114
+ .logo {
115
+ height: 18px;
116
+ }
117
+
118
+ a {
119
+ color: var(--text-h);
120
+ font-size: 16px;
121
+ border-radius: 6px;
122
+ background: var(--social-bg);
123
+ display: flex;
124
+ padding: 6px 12px;
125
+ align-items: center;
126
+ gap: 8px;
127
+ text-decoration: none;
128
+ transition: box-shadow 0.3s;
129
+
130
+ &:hover {
131
+ box-shadow: var(--shadow);
132
+ }
133
+ .button-icon {
134
+ height: 18px;
135
+ width: 18px;
136
+ }
137
+ }
138
+
139
+ @media (max-width: 1024px) {
140
+ margin-top: 20px;
141
+ flex-wrap: wrap;
142
+ justify-content: center;
143
+
144
+ li {
145
+ flex: 1 1 calc(50% - 8px);
146
+ }
147
+
148
+ a {
149
+ width: 100%;
150
+ justify-content: center;
151
+ box-sizing: border-box;
152
+ }
153
+ }
154
+ }
155
+
156
+ #spacer {
157
+ height: 88px;
158
+ border-top: 1px solid var(--border);
159
+ @media (max-width: 1024px) {
160
+ height: 48px;
161
+ }
162
+ }
163
+
164
+ .ticks {
165
+ position: relative;
166
+ width: 100%;
167
+
168
+ &::before,
169
+ &::after {
170
+ content: '';
171
+ position: absolute;
172
+ top: -4.5px;
173
+ border: 5px solid transparent;
174
+ }
175
+
176
+ &::before {
177
+ left: 0;
178
+ border-left-color: var(--border);
179
+ }
180
+ &::after {
181
+ right: 0;
182
+ border-right-color: var(--border);
183
+ }
184
+ }
src/App.jsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
3
+ import LandingPage from './pages/LandingPage';
4
+ import Dashboard from './pages/Dashboard';
5
+
6
+ function App() {
7
+ const [riskProfile, setRiskProfile] = useState(() => {
8
+ return localStorage.getItem('gs_risk_profile') || null;
9
+ });
10
+
11
+ const handleSetRiskProfile = (profile) => {
12
+ setRiskProfile(profile);
13
+ localStorage.setItem('gs_risk_profile', profile);
14
+ };
15
+
16
+ return (
17
+ <Router>
18
+ <div className="min-h-screen bg-gs-light text-gs-navy font-sans">
19
+ <Routes>
20
+ <Route
21
+ path="/"
22
+ element={<LandingPage setRiskProfile={handleSetRiskProfile} />}
23
+ />
24
+ <Route
25
+ path="/dashboard"
26
+ element={riskProfile ? <Dashboard riskProfile={riskProfile} /> : <Navigate to="/" />}
27
+ />
28
+ </Routes>
29
+ </div>
30
+ </Router>
31
+ );
32
+ }
33
+
34
+ export default App;
src/assets/hero.png ADDED

Git LFS Details

  • SHA256: 881ffbcaafc212e49addad08846a5b82761355fa20624253af3477ba33262c5c
  • Pointer size: 130 Bytes
  • Size of remote file: 13.1 kB
src/assets/react.svg ADDED

Git LFS Details

  • SHA256: fdb05910725d74cfafd1092601f1577de7710c3b6e2822cc76768856da75bbd2
  • Pointer size: 129 Bytes
  • Size of remote file: 4.31 kB
src/assets/vite.svg ADDED

Git LFS Details

  • SHA256: c2432a583b7681b0446fb28e6dee72b5844c711f237f89a2ab72831f504e8331
  • Pointer size: 129 Bytes
  • Size of remote file: 8.89 kB
src/components/FeeTransparencyModule.jsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Search } from 'lucide-react';
3
+
4
+ export default function FeeTransparencyModule({ portfolio }) {
5
+ const { hiddenFees } = portfolio;
6
+
7
+ // Convert percentages to an illustrative dollar amount based on a $10,000 investment over 1 year
8
+ const illustrativeInvestment = 10000;
9
+ const expenseRatioCost = (illustrativeInvestment * (hiddenFees.expenseRatio / 100)).toFixed(2);
10
+ const advisoryCost = (illustrativeInvestment * (hiddenFees.advisoryFee / 100)).toFixed(2);
11
+ const totalCost = (parseFloat(expenseRatioCost) + parseFloat(advisoryCost) + hiddenFees.tradingCosts).toFixed(2);
12
+
13
+ return (
14
+ <div className="bg-gs-navy text-white rounded-2xl p-8 shadow-lg relative overflow-hidden">
15
+ {/* Background Accent */}
16
+ <div className="absolute -right-10 -top-10 w-40 h-40 bg-white/5 rounded-full blur-2xl"></div>
17
+
18
+ <div className="relative z-10">
19
+ <header className="mb-6 flex justify-between items-end border-b border-white/10 pb-4">
20
+ <div>
21
+ <h2 className="text-2xl font-light mb-1 flex items-center">
22
+ <Search className="mr-2 text-gs-gold" size={24} /> Radical Transparency
23
+ </h2>
24
+ <p className="text-white/60 text-sm font-light">What you actually pay per $10,000 invested yearly</p>
25
+ </div>
26
+ <div className="text-right">
27
+ <span className="text-xs uppercase tracking-widest text-gs-gold font-semibold block mb-1">Fee Rating</span>
28
+ <span className="text-lg font-medium">{portfolio.feeImpact}</span>
29
+ </div>
30
+ </header>
31
+
32
+ <div className="space-y-4 font-light text-sm">
33
+ <div className="flex justify-between items-center bg-white/5 p-3 rounded-lg">
34
+ <span className="text-white/80">Fund Expense Ratios ({hiddenFees.expenseRatio}%)</span>
35
+ <span className="font-medium text-white">${expenseRatioCost}</span>
36
+ </div>
37
+ <div className="flex justify-between items-center bg-white/5 p-3 rounded-lg">
38
+ <span className="text-white/80">Platform/Advisory Fee ({hiddenFees.advisoryFee}%)</span>
39
+ <span className="font-medium text-white">${advisoryCost}</span>
40
+ </div>
41
+ <div className="flex justify-between items-center bg-white/5 p-3 rounded-lg">
42
+ <span className="text-white/80">Estimated Trading Costs</span>
43
+ <span className="font-medium text-white">${hiddenFees.tradingCosts.toFixed(2)}</span>
44
+ </div>
45
+
46
+ <div className="flex justify-between items-center pt-4 border-t border-white/10">
47
+ <span className="font-medium">Total Yearly Cost</span>
48
+ <span className="text-xl font-medium text-gs-gold">${totalCost}</span>
49
+ </div>
50
+ </div>
51
+
52
+ <p className="text-xs text-white/40 mt-6 italic">
53
+ *Many traditional brokerages hide these numbers in dense prospectuses. We show them to you upfront so there are no surprises.
54
+ </p>
55
+ </div>
56
+ </div>
57
+ );
58
+ }
src/components/FinancialCalculators.jsx ADDED
@@ -0,0 +1,503 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useMemo } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { TrendingUp, Wallet, Landmark, ArrowRight, X, Info, Calculator, Percent, ShieldCheck } from 'lucide-react';
4
+ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from 'recharts';
5
+
6
+ const CALCULATORS = [
7
+ {
8
+ id: 'compound',
9
+ tag: 'INVESTING',
10
+ title: 'Compound Interest Calculator',
11
+ description: 'Watch your money grow year by year with a live animated chart. Adjust return rate, contributions, and timeline.',
12
+ icon: <TrendingUp className="text-blue-500" />,
13
+ color: 'border-blue-400',
14
+ features: [
15
+ 'Year-by-year growth chart',
16
+ 'Contribution vs. gain breakdown',
17
+ 'Rule of 72 insight built in',
18
+ 'Compare fee drag scenarios'
19
+ ]
20
+ },
21
+ {
22
+ id: 'retirement',
23
+ tag: 'RETIREMENT',
24
+ title: 'Retirement Savings Calculator',
25
+ description: 'Find your magic number — how much you need to retire, based on your spending, Social Security, and withdrawal rate.',
26
+ icon: <Landmark className="text-amber-500" />,
27
+ color: 'border-amber-400',
28
+ features: [
29
+ '4% rule & safe withdrawal modeling',
30
+ 'Social Security income offset',
31
+ 'On-track vs. gap analysis',
32
+ 'Inflation-adjusted projections'
33
+ ]
34
+ },
35
+ {
36
+ id: 'tax',
37
+ tag: 'TAX STRATEGY',
38
+ title: 'Roth vs. Traditional IRA',
39
+ description: 'Side-by-side after-tax comparison at retirement. Select your current and expected future tax brackets.',
40
+ icon: <Wallet className="text-purple-500" />,
41
+ color: 'border-purple-400',
42
+ features: [
43
+ 'Current vs. retirement bracket selector',
44
+ 'After-tax value comparison',
45
+ 'Break-even tax rate shown',
46
+ 'RMD impact explained'
47
+ ]
48
+ }
49
+ ];
50
+
51
+ export default function FinancialCalculators() {
52
+ const [activeCalc, setActiveCalc] = useState(null);
53
+
54
+ return (
55
+ <div className="mt-12 mb-16">
56
+ <div className="flex items-center justify-between mb-8">
57
+ <div>
58
+ <h2 className="text-2xl font-light text-gs-navy">Institutional <span className="font-semibold">Planning Tools</span></h2>
59
+ <p className="text-gs-slate text-sm">Advanced modeling used by our private wealth advisors.</p>
60
+ </div>
61
+ </div>
62
+
63
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
64
+ {CALCULATORS.map((calc) => (
65
+ <CalculatorCard
66
+ key={calc.id}
67
+ calc={calc}
68
+ onClick={() => setActiveCalc(calc)}
69
+ />
70
+ ))}
71
+ </div>
72
+
73
+ <AnimatePresence>
74
+ {activeCalc && (
75
+ <CalculatorModal
76
+ calc={activeCalc}
77
+ onClose={() => setActiveCalc(null)}
78
+ />
79
+ )}
80
+ </AnimatePresence>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ function CalculatorCard({ calc, onClick }) {
86
+ return (
87
+ <motion.div
88
+ whileHover={{ y: -5 }}
89
+ className={`bg-white rounded-xl shadow-sm border-t-4 ${calc.color} p-6 flex flex-col h-full border-x border-b border-gray-100 hover:shadow-md transition-shadow cursor-pointer group`}
90
+ onClick={onClick}
91
+ >
92
+ <div className="flex justify-between items-start mb-6">
93
+ <div className="p-3 bg-gray-50 rounded-xl group-hover:bg-gs-light transition-colors">
94
+ {calc.icon}
95
+ </div>
96
+ <span className="text-[10px] font-bold tracking-widest text-gs-slate/60 bg-gray-100 px-2 py-1 rounded">
97
+ {calc.tag}
98
+ </span>
99
+ </div>
100
+
101
+ <h3 className="text-xl font-semibold text-gs-navy mb-3 group-hover:text-gs-gold transition-colors">
102
+ {calc.title}
103
+ </h3>
104
+
105
+ <p className="text-gs-slate text-sm font-light leading-relaxed mb-6 flex-grow">
106
+ {calc.description}
107
+ </p>
108
+
109
+ <ul className="space-y-2 mb-8">
110
+ {calc.features.map((feature, i) => (
111
+ <li key={i} className="flex items-center text-xs text-gs-slate/80">
112
+ <ShieldCheck size={14} className="text-gs-gold mr-2 flex-shrink-0" />
113
+ {feature}
114
+ </li>
115
+ ))}
116
+ </ul>
117
+
118
+ <div className="flex items-center text-gs-navy font-semibold text-sm group-hover:translate-x-1 transition-transform">
119
+ Open Calculator <ArrowRight size={16} className="ml-2" />
120
+ </div>
121
+ </motion.div>
122
+ );
123
+ }
124
+
125
+ function CalculatorModal({ calc, onClose }) {
126
+ const [inputs, setInputs] = useState({
127
+ salary: 120000,
128
+ contribution: 15, // percent
129
+ has401k: true,
130
+ employerMatch: 4,
131
+ age: 30,
132
+ retirementAge: 65,
133
+ initialSavings: 50000,
134
+ annualReturn: 7,
135
+ taxBracket: 24,
136
+ futureTaxBracket: 20
137
+ });
138
+
139
+ const handleInputChange = (e) => {
140
+ const { name, value, type, checked } = e.target;
141
+ setInputs(prev => ({
142
+ ...prev,
143
+ [name]: type === 'checkbox' ? checked : parseFloat(value)
144
+ }));
145
+ };
146
+
147
+ const renderCalculatorContent = () => {
148
+ switch (calc.id) {
149
+ case 'compound':
150
+ return <CompoundCalc inputs={inputs} onChange={handleInputChange} />;
151
+ case 'retirement':
152
+ return <RetirementCalc inputs={inputs} onChange={handleInputChange} />;
153
+ case 'tax':
154
+ return <TaxCalc inputs={inputs} onChange={handleInputChange} />;
155
+ default:
156
+ return null;
157
+ }
158
+ };
159
+
160
+ return (
161
+ <div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-gs-navy/40 backdrop-blur-sm">
162
+ <motion.div
163
+ initial={{ opacity: 0, scale: 0.95, y: 20 }}
164
+ animate={{ opacity: 1, scale: 1, y: 0 }}
165
+ exit={{ opacity: 0, scale: 0.95, y: 20 }}
166
+ className="bg-white rounded-2xl shadow-2xl w-full max-w-5xl max-h-[90vh] overflow-hidden flex flex-col md:flex-row"
167
+ >
168
+ {/* Sidebar / Inputs */}
169
+ <div className="w-full md:w-80 bg-gs-light p-8 border-r border-gray-200 overflow-y-auto">
170
+ <div className="flex items-center mb-8">
171
+ <div className="p-2 bg-white rounded-lg shadow-sm mr-3">
172
+ {calc.icon}
173
+ </div>
174
+ <h3 className="font-bold text-gs-navy uppercase tracking-wider text-sm">{calc.tag}</h3>
175
+ </div>
176
+
177
+ <div className="space-y-6">
178
+ <div className="grid grid-cols-2 gap-4">
179
+ <InputGroup
180
+ label="Current Age"
181
+ name="age"
182
+ value={inputs.age}
183
+ onChange={handleInputChange}
184
+ />
185
+ <InputGroup
186
+ label="Retire Age"
187
+ name="retirementAge"
188
+ value={inputs.retirementAge}
189
+ onChange={handleInputChange}
190
+ />
191
+ </div>
192
+ <InputGroup
193
+ label="Annual Salary"
194
+ name="salary"
195
+ value={inputs.salary}
196
+ onChange={handleInputChange}
197
+ prefix="$"
198
+ />
199
+ <InputGroup
200
+ label="401k Contribution (%)"
201
+ name="contribution"
202
+ value={inputs.contribution}
203
+ onChange={handleInputChange}
204
+ suffix="%"
205
+ type="range"
206
+ min="0"
207
+ max="50"
208
+ />
209
+ <InputGroup
210
+ label="Employer Match (%)"
211
+ name="employerMatch"
212
+ value={inputs.employerMatch}
213
+ onChange={handleInputChange}
214
+ suffix="%"
215
+ />
216
+ <div className="grid grid-cols-2 gap-4">
217
+ <InputGroup
218
+ label="Current Tax"
219
+ name="taxBracket"
220
+ value={inputs.taxBracket}
221
+ onChange={handleInputChange}
222
+ suffix="%"
223
+ />
224
+ <InputGroup
225
+ label="Retire Tax"
226
+ name="futureTaxBracket"
227
+ value={inputs.futureTaxBracket}
228
+ onChange={handleInputChange}
229
+ suffix="%"
230
+ />
231
+ </div>
232
+ <InputGroup
233
+ label="Initial Savings"
234
+ name="initialSavings"
235
+ value={inputs.initialSavings}
236
+ onChange={handleInputChange}
237
+ prefix="$"
238
+ />
239
+ <InputGroup
240
+ label="Expected Return"
241
+ name="annualReturn"
242
+ value={inputs.annualReturn}
243
+ onChange={handleInputChange}
244
+ suffix="%"
245
+ />
246
+ </div>
247
+
248
+ <div className="mt-12 p-4 bg-gs-gold/10 rounded-xl border border-gs-gold/20">
249
+ <h4 className="text-xs font-bold text-gs-gold mb-2 flex items-center">
250
+ <Info size={14} className="mr-1" /> GS ADVICE
251
+ </h4>
252
+ <InvestmentAdvice inputs={inputs} />
253
+ </div>
254
+ </div>
255
+
256
+ {/* Main Content / Results */}
257
+ <div className="flex-grow flex flex-col h-full bg-white relative">
258
+ <button
259
+ onClick={onClose}
260
+ className="absolute top-6 right-6 p-2 hover:bg-gray-100 rounded-full transition-colors z-10"
261
+ >
262
+ <X size={20} className="text-gs-slate" />
263
+ </button>
264
+
265
+ <div className="p-8 md:p-12 overflow-y-auto">
266
+ <div className="mb-10">
267
+ <h2 className="text-3xl font-light text-gs-navy mb-2">{calc.title}</h2>
268
+ <p className="text-gs-slate font-light">Interactive projection based on institutional modeling.</p>
269
+ </div>
270
+
271
+ {renderCalculatorContent()}
272
+ </div>
273
+ </div>
274
+ </motion.div>
275
+ </div>
276
+ );
277
+ }
278
+
279
+ function InputGroup({ label, name, value, onChange, prefix, suffix, type = "number", min, max }) {
280
+ return (
281
+ <div className="space-y-2">
282
+ <div className="flex justify-between items-center">
283
+ <label className="text-xs font-semibold text-gs-slate uppercase tracking-tighter">{label}</label>
284
+ {type === "range" && <span className="text-sm font-bold text-gs-navy">{value}{suffix}</span>}
285
+ </div>
286
+ <div className="relative">
287
+ {prefix && <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm">{prefix}</span>}
288
+ <input
289
+ type={type}
290
+ name={name}
291
+ value={value}
292
+ onChange={onChange}
293
+ min={min}
294
+ max={max}
295
+ className={`w-full bg-white border border-gray-200 rounded-lg p-2 text-sm focus:ring-2 focus:ring-gs-gold focus:border-transparent outline-none transition-all ${prefix ? 'pl-7' : ''} ${suffix && type !== 'range' ? 'pr-7' : ''}`}
296
+ />
297
+ {suffix && type !== "range" && <span className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm">{suffix}</span>}
298
+ </div>
299
+ </div>
300
+ );
301
+ }
302
+
303
+ function InvestmentAdvice({ inputs }) {
304
+ const { salary, contribution, employerMatch } = inputs;
305
+ const totalContribution = contribution + employerMatch;
306
+
307
+ let advice = "";
308
+ if (totalContribution < 15) {
309
+ advice = `Increase your total savings to at least 15% ($${(salary * 0.15 / 12).toLocaleString()} /mo) to remain on track for institutional-grade retirement.`;
310
+ } else if (contribution > employerMatch + 10) {
311
+ advice = "You are over-contributing to 401k. Consider diversifying into a brokerage account for liquidity or high-yield GS instruments.";
312
+ } else {
313
+ advice = "Excellent contribution level. Ensure your portfolio allocation matches your risk profile in the main dashboard.";
314
+ }
315
+
316
+ return <p className="text-xs text-gs-navy leading-relaxed">{advice}</p>;
317
+ }
318
+
319
+ // Calculator Logic Components
320
+ function CompoundCalc({ inputs }) {
321
+ const data = useMemo(() => {
322
+ let current = inputs.initialSavings;
323
+ let totalContributed = inputs.initialSavings;
324
+ const yearlyReturn = inputs.annualReturn / 100;
325
+ const yearlyContribution = (inputs.salary * (inputs.contribution / 100));
326
+
327
+ const results = [];
328
+ for (let i = 0; i <= 30; i++) {
329
+ results.push({
330
+ year: `Year ${i}`,
331
+ balance: Math.round(current),
332
+ contributions: Math.round(totalContributed)
333
+ });
334
+ current = (current + yearlyContribution) * (1 + yearlyReturn);
335
+ totalContributed += yearlyContribution;
336
+ }
337
+ return results;
338
+ }, [inputs]);
339
+
340
+ const finalBalance = data[data.length - 1].balance;
341
+
342
+ return (
343
+ <div className="space-y-8">
344
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
345
+ <div className="p-6 bg-gs-navy text-white rounded-2xl">
346
+ <p className="text-xs font-bold text-gs-gold uppercase tracking-widest mb-1">Estimated Value (30 Years)</p>
347
+ <p className="text-4xl font-light">${finalBalance.toLocaleString()}</p>
348
+ </div>
349
+ <div className="p-6 bg-gs-light rounded-2xl border border-gray-100">
350
+ <p className="text-xs font-bold text-gs-slate uppercase tracking-widest mb-1">Total Interest Earned</p>
351
+ <p className="text-4xl font-light text-gs-navy">${(finalBalance - data[data.length-1].contributions).toLocaleString()}</p>
352
+ </div>
353
+ </div>
354
+
355
+ <div className="h-[350px] w-full">
356
+ <ResponsiveContainer width="100%" height="100%">
357
+ <AreaChart data={data}>
358
+ <defs>
359
+ <linearGradient id="colorBalance" x1="0" y1="0" x2="0" y2="1">
360
+ <stop offset="5%" stopColor="#C5A880" stopOpacity={0.3}/>
361
+ <stop offset="95%" stopColor="#C5A880" stopOpacity={0}/>
362
+ </linearGradient>
363
+ </defs>
364
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#eee" />
365
+ <XAxis dataKey="year" hide />
366
+ <YAxis
367
+ tickFormatter={(value) => `$${value / 1000}k`}
368
+ axisLine={false}
369
+ tickLine={false}
370
+ tick={{fontSize: 12, fill: '#666'}}
371
+ />
372
+ <Tooltip
373
+ formatter={(value) => [`$${value.toLocaleString()}`, '']}
374
+ contentStyle={{ borderRadius: '12px', border: 'none', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)' }}
375
+ />
376
+ <Area type="monotone" dataKey="balance" stroke="#C5A880" strokeWidth={3} fillOpacity={1} fill="url(#colorBalance)" />
377
+ <Area type="monotone" dataKey="contributions" stroke="#0B233F" strokeWidth={2} fillOpacity={0.1} fill="#0B233F" />
378
+ </AreaChart>
379
+ </ResponsiveContainer>
380
+ </div>
381
+ </div>
382
+ );
383
+ }
384
+
385
+ function RetirementCalc({ inputs }) {
386
+ const { salary, contribution, employerMatch, initialSavings, annualReturn, age, retirementAge } = inputs;
387
+
388
+ const yearsToRetirement = retirementAge - age;
389
+ const annualContribution = salary * ((contribution + employerMatch) / 100);
390
+ const rate = annualReturn / 100;
391
+
392
+ // FV of current savings + FV of annual contributions
393
+ const fvInitial = initialSavings * Math.pow(1 + rate, yearsToRetirement);
394
+ const fvContributions = annualContribution * (Math.pow(1 + rate, yearsToRetirement) - 1) / rate;
395
+ const projectedTotal = fvInitial + fvContributions;
396
+
397
+ // Retirement Need: 80% of salary * 25 (4% rule)
398
+ const annualNeed = salary * 0.8;
399
+ const totalNeed = annualNeed * 25;
400
+ const gap = totalNeed - projectedTotal;
401
+
402
+ return (
403
+ <div className="space-y-6">
404
+ <div className="p-8 bg-gs-light rounded-2xl border border-dashed border-gs-gold/50 flex flex-col items-center justify-center text-center">
405
+ <Landmark size={48} className="text-gs-gold mb-4" />
406
+ <h4 className="text-xl font-medium text-gs-navy mb-2">Retirement Readiness Analysis</h4>
407
+ <p className="text-gs-slate max-w-md">
408
+ To maintain your lifestyle, you'll need approximately <span className="font-bold text-gs-navy">${(totalNeed / 1000000).toFixed(1)}M</span>.
409
+ Your current path projects a fund of <span className="font-bold text-gs-navy">${(projectedTotal / 1000000).toFixed(1)}M</span> in {yearsToRetirement} years.
410
+ </p>
411
+ </div>
412
+
413
+ <div className="grid grid-cols-3 gap-4">
414
+ <div className="p-4 bg-white border border-gray-100 rounded-xl shadow-sm text-center">
415
+ <p className="text-[10px] font-bold text-gs-slate uppercase tracking-tighter">Current Plan</p>
416
+ <p className="text-lg font-semibold text-gs-navy">${(projectedTotal / 1000000).toFixed(2)}M</p>
417
+ </div>
418
+ <div className="p-4 bg-white border border-gray-100 rounded-xl shadow-sm text-center">
419
+ <p className="text-[10px] font-bold text-gs-slate uppercase tracking-tighter">Projected {gap > 0 ? 'Gap' : 'Surplus'}</p>
420
+ <p className={`text-lg font-semibold ${gap > 0 ? 'text-red-500' : 'text-green-600'}`}>
421
+ ${Math.abs(gap / 1000000).toFixed(2)}M
422
+ </p>
423
+ </div>
424
+ <div className="p-4 bg-white border border-gray-100 rounded-xl shadow-sm text-center">
425
+ <p className="text-[10px] font-bold text-gs-slate uppercase tracking-tighter">Savings Rate</p>
426
+ <p className="text-lg font-semibold text-gs-gold">{contribution + employerMatch}%</p>
427
+ </div>
428
+ </div>
429
+
430
+ <div className="mt-4 p-4 bg-gs-navy text-white rounded-xl text-xs font-light">
431
+ <p><span className="text-gs-gold font-bold">PROJECTION BASIS:</span> Assumes {annualReturn}% annual return, inflation-adjusted spending, and adherence to the 4% safe withdrawal rule. Modeling includes GS institutional market assumptions.</p>
432
+ </div>
433
+ </div>
434
+ );
435
+ }
436
+
437
+ function TaxCalc({ inputs }) {
438
+ const { salary, contribution, initialSavings, annualReturn, age, retirementAge, taxBracket, futureTaxBracket } = inputs;
439
+
440
+ const years = retirementAge - age;
441
+ const rate = annualReturn / 100;
442
+ const annualContribution = salary * (contribution / 100);
443
+
444
+ // Traditional IRA: Invest pre-tax, pay tax at withdrawal
445
+ const tradFV_Initial = initialSavings * Math.pow(1 + rate, years);
446
+ const tradFV_Cont = annualContribution * (Math.pow(1 + rate, years) - 1) / rate;
447
+ const tradNet = (tradFV_Initial + tradFV_Cont) * (1 - (futureTaxBracket / 100));
448
+
449
+ // Roth IRA: Invest after-tax, tax-free withdrawal
450
+ const rothInitial = initialSavings * (1 - (taxBracket / 100));
451
+ const rothAnnual = annualContribution * (1 - (taxBracket / 100));
452
+ const rothFV_Initial = rothInitial * Math.pow(1 + rate, years);
453
+ const rothFV_Cont = rothAnnual * (Math.pow(1 + rate, years) - 1) / rate;
454
+ const rothNet = rothFV_Initial + rothFV_Cont;
455
+
456
+ const winner = rothNet > tradNet ? "Roth" : "Traditional";
457
+ const diffPercent = Math.abs(((rothNet - tradNet) / Math.min(rothNet, tradNet)) * 100).toFixed(1);
458
+
459
+ return (
460
+ <div className="space-y-8">
461
+ <div className="flex flex-col md:flex-row gap-4">
462
+ <div className={`flex-1 p-6 border rounded-2xl transition-all ${winner === 'Roth' ? 'border-purple-200 bg-purple-50/30' : 'border-gray-100 bg-gray-50/50 opacity-80'}`}>
463
+ <h4 className="text-purple-700 font-bold text-xs uppercase mb-4 flex justify-between">
464
+ Roth Strategy {winner === 'Roth' && <span className="bg-purple-100 text-[8px] px-2 py-0.5 rounded-full text-purple-700">OPTIMAL</span>}
465
+ </h4>
466
+ <p className="text-gs-navy text-sm font-light leading-relaxed">Pay taxes now at <span className="font-bold">{taxBracket}%</span>. All future growth and withdrawals are <span className="text-purple-700 font-bold underline">100% Tax-Free</span>.</p>
467
+ <div className="mt-6 pt-6 border-t border-purple-100">
468
+ <span className="text-3xl font-semibold text-purple-700">${(rothNet / 1000).toFixed(0)}k</span>
469
+ <p className="text-xs text-purple-600 mt-1 uppercase tracking-wider font-bold">Estimated Net Wealth</p>
470
+ </div>
471
+ </div>
472
+
473
+ <div className={`flex-1 p-6 border rounded-2xl transition-all ${winner === 'Traditional' ? 'border-gs-navy/20 bg-gs-light' : 'border-gray-100 bg-gray-50/50 opacity-80'}`}>
474
+ <h4 className="text-gs-navy font-bold text-xs uppercase mb-4 flex justify-between">
475
+ Traditional {winner === 'Traditional' && <span className="bg-gs-navy text-white text-[8px] px-2 py-0.5 rounded-full">OPTIMAL</span>}
476
+ </h4>
477
+ <p className="text-gs-navy text-sm font-light leading-relaxed">Get a tax deduction now. Entire balance is taxed at <span className="font-bold">{futureTaxBracket}%</span> during retirement.</p>
478
+ <div className="mt-6 pt-6 border-t border-gray-200">
479
+ <span className="text-3xl font-semibold text-gs-navy">${(tradNet / 1000).toFixed(0)}k</span>
480
+ <p className="text-xs text-gs-slate mt-1 uppercase tracking-wider font-bold">Estimated Net Wealth</p>
481
+ </div>
482
+ </div>
483
+ </div>
484
+
485
+ <div className="bg-gs-navy text-white p-6 rounded-2xl flex flex-col md:flex-row justify-between items-center gap-4">
486
+ <div>
487
+ <h4 className="text-gs-gold font-bold text-[10px] uppercase tracking-widest mb-1">Strategic Selection</h4>
488
+ <p className="text-lg font-light">The <span className="font-bold text-gs-gold">{winner} Strategy</span> is projected to provide <span className="font-bold">{diffPercent}% more</span> spendable wealth.</p>
489
+ </div>
490
+ <div className="flex items-center gap-3">
491
+ <div className="text-right hidden md:block">
492
+ <p className="text-[10px] text-gray-400 font-bold uppercase">Difference</p>
493
+ <p className="text-gs-gold font-bold">${Math.abs((rothNet - tradNet) / 1000).toFixed(0)}k</p>
494
+ </div>
495
+ <div className="p-3 bg-gs-gold/20 rounded-full border border-gs-gold/30">
496
+ <Percent className="text-gs-gold" size={24} />
497
+ </div>
498
+ </div>
499
+ </div>
500
+ </div>
501
+ );
502
+ }
503
+
src/components/Indicators.jsx ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Activity, ShieldAlert, Info, X, PieChart, DollarSign, Target } from 'lucide-react';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+
5
+ export default function Indicators({ portfolio }) {
6
+ const [activeInfo, setActiveInfo] = useState(null); // 'health' or 'risk'
7
+
8
+ // A simple gauge visualization using SVG
9
+ const dashArray = 283; // 2 * pi * r (r=45)
10
+ const dashOffset = dashArray - (dashArray * portfolio.healthScore) / 100;
11
+
12
+ return (
13
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6 relative">
14
+ {/* Portfolio Health Score */}
15
+ <div
16
+ onClick={() => setActiveInfo('health')}
17
+ className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 flex items-center justify-between cursor-pointer hover:border-gs-gold transition-all group"
18
+ >
19
+ <div>
20
+ <h3 className="text-sm text-gs-slate uppercase tracking-wider mb-1 font-medium flex items-center">
21
+ <Activity size={16} className="mr-2 text-gs-gold" /> Health Score
22
+ </h3>
23
+ <p className="text-3xl font-light text-gs-navy">
24
+ {portfolio.healthScore} <span className="text-base text-gray-400">/ 100</span>
25
+ </p>
26
+ <p className="text-sm text-gray-500 mt-2 font-light flex items-center group-hover:text-gs-gold transition-colors">
27
+ <Info size={14} className="mr-1" /> Click to see how this is calculated
28
+ </p>
29
+ </div>
30
+ <div className="relative w-24 h-24">
31
+ <svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
32
+ <circle cx="50" cy="50" r="45" fill="none" stroke="#f3f4f6" strokeWidth="8" />
33
+ <circle
34
+ cx="50" cy="50" r="45" fill="none"
35
+ stroke="#0B233F" strokeWidth="8"
36
+ strokeDasharray={dashArray} strokeDashoffset={dashOffset}
37
+ strokeLinecap="round"
38
+ className="transition-all duration-1000 ease-out"
39
+ />
40
+ </svg>
41
+ </div>
42
+ </div>
43
+
44
+ {/* Risk Meter */}
45
+ <div
46
+ onClick={() => setActiveInfo('risk')}
47
+ className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 flex flex-col justify-center cursor-pointer hover:border-gs-gold transition-all group"
48
+ >
49
+ <h3 className="text-sm text-gs-slate uppercase tracking-wider mb-4 font-medium flex items-center">
50
+ <ShieldAlert size={16} className="mr-2 text-gs-gold" /> Risk Level
51
+ </h3>
52
+ <div className="flex w-full h-3 bg-gray-100 rounded-full overflow-hidden mb-3">
53
+ <div className={`h-full ${portfolio.riskLevel === 'Low' ? 'bg-gs-navy w-1/3' : portfolio.riskLevel === 'Medium' ? 'bg-gs-gold w-2/3' : 'bg-red-500 w-full'} transition-all duration-500`}></div>
54
+ </div>
55
+ <div className="flex justify-between text-xs text-gray-400 font-medium uppercase mb-2">
56
+ <span className={portfolio.riskLevel === 'Low' ? 'text-gs-navy font-bold' : ''}>Low</span>
57
+ <span className={portfolio.riskLevel === 'Medium' ? 'text-gs-gold font-bold' : ''}>Medium</span>
58
+ <span className={portfolio.riskLevel === 'High' ? 'text-red-500 font-bold' : ''}>High</span>
59
+ </div>
60
+ <p className="text-xs text-gray-400 font-light flex items-center group-hover:text-gs-gold transition-colors">
61
+ <Info size={12} className="mr-1" /> How we measure risk
62
+ </p>
63
+ </div>
64
+
65
+ {/* Calculation Modal */}
66
+ <AnimatePresence>
67
+ {activeInfo && (
68
+ <div className="fixed inset-0 z-[60] flex items-center justify-center p-4">
69
+ <motion.div
70
+ initial={{ opacity: 0 }}
71
+ animate={{ opacity: 1 }}
72
+ exit={{ opacity: 0 }}
73
+ onClick={() => setActiveInfo(null)}
74
+ className="absolute inset-0 bg-gs-navy/40 backdrop-blur-sm"
75
+ />
76
+ <motion.div
77
+ initial={{ opacity: 0, scale: 0.9, y: 20 }}
78
+ animate={{ opacity: 1, scale: 1, y: 0 }}
79
+ exit={{ opacity: 0, scale: 0.9, y: 20 }}
80
+ className="relative bg-white rounded-3xl shadow-2xl w-full max-w-md overflow-hidden"
81
+ >
82
+ <div className="p-8">
83
+ <div className="flex justify-between items-start mb-6">
84
+ <div className="flex items-center">
85
+ <div className="p-3 bg-gs-light rounded-xl mr-4 text-gs-gold">
86
+ {activeInfo === 'health' ? <Activity size={24} /> : <ShieldAlert size={24} />}
87
+ </div>
88
+ <div>
89
+ <h4 className="text-xl font-semibold text-gs-navy">
90
+ {activeInfo === 'health' ? 'Health Score Logic' : 'Risk Calculation'}
91
+ </h4>
92
+ <p className="text-sm text-gs-slate font-light">Radical Transparency Report</p>
93
+ </div>
94
+ </div>
95
+ <button onClick={() => setActiveInfo(null)} className="text-gray-400 hover:text-gs-navy transition-colors">
96
+ <X size={20} />
97
+ </button>
98
+ </div>
99
+
100
+ <div className="space-y-6">
101
+ {activeInfo === 'health' ? (
102
+ <>
103
+ <div className="flex gap-4">
104
+ <div className="mt-1 text-gs-gold"><DollarSign size={18} /></div>
105
+ <div>
106
+ <p className="font-medium text-gs-navy text-sm">Fee Efficiency</p>
107
+ <p className="text-xs text-gs-slate font-light leading-relaxed">We subtract points for high expense ratios. Every 0.1% in fees costs your portfolio health (Max -20 pts).</p>
108
+ </div>
109
+ </div>
110
+ <div className="flex gap-4">
111
+ <div className="mt-1 text-gs-gold"><PieChart size={18} /></div>
112
+ <div>
113
+ <p className="font-medium text-gs-navy text-sm">Diversification Bonus</p>
114
+ <p className="text-xs text-gs-slate font-light leading-relaxed">Holding 5+ different asset classes grants a +5 point bonus for reduced concentration risk.</p>
115
+ </div>
116
+ </div>
117
+ <div className="flex gap-4">
118
+ <div className="mt-1 text-gs-gold"><Target size={18} /></div>
119
+ <div>
120
+ <p className="font-medium text-gs-navy text-sm">Profile Alignment</p>
121
+ <p className="text-xs text-gs-slate font-light leading-relaxed">If your portfolio's actual volatility doesn't match your goal (e.g. Cautious vs Balanced), we subtract 15 points.</p>
122
+ </div>
123
+ </div>
124
+ </>
125
+ ) : (
126
+ <div className="bg-gs-light p-5 rounded-2xl border border-gs-gold/20">
127
+ <p className="text-sm text-gs-navy font-medium mb-2">Market Sensitivity Logic</p>
128
+ <p className="text-xs text-gs-slate font-light leading-relaxed mb-4">
129
+ Risk isn't a guess. We measure how much your portfolio typically swings compared to the broader market to categorize your risk level.
130
+ </p>
131
+
132
+ <div className="bg-white/80 p-3 rounded-xl border border-gs-gold/10 mb-4 flex justify-between items-center shadow-sm">
133
+ <span className="text-[10px] uppercase tracking-widest text-gs-slate font-bold">Portfolio Beta</span>
134
+ <span className="text-xl font-bold text-gs-navy">{portfolio.avgBeta}</span>
135
+ </div>
136
+
137
+ <div className="space-y-2">
138
+ <div className="flex justify-between text-[10px] uppercase tracking-wider text-gs-slate font-bold">
139
+ <span>Low Risk</span>
140
+ <span className="text-gs-navy">Minimal Volatility</span>
141
+ </div>
142
+ <div className="flex justify-between text-[10px] uppercase tracking-wider text-gs-slate font-bold">
143
+ <span>Medium Risk</span>
144
+ <span className="text-gs-gold">Market Standard</span>
145
+ </div>
146
+ <div className="flex justify-between text-[10px] uppercase tracking-wider text-gs-slate font-bold">
147
+ <span>High Risk</span>
148
+ <span className="text-red-500">Aggressive Growth</span>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ )}
153
+ </div>
154
+
155
+ <button
156
+ onClick={() => setActiveInfo(null)}
157
+ className="w-full mt-8 bg-gs-navy text-white py-4 rounded-xl font-medium hover:bg-gs-navy/90 transition-colors shadow-md"
158
+ >
159
+ Got it, thanks!
160
+ </button>
161
+ </div>
162
+ </motion.div>
163
+ </div>
164
+ )}
165
+ </AnimatePresence>
166
+ </div>
167
+ );
168
+ }
src/components/InvestmentCommittee.jsx ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { Users, User, BarChart2, AlertCircle, PlayCircle, Loader2 } from 'lucide-react';
4
+ import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
5
+ import { HumanMessage, SystemMessage } from '@langchain/core/messages';
6
+
7
+ const agents = {
8
+ macro: { name: 'Macro Economist', role: 'Analyzes broader economic trends, rates, and inflation', icon: <BarChart2 size={16} />, color: 'text-blue-500', bg: 'bg-blue-500/10' },
9
+ tech: { name: 'Technical Analyst', role: 'Focuses on momentum, moving averages, and charts', icon: <User size={16} />, color: 'text-purple-500', bg: 'bg-purple-500/10' },
10
+ skeptic: { name: 'The Skeptic', role: 'Looks for flaws, overvaluation, and risks', icon: <AlertCircle size={16} />, color: 'text-red-500', bg: 'bg-red-500/10' },
11
+ system: { name: 'System', role: 'Moderator', icon: <Users size={16} />, color: 'text-gs-gold', bg: 'bg-gs-gold/10' }
12
+ };
13
+
14
+ const mockDebates = {
15
+ 'AAPL': [
16
+ { agent: 'macro', text: 'iPhone demand remains steady globally, but services growth is the real story for Apple.' },
17
+ { agent: 'tech', text: 'AAPL is consolidating near its 200-day average. A breakout above current levels would be very bullish.' },
18
+ { agent: 'skeptic', text: 'Regulatory pressure in the EU and US is a massive dark cloud that could hurt margins.' },
19
+ { agent: 'system', text: 'Consensus: Strong ecosystem but legal risks. Conviction Score: 72/100 (Hold).' }
20
+ ],
21
+ 'TSLA': [
22
+ { agent: 'macro', text: 'High interest rates are cooling the EV market, forcing aggressive price cuts.' },
23
+ { agent: 'tech', text: 'Tesla is in a clear downtrend. We need to see a higher low before turning bullish.' },
24
+ { agent: 'skeptic', text: 'Elon is distracted and competition from China is fierce. Valuation is still disconnected from reality.' },
25
+ { agent: 'system', text: 'Consensus: High volatility and macro headwinds. Conviction Score: 35/100 (Underweight).' }
26
+ ],
27
+ 'NVDA': [
28
+ { agent: 'macro', text: 'The AI infrastructure build-out is a once-in-a-generation shift that favors NVIDIA.' },
29
+ { agent: 'tech', text: 'Parabolic move. It is overextended, but momentum like this can last longer than expected.' },
30
+ { agent: 'skeptic', text: 'At some point, the hyperscalers will stop buying at this rate. The drop will be as fast as the rise.' },
31
+ { agent: 'system', text: 'Consensus: Unrivaled leader in a booming sector. Conviction Score: 88/100 (Overweight).' }
32
+ ],
33
+ 'MSFT': [
34
+ { agent: 'macro', text: 'Enterprise software and cloud (Azure) are the backbone of the modern economy.' },
35
+ { agent: 'tech', text: 'Steady uptrend. Microsoft is a "safe haven" in the tech world right now.' },
36
+ { agent: 'skeptic', text: 'The Activision deal is done, but integrating it perfectly won\'t be easy or cheap.' },
37
+ { agent: 'system', text: 'Consensus: Solid growth and AI tailwinds. Conviction Score: 82/100 (Overweight).' }
38
+ ],
39
+ 'default': [
40
+ { agent: 'macro', text: 'The macro outlook for [TICKER] is heavily dependent on current sector trends and inflationary pressures affecting production costs.' },
41
+ { agent: 'tech', text: '[TICKER] is currently testing a key resistance level. We need to see sustained volume before confirming a new upward trajectory.' },
42
+ { agent: 'skeptic', text: 'A primary concern for [TICKER] is the potential for margin compression if competitors maintain aggressive pricing strategies.' },
43
+ { agent: 'system', text: 'Consensus: Position is stable for [TICKER], but suggests a cautious stance until quarterly data confirms growth. Conviction Score: 62/100 (Neutral).' }
44
+ ]
45
+ };
46
+
47
+ export default function InvestmentCommittee({ ticker, isDebating, setIsDebating }) {
48
+ const [messages, setMessages] = useState([]);
49
+ const [convictionScore, setConvictionScore] = useState(null);
50
+ const [loading, setLoading] = useState(false);
51
+ const scrollRef = useRef(null);
52
+
53
+ useEffect(() => {
54
+ setMessages([]);
55
+ setConvictionScore(null);
56
+ setIsDebating(false);
57
+ }, [ticker]);
58
+
59
+ useEffect(() => {
60
+ if (scrollRef.current) {
61
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
62
+ }
63
+ }, [messages]);
64
+
65
+ const runDebate = async () => {
66
+ if (!ticker) return;
67
+ setIsDebating(true);
68
+ setMessages([]);
69
+ setConvictionScore(null);
70
+ setLoading(true);
71
+
72
+ const apiKey = import.meta.env.VITE_GEMINI_API_KEY;
73
+
74
+ if (apiKey && apiKey.length > 10) {
75
+ try {
76
+ const llm = new ChatGoogleGenerativeAI({
77
+ apiKey: apiKey,
78
+ modelName: 'gemini-1.5-flash',
79
+ maxOutputTokens: 2048,
80
+ });
81
+
82
+ // Agent 1: Macro
83
+ setMessages([{ agent: 'macro', text: `Quantifying macro factors for ${ticker}...` }]);
84
+ const macroResponse = await llm.invoke([
85
+ new SystemMessage(`You are a top-tier Macro Economist. Analyze the stock ${ticker} with extreme specificity. Mention a specific economic factor like "global logistics", "energy costs", or "labor markets" as it relates to this EXACT company. Provide a 12-month price target. 1 short sentence.`),
86
+ new HumanMessage(`What is your unique macro view on ${ticker}?`)
87
+ ]);
88
+ setMessages([{ agent: 'macro', text: macroResponse.content }]);
89
+
90
+ // Agent 2: Tech
91
+ setMessages(prev => [...prev, { agent: 'tech', text: `Evaluating technical metrics for ${ticker}...` }]);
92
+ const techResponse = await llm.invoke([
93
+ new SystemMessage(`You are a Technical Analyst. Analyze ${ticker}'s price chart. Mention a specific "support zone", "RSI divergence", or "moving average cross" observation for this company. Provide a specific Fair Value. 1 sentence.`),
94
+ new HumanMessage(`Provide a non-generic technical view on ${ticker}.`)
95
+ ]);
96
+ setMessages(prev => [prev[0], { agent: 'tech', text: techResponse.content }]);
97
+
98
+ // Agent 3: Skeptic
99
+ setMessages(prev => [...prev, { agent: 'skeptic', text: `Quantifying downside risks for ${ticker}...` }]);
100
+ const skepticResponse = await llm.invoke([
101
+ new SystemMessage(`You are a Professional Skeptic. Find a "poison pill" for ${ticker}. What is the one specific, non-obvious risk (e.g. patent cliff, specific litigation, supply chain bottleneck) for this stock? 1 sentence.`),
102
+ new HumanMessage(`What is the hidden risk in ${ticker}?`)
103
+ ]);
104
+ setMessages(prev => [prev[0], prev[1], { agent: 'skeptic', text: skepticResponse.content }]);
105
+
106
+ // System consensus
107
+ setMessages(prev => [...prev, { agent: 'system', text: 'Synthesizing quantitative consensus...' }]);
108
+ const consensusResponse = await llm.invoke([
109
+ new SystemMessage("Committee Moderator. Based on the previous data points, output a final Conviction Score (0-100). Format: [SCORE] followed by a 1-sentence quantitative justification."),
110
+ new HumanMessage(`Synthesize these quantitative views for ${ticker}: 1. ${macroResponse.content} 2. ${techResponse.content} 3. ${skepticResponse.content}`)
111
+ ]);
112
+
113
+ let score = parseInt(consensusResponse.content.replace(/\D/g,''));
114
+ if (isNaN(score)) score = 50;
115
+
116
+ setMessages(prev => [prev[0], prev[1], prev[2], { agent: 'system', text: consensusResponse.content }]);
117
+ setConvictionScore(score);
118
+
119
+ } catch (error) {
120
+ console.error("LLM Error:", error);
121
+ runMockDebate();
122
+ }
123
+ } else {
124
+ runMockDebate();
125
+ }
126
+
127
+ setLoading(false);
128
+ };
129
+
130
+ const runMockDebate = () => {
131
+ const script = mockDebates[ticker] || mockDebates['default'];
132
+ const debateScript = script.map(msg => ({
133
+ ...msg,
134
+ text: msg.text.replace(/\[TICKER\]/g, ticker)
135
+ }));
136
+
137
+ let step = 0;
138
+ const interval = setInterval(() => {
139
+ if (step < debateScript.length) {
140
+ const msg = debateScript[step];
141
+ if (msg.agent === 'system' && msg.text.includes('Conviction Score')) {
142
+ const match = msg.text.match(/(\d+)\/100/);
143
+ if (match) setConvictionScore(parseInt(match[1]));
144
+ }
145
+ setMessages(prev => [...prev, msg]);
146
+ step++;
147
+ } else {
148
+ clearInterval(interval);
149
+ setLoading(false);
150
+ }
151
+ }, 1200);
152
+ };
153
+
154
+ return (
155
+ <div className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 flex flex-col h-[500px]">
156
+ <header className="mb-4 flex justify-between items-center border-b pb-4">
157
+ <div>
158
+ <h2 className="text-xl font-medium text-gs-navy flex items-center">
159
+ <Users className="mr-2 text-gs-gold" size={20} /> AI Investment Committee
160
+ </h2>
161
+ <p className="text-sm text-gs-slate font-light mt-1">
162
+ {ticker ? `Analyzing: ${ticker}` : 'Select an asset from your portfolio chart to debate.'}
163
+ </p>
164
+ </div>
165
+ {ticker && !isDebating && !convictionScore && (
166
+ <button
167
+ onClick={runDebate}
168
+ disabled={loading}
169
+ className="flex items-center px-4 py-2 bg-gs-navy text-white text-sm rounded-lg hover:bg-gs-navy/90 transition-colors"
170
+ >
171
+ {loading ? <Loader2 size={16} className="animate-spin mr-2" /> : <PlayCircle size={16} className="mr-2" />}
172
+ Start Debate
173
+ </button>
174
+ )}
175
+ </header>
176
+
177
+ <div className="flex-1 overflow-y-auto space-y-4 pr-2 custom-scrollbar" ref={scrollRef}>
178
+ {!ticker && (
179
+ <div className="h-full flex items-center justify-center text-gray-400 text-sm italic">
180
+ Waiting for asset selection...
181
+ </div>
182
+ )}
183
+
184
+ <AnimatePresence>
185
+ {messages.map((msg, idx) => (
186
+ <motion.div
187
+ key={idx}
188
+ initial={{ opacity: 0, y: 10 }}
189
+ animate={{ opacity: 1, y: 0 }}
190
+ className="flex gap-3"
191
+ >
192
+ <div className={`mt-1 flex-shrink-0 w-8 h-8 rounded-full ${agents[msg.agent].bg} ${agents[msg.agent].color} flex items-center justify-center`}>
193
+ {agents[msg.agent].icon}
194
+ </div>
195
+ <div className="bg-gray-50 rounded-xl p-3 text-sm text-gs-slate border border-gray-100 w-full">
196
+ <span className={`text-xs font-semibold uppercase tracking-wider block mb-1 ${agents[msg.agent].color}`}>
197
+ {agents[msg.agent].name}
198
+ </span>
199
+ {msg.text}
200
+ </div>
201
+ </motion.div>
202
+ ))}
203
+ </AnimatePresence>
204
+
205
+ {loading && messages.length > 0 && messages.length < 4 && (
206
+ <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="flex items-center text-gray-400 text-sm ml-11">
207
+ <Loader2 size={14} className="animate-spin mr-2" /> Typing...
208
+ </motion.div>
209
+ )}
210
+ </div>
211
+
212
+ {convictionScore !== null && (
213
+ <motion.div
214
+ initial={{ opacity: 0, scale: 0.95 }}
215
+ animate={{ opacity: 1, scale: 1 }}
216
+ className="mt-4 pt-4 border-t"
217
+ >
218
+ <div className="bg-gs-light p-4 rounded-xl">
219
+ <div className="flex justify-between items-center">
220
+ <span className="font-medium text-gs-navy">Consensus Conviction Score</span>
221
+ <div className="flex items-center">
222
+ <span className={`text-2xl font-bold ${convictionScore >= 70 ? 'text-green-600' : convictionScore >= 40 ? 'text-gs-gold' : 'text-red-500'}`}>
223
+ {convictionScore}
224
+ </span>
225
+ <span className="text-gray-400 ml-1">/ 100</span>
226
+ </div>
227
+ </div>
228
+ <p className="text-[10px] text-gs-slate mt-2 italic leading-tight border-t border-gray-200 pt-2">
229
+ The committee's confidence in this asset's current risk-to-reward.
230
+ <span className="font-bold ml-1">70+ Overweight</span>,
231
+ <span className="font-bold ml-1">40-69 Hold</span>,
232
+ <span className="font-bold ml-1">Below 40 Underweight</span>.
233
+ </p>
234
+ </div>
235
+ </motion.div>
236
+ )}
237
+ </div>
238
+ );
239
+ }
src/components/MacroTracker.jsx ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Globe, Ship, AlertTriangle, ShieldCheck, Loader2, X } from 'lucide-react';
3
+ import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
4
+ import { HumanMessage } from '@langchain/core/messages';
5
+ import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
6
+
7
+ const newsHeadlines = [
8
+ { source: 'Right-Leaning', headline: 'Supply Chain Bottlenecks Expose Dependence on Foreign Imports', sentiment: 'negative' },
9
+ { source: 'Left-Leaning', headline: 'Global Trade Disruptions Threaten Consumer Price Stability', sentiment: 'negative' },
10
+ { source: 'Financial Times', headline: 'Port Congestion Peaks as Holiday Inventory Arrives Early', sentiment: 'neutral' }
11
+ ];
12
+
13
+ // Fallback data in case the ArcGIS API fails due to CORS or downtime
14
+ const mockPortData = Array.from({ length: 30 }, (_, i) => ({
15
+ date: new Date(Date.now() - (29 - i) * 24 * 60 * 60 * 1000).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
16
+ calls: Math.floor(12000 + Math.random() * 3000 + (i > 20 ? -2000 : 0)) // Simulating a recent dip
17
+ }));
18
+
19
+ export default function MacroTracker() {
20
+ const [isOpen, setIsOpen] = useState(false);
21
+ const [analysis, setAnalysis] = useState(null);
22
+ const [loading, setLoading] = useState(false);
23
+ const [chartData, setChartData] = useState([]);
24
+ const [isLive, setIsLive] = useState(false);
25
+
26
+ useEffect(() => {
27
+ const fetchPortWatchData = async () => {
28
+ try {
29
+ const apiUrl = "https://services9.arcgis.com/weJ1QsnbMYJlCHdG/arcgis/rest/services/Daily_Trade_Data/FeatureServer/0/query?where=1=1&outFields=date,calls&orderByFields=date DESC&resultRecordCount=30&f=json";
30
+ const response = await fetch(apiUrl);
31
+ if (!response.ok) throw new Error('Network response was not ok');
32
+ const data = await response.json();
33
+
34
+ if (data && data.features && data.features.length > 0) {
35
+ const parsedData = data.features.map(f => ({
36
+ date: new Date(f.attributes.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
37
+ calls: f.attributes.calls || 0
38
+ })).reverse();
39
+ setChartData(parsedData);
40
+ setIsLive(true);
41
+ } else {
42
+ throw new Error('No features returned');
43
+ }
44
+ } catch (error) {
45
+ console.warn("Failed to fetch live ArcGIS data. Falling back to mock dataset.", error);
46
+ setChartData(mockPortData);
47
+ setIsLive(false);
48
+ }
49
+ };
50
+ fetchPortWatchData();
51
+ }, []);
52
+
53
+ const synthesizeNews = async () => {
54
+ setLoading(true);
55
+ setAnalysis(null);
56
+
57
+ const apiKey = import.meta.env.VITE_GEMINI_API_KEY;
58
+
59
+ if (apiKey && apiKey.length > 10) {
60
+ try {
61
+ const llm = new ChatGoogleGenerativeAI({
62
+ apiKey: apiKey,
63
+ modelName: 'gemini-1.5-flash',
64
+ maxOutputTokens: 2048,
65
+ });
66
+
67
+ const recentDataSummary = chartData.slice(-5).map(d => `${d.date}: ${d.calls} calls`).join(", ");
68
+
69
+ const promptText = `
70
+ You are an expert, calming financial advisor speaking to a novice investor.
71
+ The user is looking at a custom dashboard of global maritime trade data (port calls).
72
+ Here is the raw data for the last 5 days indicating recent activity levels: [${recentDataSummary}].
73
+
74
+ Additionally, read these recent news headlines causing panic:
75
+ 1. ${newsHeadlines[0].headline}
76
+ 2. ${newsHeadlines[1].headline}
77
+ 3. ${newsHeadlines[2].headline}
78
+
79
+ Provide a grounded, jargon-free explanation (max 3-4 sentences) that synthesizes this numerical data and the news.
80
+ Explain why this data represents normal market noise and why they should not panic sell their portfolio.
81
+ `;
82
+
83
+ const message = new HumanMessage({
84
+ content: [{ type: "text", text: promptText }]
85
+ });
86
+
87
+ const response = await llm.invoke([message]);
88
+ setAnalysis(response.content);
89
+ setLoading(false);
90
+ } catch (error) {
91
+ console.error("Gemini Error:", error);
92
+ runMockAnalysis();
93
+ }
94
+ } else {
95
+ runMockAnalysis();
96
+ }
97
+ };
98
+
99
+ const runMockAnalysis = () => {
100
+ setTimeout(() => {
101
+ setAnalysis("While the news highlights supply chain bottlenecks, the port data shows that daily ship calls remain within normal seasonal ranges, despite a slight recent dip. This indicates that global trade is still flowing actively. Temporary disruptions and the resulting inflation spikes rarely derail a well-diversified, long-term portfolio. Stay the course and avoid making emotional decisions based on short-term headlines.");
102
+ setLoading(false);
103
+ }, 2000);
104
+ };
105
+
106
+ const handleOpen = () => {
107
+ setIsOpen(true);
108
+ if (!analysis) {
109
+ synthesizeNews();
110
+ }
111
+ };
112
+
113
+ return (
114
+ <>
115
+ <button
116
+ onClick={handleOpen}
117
+ className="w-full mt-8 py-4 bg-gs-navy text-white rounded-xl hover:bg-gs-navy/90 transition-all flex justify-center items-center font-medium shadow-md group"
118
+ >
119
+ <Globe className="mr-3 text-gs-gold group-hover:rotate-12 transition-transform" size={24} />
120
+ Generate Ground Truth (Global Uncertainty Tracker)
121
+ </button>
122
+
123
+ {isOpen && (
124
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200">
125
+ <div className="w-full max-w-5xl bg-white rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[90vh]">
126
+ <div className="bg-gs-navy p-6 text-white flex justify-between items-center sticky top-0 z-10">
127
+ <div>
128
+ <h2 className="text-xl font-medium flex items-center">
129
+ <Globe className="mr-2 text-gs-gold" size={20} /> Global Uncertainty Tracker
130
+ </h2>
131
+ <p className="text-sm text-gs-light/70 font-light mt-1">
132
+ Contextualizing geopolitical noise with real-world data to keep you grounded.
133
+ </p>
134
+ </div>
135
+ <button onClick={() => setIsOpen(false)} className="p-2 hover:bg-white/10 rounded-full transition-colors">
136
+ <X size={24} />
137
+ </button>
138
+ </div>
139
+
140
+ <div className="p-6 overflow-y-auto">
141
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
142
+ {/* Left: Custom Port Data Chart */}
143
+ <div className="flex flex-col">
144
+ <h3 className="text-sm text-gs-slate uppercase tracking-wider font-medium mb-3 flex items-center justify-between">
145
+ <div className="flex items-center">
146
+ <Ship size={16} className="mr-2 text-blue-500" /> Global Port Activity
147
+ </div>
148
+ <span className={`text-xs font-semibold px-2 py-1 rounded ${isLive ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600'}`}>
149
+ {isLive ? 'LIVE IMF DATA' : 'MOCK DATA FALLBACK'}
150
+ </span>
151
+ </h3>
152
+ <div className="rounded-xl border border-gray-200 p-4 h-64 w-full bg-gray-50/50">
153
+ <ResponsiveContainer width="100%" height="100%">
154
+ <AreaChart data={chartData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
155
+ <defs>
156
+ <linearGradient id="colorCalls" x1="0" y1="0" x2="0" y2="1">
157
+ <stop offset="5%" stopColor="#1E293B" stopOpacity={0.8}/>
158
+ <stop offset="95%" stopColor="#1E293B" stopOpacity={0}/>
159
+ </linearGradient>
160
+ </defs>
161
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#E5E7EB" />
162
+ <XAxis dataKey="date" axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#64748B' }} minTickGap={30} />
163
+ <YAxis axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#64748B' }} />
164
+ <Tooltip
165
+ contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
166
+ labelStyle={{ fontWeight: 'bold', color: '#1E293B' }}
167
+ />
168
+ <Area type="monotone" dataKey="calls" stroke="#1E293B" strokeWidth={2} fillOpacity={1} fill="url(#colorCalls)" />
169
+ </AreaChart>
170
+ </ResponsiveContainer>
171
+ </div>
172
+ <p className="text-xs text-gs-slate mt-2 italic">Daily maritime trade volume index based on global port calls.</p>
173
+ </div>
174
+
175
+ {/* Right: News */}
176
+ <div className="flex flex-col">
177
+ <h3 className="text-sm text-gs-slate uppercase tracking-wider font-medium mb-3 flex items-center">
178
+ <AlertTriangle size={16} className="mr-2 text-red-500" /> The News Cycle
179
+ </h3>
180
+ <div className="space-y-3 mb-6 flex-grow">
181
+ {newsHeadlines.map((news, idx) => (
182
+ <div key={idx} className="bg-gray-50 border-l-2 border-gs-gold p-3 rounded-r-lg text-sm">
183
+ <span className="text-xs font-semibold text-gs-slate mb-1 block uppercase tracking-wide">{news.source}</span>
184
+ <span className="text-gs-navy font-medium italic">"{news.headline}"</span>
185
+ </div>
186
+ ))}
187
+ </div>
188
+ </div>
189
+ </div>
190
+
191
+ {/* Analysis Output */}
192
+ <div className="bg-gs-light p-6 rounded-xl border border-gray-200">
193
+ <div className="flex">
194
+ <div className="flex-shrink-0 mr-4">
195
+ <div className="w-10 h-10 rounded-full bg-gs-navy flex items-center justify-center text-gs-gold">
196
+ {loading ? <Loader2 size={20} className="animate-spin" /> : <ShieldCheck size={20} />}
197
+ </div>
198
+ </div>
199
+ <div>
200
+ <h4 className="text-sm font-semibold uppercase tracking-wider text-gs-navy mb-1">
201
+ {loading ? 'Synthesizing Ground Truth...' : 'Grounded Analysis'}
202
+ </h4>
203
+ {analysis ? (
204
+ <p className="text-gs-slate leading-relaxed font-light">{analysis}</p>
205
+ ) : (
206
+ <div className="h-4 bg-gray-200 rounded w-3/4 animate-pulse mt-2"></div>
207
+ )}
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ )}
215
+ </>
216
+ );
217
+ }
src/components/PortfolioHeatmap.jsx ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useMemo } from 'react';
2
+ import { Treemap, ResponsiveContainer, Tooltip } from 'recharts';
3
+ import { getHistoricalData } from '../data/historicalData';
4
+ import { motion } from 'framer-motion';
5
+ import { X, Info } from 'lucide-react';
6
+
7
+ const HeatmapContent = (props) => {
8
+ const { x, y, width, height, name, performance } = props;
9
+
10
+ if (width < 5 || height < 5) return null;
11
+
12
+ const color = performance > 0
13
+ ? `rgba(0, 200, 5, ${Math.min(0.2 + Math.abs(performance) / 5, 0.9)})`
14
+ : `rgba(255, 80, 0, ${Math.min(0.2 + Math.abs(performance) / 5, 0.9)})`;
15
+
16
+ return (
17
+ <g>
18
+ <rect
19
+ x={x}
20
+ y={y}
21
+ width={width}
22
+ height={height}
23
+ style={{
24
+ fill: color,
25
+ stroke: '#fff',
26
+ strokeWidth: 1,
27
+ strokeOpacity: 0.2,
28
+ }}
29
+ />
30
+ {width > 40 && height > 30 && (
31
+ <text
32
+ x={x + width / 2}
33
+ y={y + height / 2 + 5}
34
+ textAnchor="middle"
35
+ fill="#fff"
36
+ fontSize={Math.min(width / 6, 12)}
37
+ fontWeight="bold"
38
+ className="pointer-events-none select-none"
39
+ >
40
+ {name}
41
+ </text>
42
+ )}
43
+ </g>
44
+ );
45
+ };
46
+
47
+ export default function PortfolioHeatmap({ onClose, allocation }) {
48
+ const heatmapData = useMemo(() => {
49
+ if (!allocation) return [];
50
+ return allocation.map(asset => {
51
+ try {
52
+ const hist = getHistoricalData(asset.ticker);
53
+ return {
54
+ name: asset.ticker,
55
+ size: asset.value || 1,
56
+ performance: hist?.percentChange || 0,
57
+ fullName: asset.name
58
+ };
59
+ } catch (e) {
60
+ return {
61
+ name: asset.ticker,
62
+ size: asset.value || 1,
63
+ performance: 0,
64
+ fullName: asset.name
65
+ };
66
+ }
67
+ });
68
+ }, [allocation]);
69
+
70
+ return (
71
+ <div className="fixed inset-0 z-[70] flex items-center justify-center p-4">
72
+ <motion.div
73
+ initial={{ opacity: 0 }}
74
+ animate={{ opacity: 1 }}
75
+ exit={{ opacity: 0 }}
76
+ onClick={onClose}
77
+ className="absolute inset-0 bg-gs-navy/60 backdrop-blur-md"
78
+ />
79
+ <motion.div
80
+ initial={{ opacity: 0, scale: 0.95, y: 20 }}
81
+ animate={{ opacity: 1, scale: 1, y: 0 }}
82
+ exit={{ opacity: 0, scale: 0.95, y: 20 }}
83
+ className="relative bg-[#111111] rounded-3xl shadow-2xl w-full max-w-5xl overflow-hidden border border-white/10 flex flex-col max-h-[90vh]"
84
+ >
85
+ {/* Header */}
86
+ <div className="p-6 md:p-8 border-b border-white/5 flex justify-between items-center bg-black/20">
87
+ <div>
88
+ <h2 className="text-2xl font-bold text-white flex items-center">
89
+ Portfolio Heatmap
90
+ <span className="ml-3 px-2 py-1 bg-white/10 rounded text-[10px] uppercase tracking-widest text-white/60 font-medium">
91
+ Live Analysis
92
+ </span>
93
+ </h2>
94
+ <p className="text-gray-400 text-sm mt-1 font-light">
95
+ Box size: Allocation % | Color: Daily Performance
96
+ </p>
97
+ </div>
98
+ <button
99
+ onClick={onClose}
100
+ className="p-2 rounded-full bg-white/5 text-white/40 hover:text-white hover:bg-white/10 transition-colors"
101
+ >
102
+ <X size={24} />
103
+ </button>
104
+ </div>
105
+
106
+ <div className="p-6 md:p-8 overflow-y-auto">
107
+ <div className="h-[400px] md:h-[500px] w-full bg-black/40 rounded-2xl overflow-hidden border border-white/5">
108
+ <ResponsiveContainer width="100%" height="100%">
109
+ <Treemap
110
+ data={heatmapData}
111
+ dataKey="size"
112
+ aspectRatio={4 / 3}
113
+ stroke="#fff"
114
+ content={<HeatmapContent />}
115
+ >
116
+ <Tooltip
117
+ content={({ active, payload }) => {
118
+ if (active && payload && payload.length) {
119
+ const data = payload[0].payload;
120
+ return (
121
+ <div className="bg-[#222] border border-white/10 p-4 rounded-xl shadow-2xl min-w-[180px]">
122
+ <p className="text-white font-bold text-base">{data.name}</p>
123
+ <p className="text-gray-400 text-[10px] mb-3 truncate max-w-[160px]">{data.fullName}</p>
124
+ <div className="flex justify-between items-center border-t border-white/5 pt-2">
125
+ <div className="text-left">
126
+ <span className="text-[9px] text-gray-500 uppercase block">Allocation</span>
127
+ <span className="text-white text-sm font-medium">{data.size}%</span>
128
+ </div>
129
+ <div className="text-right">
130
+ <span className="text-[9px] text-gray-500 uppercase block">Day Change</span>
131
+ <span className={`text-sm font-bold ${data.performance > 0 ? 'text-[#00C805]' : 'text-[#FF5000]'}`}>
132
+ {data.performance > 0 ? '+' : ''}{data.performance}%
133
+ </span>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ );
138
+ }
139
+ return null;
140
+ }}
141
+ />
142
+ </Treemap>
143
+ </ResponsiveContainer>
144
+ </div>
145
+
146
+ {/* Legend */}
147
+ <div className="mt-8 flex flex-wrap items-center justify-between gap-6">
148
+ <div className="flex items-center gap-6">
149
+ <div className="flex items-center gap-2">
150
+ <div className="w-3 h-3 bg-[#FF5000] rounded-sm shadow-[0_0_10px_rgba(255,80,0,0.3)]"></div>
151
+ <span className="text-[11px] text-gray-400 font-medium">Down</span>
152
+ </div>
153
+ <div className="flex items-center gap-2">
154
+ <div className="w-3 h-3 bg-[#00C805] rounded-sm shadow-[0_0_10px_rgba(0,200,5,0.3)]"></div>
155
+ <span className="text-[11px] text-gray-400 font-medium">Up</span>
156
+ </div>
157
+ </div>
158
+
159
+ <div className="flex items-center gap-2 text-gray-500 bg-white/5 px-4 py-2 rounded-lg border border-white/5">
160
+ <Info size={14} className="text-gs-gold" />
161
+ <span className="text-[10px] uppercase tracking-wider font-medium">Color intensity scales with volatility</span>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </motion.div>
166
+ </div>
167
+ );
168
+ }
src/components/RebalancingEngine.jsx ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { rebalancingScenarios } from '../data/mockData';
3
+ import { TrendingDown, ArrowUpRight, HandCoins } from 'lucide-react';
4
+
5
+ export default function RebalancingEngine({ onScenarioSelect }) {
6
+ const scenarios = [
7
+ { id: 'marketDrop', icon: <TrendingDown size={20} />, data: rebalancingScenarios.marketDrop },
8
+ { id: 'inflation', icon: <ArrowUpRight size={20} />, data: rebalancingScenarios.inflation },
9
+ { id: 'withdrawal', icon: <HandCoins size={20} />, data: rebalancingScenarios.withdrawal },
10
+ ];
11
+
12
+ return (
13
+ <div className="bg-white rounded-2xl p-8 shadow-sm border border-gray-100 h-full">
14
+ <header className="mb-8">
15
+ <h2 className="text-2xl font-light text-gs-navy mb-2">Scenario Planner</h2>
16
+ <p className="text-sm text-gs-slate font-light">
17
+ Life happens. See how your portfolio should adapt to different situations.
18
+ </p>
19
+ </header>
20
+
21
+ <div className="space-y-4">
22
+ {scenarios.map((scenario) => (
23
+ <button
24
+ key={scenario.id}
25
+ onClick={() => onScenarioSelect(scenario.data)}
26
+ className="w-full text-left p-5 rounded-xl border border-gray-200 hover:border-gs-gold hover:shadow-md transition-all duration-300 group bg-gs-light/50 hover:bg-white"
27
+ >
28
+ <div className="flex items-center mb-3">
29
+ <div className="w-10 h-10 rounded-full bg-white shadow-sm flex items-center justify-center mr-4 text-gs-slate group-hover:text-gs-gold transition-colors">
30
+ {scenario.icon}
31
+ </div>
32
+ <h3 className="font-medium text-gs-navy">{scenario.data.trigger}</h3>
33
+ </div>
34
+ <p className="text-sm text-gs-slate font-light ml-14 group-hover:text-gs-navy transition-colors">
35
+ Click to see recommended actions
36
+ </p>
37
+ </button>
38
+ ))}
39
+ </div>
40
+ </div>
41
+ );
42
+ }
src/components/StockPopup.jsx ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useState, useMemo } from 'react';
2
+ import { X, TrendingUp, TrendingDown, Star } from 'lucide-react';
3
+ import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine } from 'recharts';
4
+ import { getHistoricalData, fetchRealData } from '../data/historicalData';
5
+ import { Loader2 } from 'lucide-react';
6
+ import InvestmentCommittee from './InvestmentCommittee';
7
+
8
+ const TIMEFRAME_DAYS = {
9
+ '1W': 7,
10
+ '1M': 30,
11
+ '3M': 90,
12
+ '1Y': 365,
13
+ 'ALL': 365
14
+ };
15
+
16
+ export default function StockPopup({ ticker, assetName, onClose }) {
17
+ const [data, setData] = useState(null);
18
+ const [loading, setLoading] = useState(false);
19
+ const [isDebating, setIsDebating] = useState(false);
20
+ const [timeframe, setTimeframe] = useState('1M');
21
+
22
+ useEffect(() => {
23
+ async function loadData() {
24
+ if (ticker) {
25
+ setLoading(true);
26
+ const realData = await fetchRealData(ticker);
27
+ setData(realData);
28
+ setLoading(false);
29
+ setIsDebating(false);
30
+ }
31
+ }
32
+ loadData();
33
+ }, [ticker]);
34
+
35
+ const viewData = useMemo(() => {
36
+ if (!data) return null;
37
+
38
+ // Slice history based on timeframe
39
+ const daysToShow = TIMEFRAME_DAYS[timeframe] || 30;
40
+ const startIndex = Math.max(0, data.history.length - daysToShow);
41
+ const slicedHistory = data.history.slice(startIndex);
42
+
43
+ // Recalculate metrics for the specific timeframe
44
+ if (slicedHistory.length === 0) return null;
45
+
46
+ const currentPrice = slicedHistory[slicedHistory.length - 1].price;
47
+ const oldPrice = slicedHistory[0].price;
48
+ const change = Number((currentPrice - oldPrice).toFixed(2));
49
+ const percentChange = Number(((change / oldPrice) * 100).toFixed(2));
50
+ const isPositive = change >= 0;
51
+
52
+ return {
53
+ ...data,
54
+ history: slicedHistory,
55
+ change,
56
+ percentChange,
57
+ isPositive
58
+ };
59
+ }, [data, timeframe]);
60
+
61
+ if (!ticker) return null;
62
+
63
+ if (loading || !viewData) {
64
+ return (
65
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm">
66
+ <div className="text-center">
67
+ <Loader2 className="animate-spin text-gs-gold mb-4 mx-auto" size={48} />
68
+ <p className="text-white text-lg font-light">Connecting to Yahoo Finance...</p>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ const color = viewData.isPositive ? '#00C805' : '#FF5000';
75
+ const bgColor = '#111111'; // Dark theme background
76
+
77
+ return (
78
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200">
79
+ <div
80
+ className="w-full max-w-4xl max-h-[90vh] overflow-y-auto rounded-xl shadow-2xl flex flex-col animate-in zoom-in-95 duration-200"
81
+ style={{ backgroundColor: bgColor }}
82
+ >
83
+ {/* Header */}
84
+ <div className="p-6 pb-2 flex justify-between items-start sticky top-0 bg-[#111111] z-10 border-b border-gray-800">
85
+ <div>
86
+ <h2 className="text-2xl font-bold text-white flex items-center">
87
+ {assetName} <Star size={18} className="ml-3 text-gray-500 hover:text-yellow-400 cursor-pointer" />
88
+ </h2>
89
+ <div className="flex items-end mt-2 space-x-3">
90
+ <span className="text-4xl font-bold text-white">${viewData.currentPrice}</span>
91
+ <div className={`flex items-center text-lg font-medium pb-1 ${viewData.isPositive ? 'text-[#00C805]' : 'text-[#FF5000]'}`}>
92
+ {viewData.isPositive ? '+' : ''}{viewData.change} ({viewData.isPositive ? '+' : ''}{viewData.percentChange}%)
93
+ </div>
94
+ </div>
95
+ <p className="text-gray-400 text-xs mt-1">At close: {viewData.history[viewData.history.length-1].date}</p>
96
+ </div>
97
+ <button
98
+ onClick={onClose}
99
+ className="p-2 rounded-full bg-gray-800 text-gray-400 hover:text-white hover:bg-gray-700 transition-colors"
100
+ >
101
+ <X size={20} />
102
+ </button>
103
+ </div>
104
+
105
+ <div className="p-6 grid grid-cols-1 lg:grid-cols-3 gap-8">
106
+ {/* Main Chart Area */}
107
+ <div className="lg:col-span-2">
108
+ <div className="h-72 w-full mt-4">
109
+ <ResponsiveContainer width="100%" height="100%">
110
+ <AreaChart data={viewData.history} margin={{ top: 10, right: 0, left: 0, bottom: 0 }}>
111
+ <defs>
112
+ <linearGradient id="colorPrice" x1="0" y1="0" x2="0" y2="1">
113
+ <stop offset="5%" stopColor={color} stopOpacity={0.3}/>
114
+ <stop offset="95%" stopColor={color} stopOpacity={0}/>
115
+ </linearGradient>
116
+ </defs>
117
+ <XAxis dataKey="date" hide />
118
+ <YAxis domain={['dataMin', 'dataMax']} hide />
119
+ <Tooltip
120
+ contentStyle={{ backgroundColor: '#222', border: 'none', borderRadius: '8px', color: '#fff' }}
121
+ itemStyle={{ color: '#fff', fontWeight: 'bold' }}
122
+ labelStyle={{ color: '#888' }}
123
+ />
124
+ <ReferenceLine y={viewData.history[0].price} stroke="#333" strokeDasharray="3 3" />
125
+ <Area
126
+ type="monotone"
127
+ dataKey="price"
128
+ stroke={color}
129
+ strokeWidth={2}
130
+ fillOpacity={1}
131
+ fill="url(#colorPrice)"
132
+ />
133
+ </AreaChart>
134
+ </ResponsiveContainer>
135
+ </div>
136
+
137
+ {/* Time Controls */}
138
+ <div className="flex space-x-4 mt-6 border-b border-gray-800 pb-2">
139
+ {['1W', '1M', '3M', '1Y', 'ALL'].map(t => (
140
+ <button
141
+ key={t}
142
+ onClick={() => setTimeframe(t)}
143
+ className={`text-sm font-medium pb-2 border-b-2 transition-colors ${
144
+ t === timeframe
145
+ ? (viewData.isPositive ? 'text-[#00C805] border-[#00C805]' : 'text-[#FF5000] border-[#FF5000]')
146
+ : 'text-gray-500 border-transparent hover:text-gray-300'
147
+ }`}
148
+ >
149
+ {t}
150
+ </button>
151
+ ))}
152
+ </div>
153
+
154
+ <div className="mt-6 text-gray-300 text-sm leading-relaxed">
155
+ <p>Historical charting for {assetName} over the selected period. Notice the volatility patterns represented by the area chart.</p>
156
+ </div>
157
+ </div>
158
+
159
+ {/* Right Sidebar: AI Committee */}
160
+ <div className="lg:col-span-1">
161
+ {/* We render the Investment Committee inside a styled container so it fits the dark theme or stands out as a module */}
162
+ <div className="bg-white rounded-xl shadow-inner overflow-hidden border border-gray-200">
163
+ <InvestmentCommittee
164
+ ticker={ticker}
165
+ isDebating={isDebating}
166
+ setIsDebating={setIsDebating}
167
+ />
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ );
174
+ }
src/components/StockSearch.jsx ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Search, Plus, Loader2, X, Globe, Briefcase, Info, TrendingUp, ShieldCheck, PlayCircle } from 'lucide-react';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
5
+ import { HumanMessage, SystemMessage } from '@langchain/core/messages';
6
+
7
+ export default function StockSearch({ isOpen, onClose, onAddStock, currentPortfolio, totalValue }) {
8
+ const [query, setQuery] = useState('');
9
+ const [suggestions, setSuggestions] = useState([]);
10
+ const [loading, setLoading] = useState(false);
11
+ const [selectedStock, setSelectedStock] = useState(null);
12
+ const [shares, setShares] = useState(1);
13
+ const [impactAnalysis, setImpactAnalysis] = useState('');
14
+ const [isAnalyzing, setIsAnalyzing] = useState(false);
15
+ const [performanceData, setPerformanceData] = useState({ threeYearReturn: 0 });
16
+ const [error, setError] = useState('');
17
+ const searchRef = useRef(null);
18
+
19
+ // Debounced search for suggestions
20
+ useEffect(() => {
21
+ const timer = setTimeout(async () => {
22
+ if (query.length > 1) {
23
+ setLoading(true);
24
+ try {
25
+ const proxy = 'https://corsproxy.io/?';
26
+ const url = `${proxy}https://query2.finance.yahoo.com/v1/finance/search?q=${query}&newsCount=0`;
27
+ const response = await fetch(url);
28
+ const data = await response.json();
29
+
30
+ if (data.quotes) {
31
+ // Filter for US Stocks and Mutual Funds only
32
+ const usExchanges = ['NYSE', 'NASDAQ', 'AMEX', 'BATS', 'ARCA', 'OTCQB', 'OTCQX'];
33
+ const filtered = data.quotes
34
+ .filter(q => {
35
+ const isUS = usExchanges.some(ex => q.exchDisp?.includes(ex)) || !q.symbol.includes('.');
36
+ const isTargetType = q.quoteType === 'EQUITY' || q.quoteType === 'MUTUALFUND';
37
+ return q.symbol && isUS && isTargetType;
38
+ })
39
+ .slice(0, 6);
40
+ setSuggestions(filtered);
41
+ }
42
+ } catch (err) {
43
+ console.error("Autocomplete error:", err);
44
+ } finally {
45
+ setLoading(false);
46
+ }
47
+ } else {
48
+ setSuggestions([]);
49
+ }
50
+ }, 300);
51
+
52
+ return () => clearTimeout(timer);
53
+ }, [query]);
54
+
55
+ // Reset states when selectedStock is cleared
56
+ useEffect(() => {
57
+ if (!selectedStock) {
58
+ setImpactAnalysis('');
59
+ setShares(1);
60
+ }
61
+ }, [selectedStock]);
62
+
63
+ // Calculate 3-year performance whenever selection changes
64
+ useEffect(() => {
65
+ if (selectedStock) {
66
+ // Generate a stable mock performance based on ticker hash
67
+ const ticker = selectedStock.symbol;
68
+ let hash = 0;
69
+ for (let i = 0; i < ticker.length; i++) {
70
+ hash = ((hash << 5) - hash) + ticker.charCodeAt(i);
71
+ hash |= 0;
72
+ }
73
+ // Generate return between -20% and +120%
74
+ const returnVal = (hash % 140) - 20;
75
+ setPerformanceData({ threeYearReturn: returnVal });
76
+ }
77
+ }, [selectedStock]);
78
+
79
+ const analyzeImpact = async (stock, quantity) => {
80
+ console.log("Starting analyzeImpact", { stock: stock.symbol, quantity });
81
+ const apiKey = import.meta.env.VITE_GEMINI_API_KEY;
82
+
83
+ if (!apiKey || apiKey.length < 10) {
84
+ console.warn("API Key missing or too short, using local fallback");
85
+ setImpactAnalysis(`Adding ${quantity} shares of ${stock.symbol} will broaden your market exposure and shift your portfolio beta towards a more dynamic profile.`);
86
+ return;
87
+ }
88
+
89
+ setIsAnalyzing(true);
90
+ setImpactAnalysis(''); // Clear previous
91
+
92
+ try {
93
+ console.log("Invoking Gemini for impact analysis...");
94
+ const llm = new ChatGoogleGenerativeAI({
95
+ apiKey,
96
+ modelName: 'gemini-1.5-flash',
97
+ maxRetries: 1
98
+ });
99
+
100
+ const portfolioTickers = currentPortfolio.allocation.map(a => a.ticker).join(', ') || 'none';
101
+
102
+ const response = await llm.invoke([
103
+ new SystemMessage(`You are a quantitative advisor. Analyze the impact of adding ${quantity} shares of ${stock.symbol} to a portfolio of ${portfolioTickers}.
104
+ Output 1 short, specific sentence.`),
105
+ new HumanMessage(`Impact of ${quantity} shares of ${stock.symbol}.`)
106
+ ]);
107
+
108
+ console.log("AI Response received:", response.content);
109
+ setImpactAnalysis(response.content);
110
+ } catch (err) {
111
+ console.error("Impact Analysis API Error:", err);
112
+ // Immediate fallback so the user doesn't see a blank screen
113
+ const fallbackText = `This position in ${stock.symbol} provides new exposure that complements your existing holdings in ${currentPortfolio.allocation[0]?.ticker || 'diversified assets'}.`;
114
+ setImpactAnalysis(fallbackText);
115
+ } finally {
116
+ setIsAnalyzing(false);
117
+ console.log("Analysis cycle complete");
118
+ }
119
+ };
120
+
121
+ const handleSelect = (quote) => {
122
+ setSelectedStock(quote);
123
+ // Initial analysis triggered by useEffect
124
+ };
125
+
126
+ const handleConfirm = () => {
127
+ onAddStock({
128
+ ticker: selectedStock.symbol,
129
+ name: selectedStock.shortname || selectedStock.longname || selectedStock.symbol,
130
+ type: selectedStock.quoteType === 'MUTUALFUND' ? 'mf' : 'stock',
131
+ value: 5, // Base weighting, dashboard handles rebalance
132
+ color: `#${Math.floor(Math.random()*16777215).toString(16)}`,
133
+ shares: Number(shares),
134
+ dateBought: new Date().toISOString().split('T')[0]
135
+ });
136
+ setQuery('');
137
+ setSelectedStock(null);
138
+ setSuggestions([]);
139
+ onClose();
140
+ };
141
+
142
+ if (!isOpen) return null;
143
+
144
+ return (
145
+ <div className="fixed inset-0 z-[60] flex items-start justify-center pt-24 px-4 bg-gs-navy/40 backdrop-blur-md">
146
+ <motion.div
147
+ initial={{ opacity: 0, y: -20 }}
148
+ animate={{ opacity: 1, y: 0 }}
149
+ exit={{ opacity: 0, y: -20 }}
150
+ className="w-full max-w-xl bg-white rounded-2xl shadow-2xl overflow-hidden border border-gray-100"
151
+ ref={searchRef}
152
+ >
153
+ <div className="p-4 border-b border-gray-100 flex items-center gap-3">
154
+ <Search className="text-gs-gold" size={20} />
155
+ <input
156
+ autoFocus
157
+ type="text"
158
+ value={query}
159
+ onChange={(e) => setQuery(e.target.value)}
160
+ placeholder="Search company, ticker, or crypto..."
161
+ className="flex-1 bg-transparent border-none outline-none text-gs-navy text-lg placeholder:text-gray-300"
162
+ />
163
+ <button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-full transition-colors text-gs-slate">
164
+ <X size={20} />
165
+ </button>
166
+ </div>
167
+
168
+ <div className="max-h-[500px] overflow-y-auto">
169
+ {selectedStock ? (
170
+ <motion.div
171
+ initial={{ opacity: 0, x: 20 }}
172
+ animate={{ opacity: 1, x: 0 }}
173
+ className="p-8"
174
+ >
175
+ <div className="flex items-center gap-4 mb-8">
176
+ <div className="w-16 h-16 bg-gs-navy text-white rounded-2xl flex items-center justify-center text-2xl font-bold">
177
+ {selectedStock.symbol.charAt(0)}
178
+ </div>
179
+ <div>
180
+ <h3 className="text-2xl font-bold text-gs-navy">{selectedStock.symbol}</h3>
181
+ <p className="text-gs-slate">{selectedStock.shortname || selectedStock.longname}</p>
182
+ </div>
183
+ </div>
184
+
185
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
186
+ <div className="bg-gs-light p-4 rounded-xl">
187
+ <label className="text-xs font-bold text-gs-slate uppercase tracking-wider block mb-2">Quantity (Shares)</label>
188
+ <input
189
+ type="number"
190
+ value={shares}
191
+ onChange={(e) => setShares(e.target.value)}
192
+ min="1"
193
+ className="w-full bg-white border border-gray-200 rounded-lg py-2 px-3 text-lg font-bold text-gs-navy focus:outline-none focus:ring-2 focus:ring-gs-gold"
194
+ />
195
+ </div>
196
+ <div className="bg-gs-navy p-4 rounded-xl flex flex-col justify-center">
197
+ <p className="text-[10px] font-bold text-gs-gold uppercase tracking-widest mb-1">Trailing 3-Year Return</p>
198
+ <p className={`text-2xl font-bold ${performanceData.threeYearReturn >= 0 ? 'text-green-400' : 'text-red-400'}`}>
199
+ {performanceData.threeYearReturn >= 0 ? '+' : ''}{performanceData.threeYearReturn.toFixed(2)}%
200
+ </p>
201
+ <p className="text-[10px] text-gs-gold/60 mt-1 italic">Historical market performance</p>
202
+ </div>
203
+ </div>
204
+
205
+ <div className="bg-gs-navy text-white p-6 rounded-2xl mb-8 relative overflow-hidden min-h-[120px] flex flex-col justify-center">
206
+ <div className="relative z-10">
207
+ <div className="flex items-center gap-2 mb-3 text-gs-gold">
208
+ <ShieldCheck size={18} />
209
+ <span className="text-xs font-bold uppercase tracking-widest">Portfolio Impact Analysis</span>
210
+ </div>
211
+
212
+ {isAnalyzing ? (
213
+ <div className="flex items-center gap-3">
214
+ <Loader2 size={16} className="animate-spin" />
215
+ <span className="text-sm font-light italic">Committee is calculating risk shifts...</span>
216
+ </div>
217
+ ) : impactAnalysis ? (
218
+ <p className="text-sm font-light leading-relaxed animate-in fade-in slide-in-from-bottom-2 duration-500">
219
+ {impactAnalysis}
220
+ </p>
221
+ ) : (
222
+ <button
223
+ type="button"
224
+ onClick={() => {
225
+ console.log("Analyze button clicked", { selectedStock, shares });
226
+ handleAnalyze();
227
+ }}
228
+ className="flex items-center gap-2 bg-gs-gold text-gs-navy px-4 py-2 rounded-lg text-xs font-bold hover:bg-white transition-all shadow-lg active:scale-95"
229
+ >
230
+ <PlayCircle size={14} />
231
+ Generate Impact Analysis
232
+ </button>
233
+ )}
234
+ </div>
235
+ <TrendingUp className="absolute -right-4 -bottom-4 text-white/5" size={120} />
236
+ </div>
237
+
238
+ <div className="flex gap-4">
239
+ <button
240
+ onClick={() => setSelectedStock(null)}
241
+ className="flex-1 py-4 rounded-xl border border-gray-200 text-gs-slate font-medium hover:bg-gray-50 transition-all"
242
+ >
243
+ Back to Search
244
+ </button>
245
+ <button
246
+ onClick={handleConfirm}
247
+ className="flex-[2] py-4 rounded-xl bg-gs-navy text-white font-bold hover:bg-gs-gold hover:text-gs-navy transition-all shadow-lg hover:shadow-gs-gold/20"
248
+ >
249
+ Confirm & Add to Portfolio
250
+ </button>
251
+ </div>
252
+ </motion.div>
253
+ ) : (
254
+ <>
255
+ {loading && query.length > 1 && (
256
+ <div className="p-8 text-center text-gs-slate flex flex-col items-center gap-2">
257
+ <Loader2 className="animate-spin text-gs-gold" size={24} />
258
+ <span className="text-sm font-light">Searching global markets...</span>
259
+ </div>
260
+ )}
261
+
262
+ {!loading && suggestions.length > 0 && (
263
+ <div className="py-2">
264
+ {suggestions.map((quote, idx) => (
265
+ <button
266
+ key={idx}
267
+ onClick={() => handleSelect(quote)}
268
+ className="w-full flex items-center justify-between px-6 py-4 hover:bg-gs-light transition-all text-left group"
269
+ >
270
+ <div className="flex items-center gap-4">
271
+ <div className={`w-10 h-10 rounded-lg flex items-center justify-center text-gs-navy group-hover:bg-white transition-colors ${quote.quoteType === 'MUTUALFUND' ? 'bg-purple-100 text-purple-700' : 'bg-gray-100'}`}>
272
+ {quote.quoteType === 'MUTUALFUND' ? <Globe size={18} /> : <Briefcase size={18} />}
273
+ </div>
274
+ <div>
275
+ <div className="flex items-center gap-2">
276
+ <span className="font-bold text-gs-navy">{quote.symbol}</span>
277
+ <span className={`text-[8px] font-bold px-1.5 py-0.5 rounded uppercase tracking-tighter ${quote.quoteType === 'MUTUALFUND' ? 'bg-purple-500 text-white' : 'bg-gs-navy text-gs-gold'}`}>
278
+ {quote.quoteType === 'MUTUALFUND' ? 'Mutual Fund' : 'Stock'}
279
+ </span>
280
+ </div>
281
+ <div className="text-xs text-gs-slate truncate max-w-[250px]">
282
+ {quote.shortname || quote.longname}
283
+ </div>
284
+ </div>
285
+ </div>
286
+ <div className="flex items-center gap-2">
287
+ <span className="text-[10px] font-bold bg-gray-100 text-gray-500 px-2 py-1 rounded uppercase tracking-wider">
288
+ {quote.exchDisp}
289
+ </span>
290
+ <Plus size={16} className="text-gs-gold opacity-0 group-hover:opacity-100 transition-opacity" />
291
+ </div>
292
+ </button>
293
+ ))}
294
+ </div>
295
+ )}
296
+
297
+ {!loading && query.length > 1 && suggestions.length === 0 && (
298
+ <div className="p-12 text-center text-gs-slate font-light">
299
+ No assets found for "{query}"
300
+ </div>
301
+ )}
302
+
303
+ {!query && (
304
+ <div className="p-8 text-center text-gs-slate">
305
+ <p className="text-sm">Try searching for <span className="font-medium text-gs-navy">"Apple"</span>, <span className="font-medium text-gs-navy">"NVDA"</span>, or <span className="font-medium text-gs-navy">"Bitcoin"</span></p>
306
+ </div>
307
+ )}
308
+ </>
309
+ )}
310
+ </div>
311
+
312
+ <div className="p-3 bg-gs-light text-[10px] text-center text-gs-slate uppercase tracking-widest font-medium border-t border-gray-100">
313
+ Powered by Yahoo Finance Real-time Search
314
+ </div>
315
+ </motion.div>
316
+ </div>
317
+ );
318
+ }
src/components/TransparencyModal.jsx ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { X, Info, DollarSign, Shield } from 'lucide-react';
4
+
5
+ export default function TransparencyModal({ isOpen, onClose, data, currentPortfolio, totalValue, prices }) {
6
+ if (!isOpen || !data) return null;
7
+
8
+ return (
9
+ <AnimatePresence>
10
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
11
+ {/* Backdrop */}
12
+ <motion.div
13
+ initial={{ opacity: 0 }}
14
+ animate={{ opacity: 1 }}
15
+ exit={{ opacity: 0 }}
16
+ onClick={onClose}
17
+ className="absolute inset-0 bg-gs-navy/60 backdrop-blur-sm"
18
+ />
19
+
20
+ {/* Modal Content */}
21
+ <motion.div
22
+ initial={{ opacity: 0, scale: 0.95, y: 20 }}
23
+ animate={{ opacity: 1, scale: 1, y: 0 }}
24
+ exit={{ opacity: 0, scale: 0.95, y: 20 }}
25
+ className="relative bg-white rounded-3xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-hidden border border-gray-100 flex flex-col"
26
+ >
27
+ {/* Header (Fixed at top) */}
28
+ <div className="bg-gs-navy p-6 flex justify-between items-start text-white shrink-0">
29
+ <div>
30
+ <span className="text-gs-gold text-xs font-semibold uppercase tracking-wider mb-2 block">Recommendation</span>
31
+ <h2 className="text-2xl font-light">{data.title}</h2>
32
+ </div>
33
+ <button onClick={onClose} className="text-white/60 hover:text-white transition-colors">
34
+ <X size={24} />
35
+ </button>
36
+ </div>
37
+
38
+ <div className="p-8 space-y-6 overflow-y-auto scrollbar-hide">
39
+ {/* The Advice */}
40
+ <div className="bg-gs-light p-5 rounded-xl border-l-4 border-gs-gold">
41
+ <h3 className="font-medium text-gs-navy mb-1 flex items-center">
42
+ <Info size={18} className="mr-2 text-gs-gold" /> The Action Plan
43
+ </h3>
44
+ <p className="text-gs-slate font-light text-lg">
45
+ {(() => {
46
+ let advice = data.advice;
47
+ const hasBonds = currentPortfolio?.allocation?.some(a =>
48
+ a.ticker?.includes('BND') ||
49
+ a.name?.toLowerCase().includes('bond')
50
+ );
51
+ if (!hasBonds && advice.includes('Bonds')) {
52
+ advice = advice.replace('Bonds', 'Cash Reserves');
53
+ }
54
+ return advice;
55
+ })()}
56
+ </p>
57
+ </div>
58
+
59
+ {/* Visual Comparison Section */}
60
+ <div className="bg-gs-light/50 p-6 rounded-2xl border border-gray-100 min-h-[100px] flex flex-col justify-center">
61
+ <h3 className="text-xs uppercase tracking-widest text-gs-slate font-semibold mb-4">
62
+ Visual Rebalance Suggestion
63
+ </h3>
64
+
65
+ <div className="space-y-6">
66
+ {currentPortfolio?.allocation?.length > 0 ? (
67
+ currentPortfolio.allocation.slice(0, 3).map((asset, idx) => {
68
+ if (!asset) return null;
69
+
70
+ // Mock logic for "Target" based on scenario
71
+ let change = 0;
72
+ const trigger = data.trigger || "";
73
+ const assetName = asset.name || "";
74
+
75
+ if (trigger.includes("Market Drop")) {
76
+ if (assetName.includes("Bond") || assetName.includes("Cash")) change = -5;
77
+ else change = 5;
78
+ } else if (trigger.includes("Life Expense")) {
79
+ if (assetName.includes("Cash")) change = 20;
80
+ else change = -10;
81
+ } else if (trigger.includes("Inflation")) {
82
+ if (assetName.includes("Vanguard") || assetName.includes("Value")) change = 8;
83
+ else change = -4;
84
+ }
85
+
86
+ const val = Number(asset.value) || 0;
87
+ const targetValue = Math.max(0, Math.min(100, val + change));
88
+
89
+ // Calculate Dollar and Share Changes
90
+ const ticker = asset.ticker;
91
+ const priceObj = prices[ticker];
92
+ const price = priceObj?.price;
93
+
94
+ // Use a fallback total value if current calculation is still in flight
95
+ const activeTotal = totalValue > 0 ? totalValue : 50000;
96
+
97
+ const currentValue = (val / 100) * activeTotal;
98
+ const targetValueDollar = (targetValue / 100) * activeTotal;
99
+ const dollarDiff = targetValueDollar - currentValue;
100
+ const sharesDiff = price ? Math.abs(Math.round(dollarDiff / price)) : null;
101
+
102
+ return (
103
+ <div key={idx} className="space-y-2">
104
+ <div className="flex justify-between text-xs">
105
+ <span className="font-bold text-gs-navy">{ticker}</span>
106
+ <div className="text-right">
107
+ <span className="text-gs-slate">
108
+ {val.toFixed(2)}% <span className="mx-2">→</span>
109
+ <span className={change > 0 ? 'text-green-600' : change < 0 ? 'text-red-500' : 'text-gs-navy'}>
110
+ {targetValue.toFixed(2)}%
111
+ </span>
112
+ </span>
113
+ <p className={`text-[10px] font-bold ${change > 0 ? 'text-green-600' : 'text-red-500'}`}>
114
+ {change > 0 ? 'Buy' : 'Sell'} ${Math.abs(Math.round(dollarDiff)).toLocaleString()}
115
+ {sharesDiff !== null ? ` (${sharesDiff} shares)` : ' (Calculating...)'}
116
+ </p>
117
+ </div>
118
+ </div>
119
+ <div className="h-3 w-full bg-gray-200 rounded-full overflow-hidden flex relative">
120
+ <div
121
+ className="h-full bg-gs-navy opacity-30"
122
+ style={{ width: `${val}%` }}
123
+ ></div>
124
+ <div
125
+ className={`h-full absolute top-0 left-0 transition-all duration-1000 ${change >= 0 ? 'bg-green-500' : 'bg-red-500'}`}
126
+ style={{ width: `${targetValue}%` }}
127
+ ></div>
128
+ </div>
129
+ </div>
130
+ );
131
+ })
132
+ ) : (
133
+ <div className="text-center py-4">
134
+ <p className="text-xs text-gs-slate italic">Add at least one stock to see a visual rebalance simulation.</p>
135
+ </div>
136
+ )}
137
+ </div>
138
+ <p className="text-[10px] text-gs-slate mt-4 italic text-center">
139
+ *Simulated shift based on your {currentPortfolio?.riskLevel || 'selected'} risk profile.
140
+ </p>
141
+ </div>
142
+
143
+ {/* The Why */}
144
+ <div>
145
+ <h3 className="font-medium text-gs-navy mb-2 flex items-center">
146
+ <Shield size={18} className="mr-2 text-gs-slate" /> Why we recommend this
147
+ </h3>
148
+ <p className="text-gs-slate font-light text-sm leading-relaxed">
149
+ {data.explanation}
150
+ </p>
151
+ </div>
152
+
153
+ <hr className="border-gray-100" />
154
+
155
+ {/* Radical Transparency Section */}
156
+ <div>
157
+ <h3 className="text-xs uppercase tracking-widest text-gs-slate font-semibold mb-4">
158
+ Full Transparency
159
+ </h3>
160
+
161
+ <div className="space-y-3">
162
+ <div className="flex justify-between items-center p-3 rounded-lg border border-gray-100 bg-white">
163
+ <span className="text-sm text-gs-slate font-light flex items-center">
164
+ <DollarSign size={14} className="mr-2 text-gray-400" /> Fee Impact
165
+ </span>
166
+ <span className="font-medium text-gs-navy text-sm">{data.feeImpactDollars}</span>
167
+ </div>
168
+
169
+ <div className="flex justify-between items-center p-3 rounded-lg border border-gray-100 bg-white">
170
+ <span className="text-sm text-gs-slate font-light flex items-center">
171
+ <Shield size={14} className="mr-2 text-gray-400" /> Tax Considerations
172
+ </span>
173
+ <span className="font-medium text-gs-navy text-sm">{data.taxImpact}</span>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
178
+ <button
179
+ onClick={onClose}
180
+ className="w-full mt-4 bg-gs-navy text-white py-4 rounded-xl font-medium hover:bg-gs-navy/90 transition-colors shadow-md shrink-0"
181
+ >
182
+ I Understand
183
+ </button>
184
+ </div>
185
+ </motion.div>
186
+ </div>
187
+ </AnimatePresence>
188
+ );
189
+ }
src/data/historicalData.js ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { tickerMetrics } from './tickerMetrics';
2
+
3
+ // Helper to generate a realistic random walk for a stock price
4
+ const generateRandomWalk = (startPrice, days, volatility) => {
5
+ const data = [];
6
+ let currentPrice = startPrice;
7
+ const now = new Date();
8
+
9
+ for (let i = days; i >= 0; i--) {
10
+ const date = new Date(now);
11
+ date.setDate(date.getDate() - i);
12
+
13
+ // Random daily return based on volatility (standard deviation)
14
+ const dailyReturn = (Math.random() - 0.5) * volatility;
15
+ currentPrice = currentPrice * (1 + dailyReturn);
16
+
17
+ data.push({
18
+ time: date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
19
+ date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
20
+ price: Number(currentPrice.toFixed(2))
21
+ });
22
+ }
23
+ return data;
24
+ };
25
+
26
+ // Base starting prices for realism
27
+ const basePrices = {
28
+ 'SPY': 510.50,
29
+ 'VOO': 468.20,
30
+ 'BND': 72.15,
31
+ 'BNDX': 48.90,
32
+ 'AAPL': 175.40,
33
+ 'MSFT': 420.10,
34
+ 'GOOGL': 155.30,
35
+ 'AMZN': 180.25,
36
+ 'JNJ': 155.60,
37
+ 'BRK-B': 410.80,
38
+ 'JPM': 195.40,
39
+ 'VXUS': 58.70,
40
+ 'TSLA': 175.20,
41
+ 'QQQ': 440.50,
42
+ 'VTI': 255.60,
43
+ 'CASH': 1.00
44
+ };
45
+
46
+ const mockDataCache = {};
47
+
48
+ export const fetchRealData = async (ticker) => {
49
+ if (ticker === 'CASH') return getHistoricalData('CASH');
50
+
51
+ try {
52
+ const proxy = 'https://corsproxy.io/?';
53
+ const url = `${proxy}https://query1.finance.yahoo.com/v8/finance/chart/${ticker}?interval=1d&range=1y`;
54
+ const response = await fetch(url);
55
+ const data = await response.json();
56
+
57
+ if (!data.chart?.result?.[0]) throw new Error('Invalid ticker');
58
+
59
+ const result = data.chart.result[0];
60
+ const timestamps = result.timestamp;
61
+ const quotes = result.indicators.quote[0].close;
62
+
63
+ const history = timestamps.map((ts, i) => {
64
+ const date = new Date(ts * 1000);
65
+ return {
66
+ time: date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
67
+ date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
68
+ price: Number(quotes[i]?.toFixed(2)) || 0
69
+ };
70
+ }).filter(d => d.price > 0);
71
+
72
+ const currentPrice = history[history.length - 1].price;
73
+ const oldPrice = history[0].price;
74
+ const change = currentPrice - oldPrice;
75
+ const percentChange = (change / oldPrice) * 100;
76
+
77
+ const finalData = {
78
+ ticker,
79
+ currentPrice: Number(currentPrice.toFixed(2)),
80
+ change: Number(change.toFixed(2)),
81
+ percentChange: Number(percentChange.toFixed(2)),
82
+ isPositive: change >= 0,
83
+ history
84
+ };
85
+
86
+ mockDataCache[ticker] = finalData;
87
+ return finalData;
88
+ } catch (error) {
89
+ console.error("Yahoo Finance Error:", error);
90
+ return getHistoricalData(ticker); // Fallback to mock
91
+ }
92
+ };
93
+
94
+ export const getHistoricalData = (ticker) => {
95
+ if (mockDataCache[ticker]) {
96
+ return mockDataCache[ticker];
97
+ }
98
+
99
+ const startPrice = basePrices[ticker] || 100;
100
+ const metrics = tickerMetrics[ticker] || { beta: 1 };
101
+ const volatility = (metrics.beta * 0.015);
102
+
103
+ const history = generateRandomWalk(startPrice, 365, volatility);
104
+ const currentPrice = history[history.length - 1].price;
105
+ const oldPrice = history[0].price;
106
+ const change = currentPrice - oldPrice;
107
+ const percentChange = (change / oldPrice) * 100;
108
+
109
+ const result = {
110
+ ticker,
111
+ currentPrice: Number(currentPrice.toFixed(2)),
112
+ change: Number(change.toFixed(2)),
113
+ percentChange: Number(percentChange.toFixed(2)),
114
+ isPositive: change >= 0,
115
+ history
116
+ };
117
+
118
+ mockDataCache[ticker] = result;
119
+ return result;
120
+ };
src/data/mockData.js ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const mockPortfolios = {
2
+ Cautious: {
3
+ allocation: [
4
+ { name: 'Vanguard Total Bond (BND)', ticker: 'BND', value: 40, color: '#1E293B', shares: 145, dateBought: '2023-01-15', type: 'mf' },
5
+ { name: 'Vanguard Intl Bond (BNDX)', ticker: 'BNDX', value: 20, color: '#334155', shares: 85, dateBought: '2023-03-22', type: 'mf' },
6
+ { name: 'SPDR S&P 500 (SPY)', ticker: 'SPY', value: 15, color: '#C5A880', shares: 12, dateBought: '2022-11-10', type: 'mf' },
7
+ { name: 'Johnson & Johnson (JNJ)', ticker: 'JNJ', value: 15, color: '#64748B', shares: 35, dateBought: '2023-05-05', type: 'stock' },
8
+ { name: 'Cash Equivalents', ticker: 'CASH', value: 10, color: '#E5E7EB', shares: 2500, dateBought: '2024-01-01', type: 'stock' }
9
+ ],
10
+ riskLevel: 'Low',
11
+ expectedReturn: '4-5%',
12
+ feeImpact: 'Low',
13
+ hiddenFees: { expenseRatio: 0.05, advisoryFee: 0.0, tradingCosts: 2.0 }
14
+ },
15
+ Balanced: {
16
+ allocation: [
17
+ { name: 'Vanguard S&P 500 (VOO)', ticker: 'VOO', value: 30, color: '#0B233F', shares: 45, dateBought: '2022-08-14', type: 'mf' },
18
+ { name: 'Microsoft Corp (MSFT)', ticker: 'MSFT', value: 15, color: '#C5A880', shares: 25, dateBought: '2021-12-01', type: 'stock' },
19
+ { name: 'Apple Inc. (AAPL)', ticker: 'AAPL', value: 15, color: '#1E293B', shares: 60, dateBought: '2022-02-18', type: 'stock' },
20
+ { name: 'Vanguard Total Intl (VXUS)', ticker: 'VXUS', value: 15, color: '#64748B', shares: 90, dateBought: '2023-06-30', type: 'mf' },
21
+ { name: 'Berkshire Hathaway (BRK-B)', ticker: 'BRK-B', value: 10, color: '#334155', shares: 18, dateBought: '2022-05-12', type: 'stock' },
22
+ { name: 'Vanguard Total Bond (BND)', ticker: 'BND', value: 15, color: '#94A3B8', shares: 70, dateBought: '2023-11-20', type: 'mf' }
23
+ ],
24
+ riskLevel: 'Medium',
25
+ expectedReturn: '7-9%',
26
+ feeImpact: 'Medium',
27
+ hiddenFees: { expenseRatio: 0.04, advisoryFee: 0.0, tradingCosts: 10.0 }
28
+ },
29
+ Bold: {
30
+ allocation: [
31
+ { name: 'Invesco QQQ Trust (QQQ)', ticker: 'QQQ', value: 25, color: '#0B233F', shares: 35, dateBought: '2021-09-10', type: 'mf' },
32
+ { name: 'Tesla, Inc. (TSLA)', ticker: 'TSLA', value: 20, color: '#C5A880', shares: 40, dateBought: '2022-10-05', type: 'stock' },
33
+ { name: 'Amazon.com (AMZN)', ticker: 'AMZN', value: 15, color: '#1E293B', shares: 55, dateBought: '2023-02-28', type: 'stock' },
34
+ { name: 'Google (GOOGL)', ticker: 'GOOGL', value: 15, color: '#64748B', shares: 65, dateBought: '2023-04-14', type: 'stock' },
35
+ { name: 'JPMorgan Chase (JPM)', ticker: 'JPM', value: 15, color: '#334155', shares: 42, dateBought: '2022-07-22', type: 'stock' },
36
+ { name: 'Vanguard S&P 500 (VOO)', ticker: 'VOO', value: 10, color: '#94A3B8', shares: 15, dateBought: '2024-01-10', type: 'mf' }
37
+ ],
38
+ riskLevel: 'High',
39
+ expectedReturn: '12-15%',
40
+ feeImpact: 'High',
41
+ hiddenFees: { expenseRatio: 0.12, advisoryFee: 0.0, tradingCosts: 25.0 }
42
+ }
43
+ };
44
+
45
+ export const rebalancingScenarios = {
46
+ marketDrop: {
47
+ trigger: "Market Drop of 10%+",
48
+ title: "Market Correction Strategy",
49
+ advice: "Reallocate 5% from Bonds to Equities (Buy the Dip).",
50
+ explanation: "During a drop of 10% or more, high-quality stocks go 'on sale'. We automatically shift a small portion of your stable bond holdings into equities to capture the eventual recovery. This is a standard, disciplined approach to buy low and sell high without timing the market.",
51
+ feeImpactDollars: "$2.50 (ETF Bid/Ask Spread)",
52
+ taxImpact: "None (Done within tax-advantaged accounts if applicable)"
53
+ },
54
+ inflation: {
55
+ trigger: "High Inflation (4%+)",
56
+ title: "Inflation Protection Shift",
57
+ advice: "Increase allocation to Value Stocks and International Equities.",
58
+ explanation: "When inflation runs hot, growth stocks often suffer while value companies (with current cash flows) and international equities can provide better protection. This shift acts as a shield to preserve your real purchasing power.",
59
+ feeImpactDollars: "$1.20 (Rebalancing Costs)",
60
+ taxImpact: "Minimal (Loss harvesting applied where possible)"
61
+ },
62
+ withdrawal: {
63
+ trigger: "Major Life Expense",
64
+ title: "Capital Preservation Mode",
65
+ advice: "Shift 20% of Equities into Cash Equivalents and Short-Term Bonds.",
66
+ explanation: "When you have a major life event coming up (like buying a house), you cannot afford short-term market volatility. We lock in your gains by shifting to highly liquid, stable assets so your money is guaranteed to be there when you need it.",
67
+ feeImpactDollars: "$0.00 (Zero-fee transaction)",
68
+ taxImpact: "Moderate (Capital gains realized; tax-loss harvesting offset attempted)"
69
+ }
70
+ };
src/data/tickerMetrics.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const tickerMetrics = {
2
+ 'SPY': { divYield: 1.32, beta: 1.00, expenseRatio: 0.09, peRatio: 26.5 },
3
+ 'VOO': { divYield: 1.34, beta: 1.00, expenseRatio: 0.03, peRatio: 26.5 },
4
+ 'BND': { divYield: 3.25, beta: 0.12, expenseRatio: 0.03, peRatio: 0 },
5
+ 'BNDX': { divYield: 2.50, beta: 0.15, expenseRatio: 0.07, peRatio: 0 },
6
+ 'AAPL': { divYield: 0.48, beta: 1.24, expenseRatio: 0, peRatio: 31.2 },
7
+ 'MSFT': { divYield: 0.70, beta: 0.90, expenseRatio: 0, peRatio: 35.5 },
8
+ 'GOOGL': { divYield: 0, beta: 1.05, expenseRatio: 0, peRatio: 28.2 },
9
+ 'AMZN': { divYield: 0, beta: 1.15, expenseRatio: 0, peRatio: 42.1 },
10
+ 'JNJ': { divYield: 3.00, beta: 0.55, expenseRatio: 0, peRatio: 18.5 },
11
+ 'BRK-B': { divYield: 0, beta: 0.85, expenseRatio: 0, peRatio: 22.4 },
12
+ 'JPM': { divYield: 2.30, beta: 1.10, expenseRatio: 0, peRatio: 12.5 },
13
+ 'VXUS': { divYield: 3.12, beta: 1.05, expenseRatio: 0.07, peRatio: 15.4 },
14
+ 'TSLA': { divYield: 0, beta: 2.31, expenseRatio: 0, peRatio: 72.8 },
15
+ 'QQQ': { divYield: 0.61, beta: 1.18, expenseRatio: 0.20, peRatio: 35.1 },
16
+ 'VTI': { divYield: 1.35, beta: 1.00, expenseRatio: 0.03, peRatio: 25.8 },
17
+ 'CASH': { divYield: 4.50, beta: 0, expenseRatio: 0, peRatio: 0 },
18
+ };
src/index.css ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Outfit:wght@100..900&display=swap');
2
+
3
+ @tailwind base;
4
+ @tailwind components;
5
+ @tailwind utilities;
6
+
7
+ @layer base {
8
+ body {
9
+ @apply bg-gs-light text-gs-navy font-sans antialiased;
10
+ font-feature-settings: "cv02", "cv03", "cv04", "cv11";
11
+ }
12
+
13
+ h1, h2, h3, h4, h5, h6 {
14
+ font-family: 'Outfit', sans-serif;
15
+ }
16
+ }
17
+
18
+ /* Custom styles for animations or specific components if needed */
19
+ .glass-panel {
20
+ @apply bg-white/80 backdrop-blur-md border border-white/20 shadow-xl rounded-2xl;
21
+ }
src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.jsx'
5
+
6
+ createRoot(document.getElementById('root')).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
src/pages/Dashboard.jsx ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useMemo, useEffect } from 'react';
2
+ import { fetchRealData } from '../data/historicalData';
3
+ import { mockPortfolios } from '../data/mockData';
4
+ import { tickerMetrics } from '../data/tickerMetrics';
5
+ import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';
6
+ import Indicators from '../components/Indicators';
7
+ import FeeTransparencyModule from '../components/FeeTransparencyModule';
8
+ import RebalancingEngine from '../components/RebalancingEngine';
9
+ import TransparencyModal from '../components/TransparencyModal';
10
+ import MacroTracker from '../components/MacroTracker';
11
+ import StockPopup from '../components/StockPopup';
12
+ import PortfolioHeatmap from '../components/PortfolioHeatmap';
13
+ import FinancialCalculators from '../components/FinancialCalculators';
14
+ import { LayoutGrid, Plus } from 'lucide-react';
15
+ import { AnimatePresence } from 'framer-motion';
16
+ import StockSearch from '../components/StockSearch';
17
+
18
+ export default function Dashboard({ riskProfile }) {
19
+ // Initialize with a cached portfolio if available, otherwise an empty one
20
+ const [currentPortfolio, setCurrentPortfolio] = useState(() => {
21
+ const saved = localStorage.getItem('gs_portfolio');
22
+ if (saved) {
23
+ try {
24
+ return JSON.parse(saved);
25
+ } catch (e) {
26
+ console.error("Error loading saved portfolio:", e);
27
+ }
28
+ }
29
+ return {
30
+ ...mockPortfolios[riskProfile],
31
+ allocation: []
32
+ };
33
+ });
34
+
35
+ // Save to cache whenever portfolio changes
36
+ useEffect(() => {
37
+ localStorage.setItem('gs_portfolio', JSON.stringify(currentPortfolio));
38
+ }, [currentPortfolio]);
39
+ const [modalData, setModalData] = useState(null);
40
+
41
+ // State for popups
42
+ const [activePopupAsset, setActivePopupAsset] = useState(null);
43
+ const [showHeatmap, setShowHeatmap] = useState(false);
44
+ const [isSearchOpen, setIsSearchOpen] = useState(false);
45
+ const [totals, setTotals] = useState({ value: 0, change: 0, percent: 0, rawValue: 0, loading: true });
46
+ const [prices, setPrices] = useState({});
47
+
48
+ // Dynamic Calculation of Health, Risk, and Weights based on real metrics
49
+ const displayPortfolio = useMemo(() => {
50
+ let totalBeta = 0;
51
+ let totalExpense = 0;
52
+ const numAssets = currentPortfolio.allocation.length;
53
+
54
+ // Calculate current market total
55
+ let marketTotal = 0;
56
+ currentPortfolio.allocation.forEach(asset => {
57
+ const priceData = prices[asset.ticker] || { price: 0 };
58
+ marketTotal += asset.shares * priceData.price;
59
+ });
60
+
61
+ const updatedAllocation = currentPortfolio.allocation.map(asset => {
62
+ const priceData = prices[asset.ticker] || { price: 0, percent: 0 };
63
+ const marketVal = asset.shares * priceData.price;
64
+ const weight = marketTotal > 0 ? (marketVal / marketTotal) * 100 : 0;
65
+
66
+ const metrics = tickerMetrics[asset.ticker] || { beta: 1, expenseRatio: 0.1 };
67
+ totalBeta += (metrics.beta || 1) * (weight / 100);
68
+ totalExpense += (metrics.expenseRatio || 0.1) * (weight / 100);
69
+
70
+ return {
71
+ ...asset,
72
+ value: Number(weight.toFixed(2)),
73
+ dayChange: priceData.percent,
74
+ dollarChange: priceData.change
75
+ };
76
+ });
77
+
78
+ if (numAssets === 0) {
79
+ return {
80
+ ...currentPortfolio,
81
+ healthScore: 0,
82
+ riskLevel: 'None',
83
+ avgBeta: '0.00',
84
+ stockSplit: 0,
85
+ mfSplit: 0
86
+ };
87
+ }
88
+
89
+ // Determine Risk Level
90
+ let riskLevel = 'Medium';
91
+ if (totalBeta < 0.6) riskLevel = 'Low';
92
+ else if (totalBeta > 1.2) riskLevel = 'High';
93
+
94
+ // Determine Health Score (Base 100)
95
+ let healthScore = 100;
96
+ healthScore -= Math.min(totalExpense * 100 * 2, 20);
97
+ if (numAssets >= 5) healthScore += 5;
98
+
99
+ const profileMap = { 'Cautious': 'Low', 'Balanced': 'Medium', 'Bold': 'High' };
100
+ if (riskLevel !== profileMap[riskProfile]) healthScore -= 15;
101
+ healthScore = Math.min(Math.max(Math.round(healthScore), 0), 100);
102
+
103
+ // Calculate Asset Type Split
104
+ let stockWeight = 0;
105
+ let mfWeight = 0;
106
+ updatedAllocation.forEach(asset => {
107
+ if (asset.type === 'mf') mfWeight += asset.value;
108
+ else stockWeight += asset.value;
109
+ });
110
+
111
+ return {
112
+ ...currentPortfolio,
113
+ allocation: updatedAllocation,
114
+ healthScore,
115
+ riskLevel,
116
+ avgBeta: totalBeta.toFixed(2),
117
+ stockSplit: stockWeight,
118
+ mfSplit: mfWeight
119
+ };
120
+ }, [currentPortfolio, riskProfile, prices]);
121
+
122
+ const handleRebalance = (scenario) => {
123
+ setModalData(scenario);
124
+ };
125
+
126
+ const closeModal = () => setModalData(null);
127
+ const closeStockPopup = () => setActivePopupAsset(null);
128
+
129
+ const handleAddStock = (newAsset) => {
130
+ setCurrentPortfolio(prev => {
131
+ // If portfolio is empty, the first stock gets 100% allocation
132
+ if (prev.allocation.length === 0) {
133
+ return {
134
+ ...prev,
135
+ allocation: [{ ...newAsset, value: 100 }]
136
+ };
137
+ }
138
+
139
+ const scale = (100 - newAsset.value) / 100;
140
+ const updatedExisting = prev.allocation.map(a => ({
141
+ ...a,
142
+ value: Number((a.value * scale).toFixed(2))
143
+ }));
144
+
145
+ // Ensure sum is exactly 100
146
+ const currentSum = updatedExisting.reduce((acc, a) => acc + a.value, 0) + newAsset.value;
147
+ if (currentSum !== 100 && updatedExisting.length > 0) {
148
+ updatedExisting[0].value += Number((100 - currentSum).toFixed(2));
149
+ }
150
+
151
+ return {
152
+ ...prev,
153
+ allocation: [...updatedExisting, newAsset]
154
+ };
155
+ });
156
+ };
157
+
158
+ useEffect(() => {
159
+ async function calculateTotals() {
160
+ if (currentPortfolio.allocation.length === 0) {
161
+ setTotals({ value: '$0.00', change: '$0.00', percent: '0.00', isPositive: true, loading: false });
162
+ return;
163
+ }
164
+
165
+ setTotals(prev => ({ ...prev, loading: true }));
166
+
167
+ const pricePromises = currentPortfolio.allocation.map(asset => fetchRealData(asset.ticker));
168
+ const results = await Promise.all(pricePromises);
169
+
170
+ let newTotalValue = 0;
171
+ let newTotalChange = 0;
172
+ const priceMap = {};
173
+
174
+ results.forEach((data, index) => {
175
+ const ticker = currentPortfolio.allocation[index].ticker;
176
+ priceMap[ticker] = {
177
+ price: data.currentPrice,
178
+ change: data.change,
179
+ percent: (data.change / (data.currentPrice - data.change)) * 100
180
+ };
181
+ newTotalValue += currentPortfolio.allocation[index].shares * data.currentPrice;
182
+ newTotalChange += (currentPortfolio.allocation[index].shares * data.change);
183
+ });
184
+
185
+ setPrices(priceMap);
186
+
187
+ const percent = newTotalValue > 0 ? (newTotalChange / (newTotalValue - newTotalChange)) * 100 : 0;
188
+
189
+ // Update totals
190
+ setTotals({
191
+ value: newTotalValue.toLocaleString('en-US', { style: 'currency', currency: 'USD' }),
192
+ change: newTotalChange.toLocaleString('en-US', { style: 'currency', currency: 'USD' }),
193
+ percent: percent.toFixed(2),
194
+ rawValue: newTotalValue,
195
+ isPositive: newTotalChange >= 0,
196
+ loading: false
197
+ });
198
+ }
199
+ calculateTotals();
200
+ }, [JSON.stringify(currentPortfolio.allocation.map(a => `${a.ticker}-${a.shares}`))]); // Only watch ticker/shares, not the derived weights changes
201
+
202
+ return (
203
+ <div className="min-h-screen bg-gs-light p-6 md:p-12 relative">
204
+ <div className="max-w-7xl mx-auto">
205
+ <header className="mb-10 flex flex-col md:flex-row justify-between items-start md:items-end gap-6">
206
+ <div>
207
+ <h1 className="text-3xl md:text-4xl font-light text-gs-navy mb-2">
208
+ Your <span className="font-semibold">{riskProfile}</span> Portfolio
209
+ </h1>
210
+ <p className="text-gs-slate text-lg font-light">
211
+ Built for your goals. Transparently managed.
212
+ </p>
213
+ </div>
214
+
215
+ <div className="bg-white px-8 py-4 rounded-2xl border border-gray-100 shadow-sm flex items-center gap-8 min-w-[320px]">
216
+ <div>
217
+ <p className="text-[10px] font-bold text-gs-slate uppercase tracking-widest mb-1">Total Value</p>
218
+ {totals.loading ? (
219
+ <div className="h-8 w-24 bg-gray-100 animate-pulse rounded"></div>
220
+ ) : (
221
+ <p className="text-2xl font-bold text-gs-navy">{totals.value}</p>
222
+ )}
223
+ </div>
224
+ <div className="h-10 w-px bg-gray-100"></div>
225
+ <div>
226
+ <p className="text-[10px] font-bold text-gs-slate uppercase tracking-widest mb-1">Day Change</p>
227
+ {totals.loading ? (
228
+ <div className="h-8 w-24 bg-gray-100 animate-pulse rounded"></div>
229
+ ) : (
230
+ <p className={`text-lg font-bold ${totals.isPositive ? 'text-green-600' : 'text-red-500'}`}>
231
+ {totals.isPositive ? '+' : ''}{totals.change}
232
+ <span className="text-xs ml-1 font-medium">({totals.isPositive ? '+' : ''}{totals.percent}%)</span>
233
+ </p>
234
+ )}
235
+ </div>
236
+ </div>
237
+ </header>
238
+
239
+ {/* Top Section: Allocation */}
240
+ <div className="bg-white rounded-2xl p-8 shadow-sm border border-gray-100 flex flex-col md:flex-row items-center mb-8">
241
+ <div className="w-full md:w-1/2 h-64">
242
+ <ResponsiveContainer width="100%" height="100%">
243
+ <PieChart>
244
+ <Pie
245
+ data={displayPortfolio.allocation}
246
+ cx="50%"
247
+ cy="50%"
248
+ innerRadius={80}
249
+ outerRadius={110}
250
+ paddingAngle={2}
251
+ dataKey="value"
252
+ nameKey="name"
253
+ stroke="none"
254
+ >
255
+ {displayPortfolio.allocation.map((entry, index) => (
256
+ <Cell key={`cell-${index}`} fill={entry.color} />
257
+ ))}
258
+ </Pie>
259
+ <Tooltip
260
+ formatter={(value, name) => [`${value}%`, name]}
261
+ contentStyle={{ borderRadius: '12px', border: 'none', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)' }}
262
+ />
263
+ </PieChart>
264
+ </ResponsiveContainer>
265
+ </div>
266
+ <div className="w-full md:w-1/2 space-y-4">
267
+ <div className="flex justify-between items-center mb-4">
268
+ <h3 className="text-xl font-medium text-gs-navy">Current Allocation</h3>
269
+ <div className="flex gap-2">
270
+ <button
271
+ onClick={() => setIsSearchOpen(true)}
272
+ className="bg-gs-navy text-white p-1.5 rounded-lg hover:bg-gs-gold hover:text-gs-navy transition-all shadow-sm"
273
+ title="Add Asset"
274
+ >
275
+ <Plus size={18} />
276
+ </button>
277
+ <button
278
+ onClick={() => setShowHeatmap(true)}
279
+ className="text-gs-slate hover:text-gs-gold transition-colors p-1"
280
+ title="View Heatmap"
281
+ >
282
+ <LayoutGrid size={18} />
283
+ </button>
284
+ </div>
285
+ </div>
286
+
287
+ {/* Asset Type Split Indicator */}
288
+ <div className="mb-6 bg-gs-light/30 p-4 rounded-xl border border-gray-100">
289
+ <div className="flex justify-between text-[10px] font-bold text-gs-slate uppercase tracking-widest mb-2">
290
+ <span>Stocks ({displayPortfolio.stockSplit.toFixed(2)}%)</span>
291
+ <span>Mutual Funds ({displayPortfolio.mfSplit.toFixed(2)}%)</span>
292
+ </div>
293
+ <div className="h-2 w-full bg-gray-200 rounded-full overflow-hidden flex">
294
+ <div
295
+ className="h-full bg-gs-navy transition-all duration-1000"
296
+ style={{ width: `${displayPortfolio.stockSplit}%` }}
297
+ ></div>
298
+ <div
299
+ className="h-full bg-gs-gold transition-all duration-1000"
300
+ style={{ width: `${displayPortfolio.mfSplit}%` }}
301
+ ></div>
302
+ </div>
303
+ </div>
304
+
305
+ <p className="text-xs text-gs-slate mb-3 italic">Click an asset to view historical performance and AI analysis.</p>
306
+ <div className="max-h-60 overflow-y-auto pr-2">
307
+ {displayPortfolio.allocation.length > 0 ? (
308
+ displayPortfolio.allocation.map((asset, idx) => (
309
+ <button
310
+ key={idx}
311
+ onClick={() => {
312
+ if (asset.ticker) {
313
+ setActivePopupAsset({ ticker: asset.ticker, name: asset.name });
314
+ }
315
+ }}
316
+ className="w-full text-left flex justify-between items-center p-3 rounded-lg transition-colors mb-2 bg-gs-light/50 hover:bg-gray-100 hover:shadow-sm border border-transparent hover:border-gray-200 group"
317
+ >
318
+ <div className="flex items-center justify-between w-full">
319
+ <div className="flex items-center">
320
+ <div className="w-4 h-4 rounded-full mr-3 shadow-sm" style={{ backgroundColor: asset.color }}></div>
321
+ <div className="flex flex-col items-start">
322
+ <span className="text-gs-slate font-medium text-sm group-hover:text-gs-navy transition-colors">{asset.name}</span>
323
+ <div className="flex items-center gap-2 mt-0.5">
324
+ <span className="text-[10px] text-gray-400 font-medium">{asset.shares} shares</span>
325
+ {asset.dayChange !== undefined && (
326
+ <span className={`text-[10px] font-bold ${asset.dayChange >= 0 ? 'text-green-600' : 'text-red-500'}`}>
327
+ {asset.dayChange >= 0 ? '+' : ''}${Math.abs(asset.dollarChange || 0).toFixed(2)} ({asset.dayChange >= 0 ? '▲' : '▼'} {Math.abs(asset.dayChange).toFixed(2)}%)
328
+ </span>
329
+ )}
330
+ </div>
331
+ </div>
332
+ </div>
333
+ <span className="font-bold text-gs-navy text-sm ml-4">{asset.value.toFixed(2)}%</span>
334
+ </div>
335
+ </button>
336
+ ))
337
+ ) : (
338
+ <div className="text-center py-12 bg-gs-light/20 rounded-2xl border-2 border-dashed border-gray-200">
339
+ <p className="text-gs-slate text-sm font-light mb-6 italic">Your portfolio is currently empty.</p>
340
+ <button
341
+ onClick={() => setIsSearchOpen(true)}
342
+ className="inline-flex items-center gap-2 px-8 py-3 bg-gs-navy text-white rounded-xl hover:bg-gs-gold hover:text-gs-navy transition-all shadow-lg font-bold"
343
+ >
344
+ <Plus size={18} />
345
+ Build Your Portfolio
346
+ </button>
347
+ </div>
348
+ )}
349
+ </div>
350
+ </div>
351
+ </div>
352
+
353
+ {/* Investment & Retirement Planning Section */}
354
+ <FinancialCalculators />
355
+
356
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-8">
357
+ {/* Left Column: Indicators */}
358
+ <div className="lg:col-span-2 space-y-8">
359
+ <Indicators portfolio={displayPortfolio} />
360
+ <FeeTransparencyModule portfolio={displayPortfolio} />
361
+ </div>
362
+
363
+ {/* Right Column: Rebalancing Engine */}
364
+ <div className="space-y-8">
365
+ <RebalancingEngine onScenarioSelect={handleRebalance} />
366
+ </div>
367
+ </div>
368
+
369
+ {/* Bottom Section: MacroTracker */}
370
+ <MacroTracker />
371
+ </div>
372
+
373
+ <TransparencyModal
374
+ isOpen={!!modalData}
375
+ onClose={closeModal}
376
+ data={modalData}
377
+ currentPortfolio={displayPortfolio}
378
+ totalValue={totals.rawValue}
379
+ prices={prices}
380
+ />
381
+
382
+ <StockPopup
383
+ ticker={activePopupAsset?.ticker}
384
+ assetName={activePopupAsset?.name}
385
+ onClose={closeStockPopup}
386
+ />
387
+
388
+ <AnimatePresence>
389
+ {showHeatmap && (
390
+ <PortfolioHeatmap
391
+ onClose={() => setShowHeatmap(false)}
392
+ allocation={displayPortfolio.allocation}
393
+ />
394
+ )}
395
+ </AnimatePresence>
396
+
397
+ <AnimatePresence>
398
+ {isSearchOpen && (
399
+ <StockSearch
400
+ isOpen={isSearchOpen}
401
+ onClose={() => setIsSearchOpen(false)}
402
+ onAddStock={handleAddStock}
403
+ currentPortfolio={displayPortfolio}
404
+ totalValue={totals.value}
405
+ />
406
+ )}
407
+ </AnimatePresence>
408
+ </div>
409
+ );
410
+ }
src/pages/LandingPage.jsx ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import { ShieldCheck, TrendingUp, Zap } from 'lucide-react';
5
+
6
+ const questions = [
7
+ {
8
+ id: 1,
9
+ text: "What are you saving for?",
10
+ options: [
11
+ { label: "A major purchase soon (house, car)", score: 1 },
12
+ { label: "General wealth building", score: 2 },
13
+ { label: "Retirement (20+ years away)", score: 3 }
14
+ ]
15
+ },
16
+ {
17
+ id: 2,
18
+ text: "How do you feel about sudden market drops?",
19
+ options: [
20
+ { label: "I'd panic and want to sell", score: 1 },
21
+ { label: "I'd be concerned but hold on", score: 2 },
22
+ { label: "I'd see it as a buying opportunity", score: 3 }
23
+ ]
24
+ },
25
+ {
26
+ id: 3,
27
+ text: "What is your primary investment goal?",
28
+ options: [
29
+ { label: "Protect my money from losing value", score: 1 },
30
+ { label: "Steady, moderate growth over time", score: 2 },
31
+ { label: "Maximum growth, regardless of ups and downs", score: 3 }
32
+ ]
33
+ }
34
+ ];
35
+
36
+ export default function LandingPage({ setRiskProfile }) {
37
+ const [currentStep, setCurrentStep] = useState(0);
38
+ const [totalScore, setTotalScore] = useState(0);
39
+ const navigate = useNavigate();
40
+
41
+ const handleOptionSelect = (score) => {
42
+ setTotalScore(prev => prev + score);
43
+ if (currentStep < questions.length - 1) {
44
+ setCurrentStep(prev => prev + 1);
45
+ } else {
46
+ determineProfile(totalScore + score);
47
+ }
48
+ };
49
+
50
+ const determineProfile = (finalScore) => {
51
+ let profile = 'Balanced';
52
+ if (finalScore <= 4) profile = 'Cautious';
53
+ if (finalScore >= 8) profile = 'Bold';
54
+
55
+ setRiskProfile(profile);
56
+ navigate('/dashboard');
57
+ };
58
+
59
+ return (
60
+ <div className="min-h-screen bg-gs-navy flex flex-col items-center justify-center p-6 relative overflow-hidden">
61
+ {/* Decorative background elements */}
62
+ <div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-gs-gold/20 rounded-full blur-[120px]"></div>
63
+ <div className="absolute bottom-[-10%] right-[-10%] w-[50%] h-[50%] bg-blue-500/10 rounded-full blur-[150px]"></div>
64
+
65
+ <div className="z-10 text-center mb-12">
66
+ <h1 className="text-4xl md:text-5xl font-light text-white mb-4 tracking-tight">
67
+ Wealth management, <span className="text-gs-gold font-normal">simplified.</span>
68
+ </h1>
69
+ <p className="text-gs-light/70 text-lg md:text-xl max-w-xl mx-auto font-light">
70
+ No jargon. No hidden fees. Just clear strategies tailored to your goals. Let's find out what kind of investor you are.
71
+ </p>
72
+ </div>
73
+
74
+ <div className="z-10 w-full max-w-2xl bg-white/10 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl p-8 md:p-12">
75
+ <div className="flex justify-between mb-8">
76
+ {questions.map((q, idx) => (
77
+ <div key={q.id} className="flex-1 flex items-center">
78
+ <div className={`h-2 w-full rounded-full transition-all duration-500 ${idx <= currentStep ? 'bg-gs-gold' : 'bg-white/20'}`}></div>
79
+ {idx < questions.length - 1 && <div className="w-4"></div>}
80
+ </div>
81
+ ))}
82
+ </div>
83
+
84
+ <AnimatePresence mode="wait">
85
+ <motion.div
86
+ key={currentStep}
87
+ initial={{ opacity: 0, x: 20 }}
88
+ animate={{ opacity: 1, x: 0 }}
89
+ exit={{ opacity: 0, x: -20 }}
90
+ transition={{ duration: 0.3 }}
91
+ >
92
+ <h2 className="text-2xl text-white font-medium mb-6 text-center">
93
+ {questions[currentStep].text}
94
+ </h2>
95
+ <div className="space-y-4">
96
+ {questions[currentStep].options.map((option, idx) => (
97
+ <button
98
+ key={idx}
99
+ onClick={() => handleOptionSelect(option.score)}
100
+ className="w-full text-left px-6 py-4 rounded-xl bg-white/5 border border-white/10 hover:bg-gs-gold/20 hover:border-gs-gold/50 transition-all duration-300 text-white font-light flex items-center group"
101
+ >
102
+ <div className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center mr-4 group-hover:bg-gs-gold/30 group-hover:text-gs-gold transition-colors">
103
+ {idx === 0 && <ShieldCheck size={18} />}
104
+ {idx === 1 && <TrendingUp size={18} />}
105
+ {idx === 2 && <Zap size={18} />}
106
+ </div>
107
+ {option.label}
108
+ </button>
109
+ ))}
110
+ </div>
111
+ </motion.div>
112
+ </AnimatePresence>
113
+ </div>
114
+ </div>
115
+ );
116
+ }
tailwind.config.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ 'gs-navy': '#0B233F',
11
+ 'gs-slate': '#2C3E50',
12
+ 'gs-gold': '#C5A880',
13
+ 'gs-light': '#F8F9FA',
14
+ },
15
+ fontFamily: {
16
+ sans: ['Inter', 'sans-serif'],
17
+ }
18
+ },
19
+ },
20
+ plugins: [],
21
+ }
vite.config.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ server: {
8
+ proxy: {
9
+ '/api/yahoo': {
10
+ target: 'https://query1.finance.yahoo.com',
11
+ changeOrigin: true,
12
+ rewrite: (path) => path.replace(/^\/api\/yahoo/, '')
13
+ }
14
+ }
15
+ }
16
+ })