Aziz30 commited on
Commit
f768220
·
verified ·
1 Parent(s): dedb874

Upload 18 files

Browse files
.gitattributes CHANGED
@@ -35,3 +35,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  Projet4A_PredictionsBoursieres-main/Modèle[[:space:]]IA/global_lstm_returns.keras filter=lfs diff=lfs merge=lfs -text
37
  Projet4A_PredictionsBoursieres-main/Modèle[[:space:]]IA/global_return_lstm.keras filter=lfs diff=lfs merge=lfs -text
 
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  Projet4A_PredictionsBoursieres-main/Modèle[[:space:]]IA/global_lstm_returns.keras filter=lfs diff=lfs merge=lfs -text
37
  Projet4A_PredictionsBoursieres-main/Modèle[[:space:]]IA/global_return_lstm.keras filter=lfs diff=lfs merge=lfs -text
38
+ Modèle[[:space:]]IA/global_lstm_returns.keras filter=lfs diff=lfs merge=lfs -text
Data/ALL_CLEANED.csv ADDED
The diff for this file is too large to render. See raw diff
 
Data/ALL_FEATURES.csv ADDED
The diff for this file is too large to render. See raw diff
 
Data/data_report.csv ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ symbol,start_date,end_date,days_count,last_price,last_volume,update_time
2
+ AAPL,1980-12-12,2025-12-10,11341,278.1700134277344,12514521,2025-12-10 18:34:31
3
+ TSLA,2010-06-29,2025-12-10,3888,445.5950012207031,28317579,2025-12-10 18:34:31
4
+ MSFT,1986-03-13,2025-12-10,10015,477.1300048828125,14440362,2025-12-10 18:34:31
5
+ BTC-USD,2014-09-17,2025-12-10,4103,92234.1796875,55508463616,2025-12-10 18:34:31
6
+ GOOGL,2004-08-19,2025-12-10,5363,316.07000732421875,15150934,2025-12-10 18:34:31
7
+ NVDA,1999-01-22,2025-12-10,6764,182.6300048828125,79020963,2025-12-10 18:34:31
8
+ AMZN,1997-05-15,2025-12-10,7189,230.2949981689453,17906775,2025-12-10 18:34:31
9
+ META,2012-05-18,2025-12-10,3411,644.72998046875,7253473,2025-12-10 18:34:31
Interface Graphique/app.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dash
2
+ from dash import dcc, html, Input, Output
3
+ import yfinance as yf
4
+
5
+ # === INITIALISATION ===
6
+ app = dash.Dash(__name__,use_pages=True, suppress_callback_exceptions=True, external_stylesheets=[ "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" ])
7
+ app.title = "TradeLux - Plateforme de Trading"
8
+
9
+ # === TICKERS ===
10
+ TICKERS = {
11
+ "BTC-USD": "BTC/USD",
12
+ "ETH-USD": "ETH/USD",
13
+ "^IXIC": "NASDAQ",
14
+ "AAPL": "AAPL",
15
+ "GOOGL": "GOOGL"
16
+ }
17
+
18
+ # === RÉCUPÉRATION DONNÉES ===
19
+ def fetch_ticker_data():
20
+ data = []
21
+ for symbol, label in TICKERS.items():
22
+ try:
23
+ stock = yf.Ticker(symbol)
24
+ hist = stock.history(period="1d", interval="1m")
25
+ if len(hist) >= 2:
26
+ current = hist['Close'].iloc[-1]
27
+ prev = hist['Close'].iloc[-2]
28
+ change = (current - prev) / prev * 100
29
+ change_str = f"up {change:.2f}%" if change > 0 else f"down {abs(change):.2f}%"
30
+ change_class = "up" if change > 0 else "down"
31
+ data.append({"label": label, "value": f"{current:,.2f}", "change": change_str, "class": change_class})
32
+ else:
33
+ data.append({"label": label, "value": "N/A", "change": "down 0.0%", "class": "down"})
34
+ except:
35
+ data.append({"label": label, "value": "ERR", "change": "down 0.0%", "class": "down"})
36
+ return data
37
+
38
+ # === LAYOUT COMPLET (TOUT DEDANS) ===
39
+ app.layout = html.Div([
40
+ # Fond animé
41
+ html.Div(className="trade-bg"),
42
+ html.Div(className="grid-lines"),
43
+ html.Div([html.Div(className="particle") for _ in range(40)]),
44
+
45
+ # === OBLIGATOIRE : dcc.Interval + dcc.Location ===
46
+ dcc.Location(id="url", refresh=False),
47
+ dcc.Interval(id="interval-component", interval=5*60*1000, n_intervals=0, disabled=True),
48
+
49
+ # Ticker
50
+ html.Div(id="ticker-container"),
51
+
52
+ # Navbar
53
+ html.Div(id="navbar", className="navbar", children=[
54
+ html.Div(className="navbar-left", children=[
55
+ html.Img(src="/assets/logo.png", className="logo", alt="Logo")
56
+ ]),
57
+ html.Div(className="nav-links", children=[
58
+ dcc.Link("Accueil", href="/", className="nav-link"),
59
+ dcc.Link("Marchés", href="/actions_page", className="nav-link"),
60
+ dcc.Link("Analyse", href="/data", className="nav-link"),
61
+ html.A("Contact", href="#contact-section", className="nav-link", **{"data-scroll": ""}),
62
+ ]),
63
+ ]),
64
+
65
+ # Contenu principal avec animation
66
+ html.Div(id="page-content", className="page-content", children=[
67
+ html.Div(id="page-transition", children=dash.page_container)
68
+ ])
69
+ ])
70
+
71
+ # === CALLBACK PRINCIPAL : TOUT CONTRÔLE ===
72
+ @app.callback(
73
+ Output("ticker-container", "children"),
74
+ Output("interval-component", "disabled"),
75
+ Output("navbar", "className"),
76
+ Output("page-content", "className"),
77
+ Output("page-transition", "className"),
78
+ Input("url", "pathname")
79
+ )
80
+ def control_layout(pathname):
81
+ if pathname == "/":
82
+ return (
83
+ html.Div(className="ticker-wrap", children=[
84
+ html.Div(id="ticker-inner", className="ticker-inner")
85
+ ]),
86
+ False, # interval activé
87
+ "navbar with-ticker",
88
+ "page-content with-ticker",
89
+ "page-fade-in"
90
+ )
91
+ else:
92
+ return (
93
+ "", # pas de ticker
94
+ True, # interval désactivé
95
+ "navbar no-ticker",
96
+ "page-content no-ticker",
97
+ "page-fade-in"
98
+ )
99
+
100
+ # === CALLBACK TICKER : FLUIDE INFINI ===
101
+ @app.callback(
102
+ Output("ticker-inner", "children"),
103
+ Input("interval-component", "n_intervals")
104
+ )
105
+ def update_ticker(n):
106
+ data = fetch_ticker_data()
107
+ items = [
108
+ html.Div(className="ticker-item", children=[
109
+ html.Span(d["label"]),
110
+ html.Span(className="ticker-value", children=d["value"]),
111
+ html.Span(className=f"ticker-change {d['class']}", children=d["change"])
112
+ ])
113
+ for symbol, d in zip(TICKERS.keys(), data)
114
+ ]
115
+ ticker_set = html.Div(className="ticker-set", children=items)
116
+ return [ticker_set, ticker_set] # 2x → boucle parfaite
117
+
118
+ # === LANCEMENT ===
119
+ if __name__ == "__main__":
120
+ app.run( port=7860, debug=True)
Interface Graphique/assets/logo.png ADDED
Interface Graphique/assets/style.css ADDED
@@ -0,0 +1,1038 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ---------------------------
2
+ THEME
3
+ --------------------------- */
4
+
5
+ /* Polices */
6
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;500;700;900&display=swap');
7
+
8
+ /* Variables */
9
+ :root {
10
+ --bg: #010214;
11
+ --panel: rgba(255,255,255,0.02);
12
+ --accent: #00f0ff;
13
+ --accent-2: #8be9ff;
14
+ --glass-border: rgba(140,220,255,0.08);
15
+ --glass-shadow: rgba(0,240,255,0.05);
16
+ --muted: #b8dff0;
17
+ }
18
+
19
+ /* Global */
20
+ * { box-sizing: border-box; }
21
+ body {
22
+ margin: 0;
23
+ font-family: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
24
+ background: var(--bg);
25
+ color: #eaf6ff;
26
+ -webkit-font-smoothing: antialiased;
27
+ -moz-osx-font-smoothing: grayscale;
28
+ overflow-x: hidden;
29
+ }
30
+
31
+ /* BACKGROUND ANIMÉ */
32
+ .trade-bg {
33
+ position: fixed; inset: 0; z-index: -20;
34
+ background: radial-gradient(circle at 20% 20%, #0c0f2a, #010214 80%);
35
+ overflow: hidden;
36
+ }
37
+
38
+ .grid-lines {
39
+ position: fixed; inset: 0; z-index: -19;
40
+ background-image:
41
+ linear-gradient(0deg, rgba(0,255,255,0.02) 1px, transparent 1px),
42
+ linear-gradient(90deg, rgba(0,255,255,0.01) 1px, transparent 1px);
43
+ background-size: 300px 300px, 300px 300px;
44
+ animation: gridShift 40s linear infinite;
45
+ opacity: 0.6;
46
+ }
47
+ @keyframes gridShift { from { transform: translate(0,0); } to { transform: translate(-200px,-200px); } }
48
+
49
+ /* PARTICULES */
50
+ .particle {
51
+ position: absolute;
52
+ width: 3px; height: 3px; border-radius: 50%;
53
+ background: linear-gradient(180deg, var(--accent), var(--accent-2));
54
+ opacity: 0.8;
55
+ animation: floatUp 12s linear infinite;
56
+ }
57
+ @keyframes floatUp {
58
+ 0% { transform: translateY(0) scale(1); opacity: 0.8; }
59
+ 50% { transform: translateY(-80vh) scale(1.3); opacity: 1; }
60
+ 100% { transform: translateY(0) scale(1); opacity: 0.5; }
61
+ }
62
+
63
+ /* ====================== TICKER ULTRA-FLUIDE INFINI ====================== */
64
+ .ticker-wrap {
65
+ position: fixed;
66
+ top: 0; left: 0; right: 0;
67
+ height: 44px;
68
+ overflow: hidden;
69
+ z-index: 1000;
70
+ pointer-events: none;
71
+ background: linear-gradient(90deg, rgba(0,10,18,0.8), rgba(0,10,18,0.6));
72
+ backdrop-filter: blur(8px);
73
+ border-bottom: 1px solid rgba(0,240,255,0.1);
74
+ }
75
+
76
+ .ticker-inner {
77
+ display: flex;
78
+ animation: scroll-left 20s linear infinite;
79
+ gap: 30px;
80
+ will-change: transform;
81
+ }
82
+
83
+ .ticker-set {
84
+ display: flex;
85
+ gap: 30px;
86
+ flex-shrink: 0;
87
+ }
88
+
89
+ .ticker-item {
90
+ display: flex;
91
+ gap: 12px;
92
+ align-items: center;
93
+ padding: 6px 18px;
94
+ background: rgba(0,10,18,0.7);
95
+ border-radius: 10px;
96
+ border: 1px solid rgba(0,240,255,0.1);
97
+ box-shadow: 0 4px 15px rgba(0,240,255,0.08);
98
+ backdrop-filter: blur(4px);
99
+ transition: all 0.3s ease;
100
+ white-space: nowrap;
101
+ }
102
+
103
+ .ticker-item:hover {
104
+ transform: translateY(-2px) scale(1.05);
105
+ box-shadow: 0 8px 25px rgba(0,240,255,0.2);
106
+ border-color: rgba(0,240,255,0.3);
107
+ }
108
+
109
+ .ticker-value {
110
+ font-weight: 700;
111
+ color: #e6ffff;
112
+ font-size: 1rem;
113
+ }
114
+
115
+ .ticker-change {
116
+ font-weight: 600;
117
+ font-size: 0.9rem;
118
+ }
119
+
120
+ .ticker-change.up { color: #7cffb2; text-shadow: 0 0 8px rgba(124,255,178,0.4); }
121
+ .ticker-change.down { color: #ff7b7b; text-shadow: 0 0 8px rgba(255,123,123,0.3); }
122
+
123
+ @keyframes scroll-left {
124
+ 0% { transform: translateX(0); }
125
+ 100% { transform: translateX(-50%); }
126
+ }
127
+
128
+ /* ====================== NAVBAR DYNAMIQUE ====================== */
129
+ .navbar {
130
+ position: fixed;
131
+ left: 0; right: 0;
132
+ z-index: 2000;
133
+ display: flex;
134
+ justify-content: space-between;
135
+ align-items: center;
136
+ padding: 14px 48px;
137
+ gap: 20px;
138
+ background: linear-gradient(180deg, rgba(2,8,18,0.6), rgba(2,8,18,0.35));
139
+ border-bottom: 1px solid rgba(140,220,255,0.03);
140
+ backdrop-filter: blur(8px);
141
+ box-shadow: 0 8px 30px rgba(0,0,0,0.6);
142
+ transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
143
+ }
144
+
145
+ .navbar.with-ticker { top: 44px; }
146
+ .navbar.no-ticker { top: 0; border-top: 1px solid rgba(140,220,255,0.03); }
147
+
148
+ .navbar .logo { height: 52px; filter: drop-shadow(0 4px 18px rgba(0,240,255,0.06)); }
149
+
150
+ .nav-links {
151
+ display: flex;
152
+ gap: 28px;
153
+ align-items: center;
154
+ }
155
+
156
+ .nav-link {
157
+ color: var(--muted);
158
+ text-decoration: none;
159
+ font-weight: 600;
160
+ letter-spacing: 0.6px;
161
+ padding: 6px 4px;
162
+ position: relative;
163
+ transition: all 0.3s ease;
164
+ }
165
+
166
+ .nav-link::after {
167
+ content: "";
168
+ position: absolute;
169
+ left: 0; right: 0; bottom: -6px;
170
+ height: 2px; width: 0%;
171
+ background: linear-gradient(90deg, var(--accent), var(--accent-2));
172
+ transition: width 0.28s ease;
173
+ }
174
+
175
+ .nav-link:hover {
176
+ color: var(--accent-2);
177
+ transform: translateY(-1px);
178
+ }
179
+
180
+ .nav-link:hover::after { width: 100%; }
181
+
182
+ /* ====================== PAGE CONTENT ====================== */
183
+ .page-content {
184
+ position: relative;
185
+ z-index: 1000;
186
+ max-width: 1200px;
187
+ margin: 0 auto;
188
+ transition: padding-top 0.4s ease;
189
+ }
190
+
191
+ .page-content:has(.actions-page) {
192
+ max-width: none;
193
+ margin: 0;
194
+ padding-left: 0;
195
+ padding-right: 0;
196
+ }
197
+
198
+ .page-content.with-ticker { padding-top: 140px; }
199
+ .page-content.no-ticker { padding-top: 96px; }
200
+
201
+ /* ====================== HERO - TITRE SANS FOND + PLUS D'ESPACE ====================== */
202
+ .hero {
203
+ text-align: center;
204
+ padding: 200px 20px 100px;
205
+ margin: 0;
206
+ background: transparent !important;
207
+ position: relative;
208
+ z-index: 100;
209
+ animation: heroFade 2s ease forwards;
210
+ }
211
+
212
+ .hero h1 {
213
+ font-size: 4.3rem;
214
+ font-weight: 900;
215
+ background: linear-gradient(90deg, var(--accent), var(--accent-2), var(--accent));
216
+ -webkit-background-clip: text;
217
+ background-clip: text;
218
+ -webkit-text-fill-color: transparent;
219
+ text-shadow:
220
+ 0 0 20px rgba(0,240,255,0.6),
221
+ 0 0 40px rgba(0,240,255,0.4),
222
+ 0 0 80px rgba(0,240,255,0.2);
223
+ margin: 0 0 28px;
224
+ letter-spacing: 1.6px;
225
+ animation: titlePulse 3s ease-in-out infinite alternate;
226
+ }
227
+
228
+ @keyframes titlePulse {
229
+ from {
230
+ text-shadow: 0 0 20px rgba(0,240,255,0.6), 0 0 40px rgba(0,240,255,0.4), 0 0 80px rgba(0,240,255,0.2);
231
+ }
232
+ to {
233
+ text-shadow: 0 0 30px rgba(0,240,255,0.8), 0 0 60px rgba(0,240,255,0.6), 0 0 100px rgba(0,240,255,0.4);
234
+ }
235
+ }
236
+
237
+ .hero .neon-underline {
238
+ height: 6px;
239
+ width: 200px;
240
+ background: var(--accent);
241
+ margin: 0 auto 40px;
242
+ border-radius: 3px;
243
+ box-shadow:
244
+ 0 0 25px var(--accent),
245
+ 0 0 50px rgba(0,240,255,0.8),
246
+ 0 0 70px rgba(0,240,255,0.5);
247
+ animation: pulseGlow 1.6s ease-in-out infinite alternate;
248
+ }
249
+
250
+ .hero p {
251
+ color: #bfeeff;
252
+ font-size: 1.25rem;
253
+ font-weight: 500;
254
+ max-width: 850px;
255
+ margin: 0 auto 50px;
256
+ line-height: 1.8;
257
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
258
+ }
259
+
260
+ @keyframes heroFade { 0% { opacity: 0; transform: translateY(-20px); } 100% { opacity: 1; transform: translateY(0); } }
261
+
262
+ /* ====================== À PROPOS - DESCENDU ENCORE PLUS ====================== */
263
+ .about-section {
264
+ padding: 260px 20px 120px;
265
+ display: flex;
266
+ justify-content: center;
267
+ position: relative;
268
+ }
269
+
270
+ .about-container {
271
+ max-width: 1100px;
272
+ width: 100%;
273
+ background: linear-gradient(145deg, rgba(10, 25, 50, 0.6), rgba(5, 15, 35, 0.45));
274
+ border-radius: 24px;
275
+ border: 1px solid rgba(0, 240, 255, 0.18);
276
+ backdrop-filter: blur(16px);
277
+ -webkit-backdrop-filter: blur(16px);
278
+ padding: 50px 40px;
279
+ box-shadow: 0 25px 80px rgba(0, 0, 0, 0.6), 0 0 40px rgba(0, 240, 255, 0.12);
280
+ position: relative;
281
+ transition: all 0.6s ease;
282
+ }
283
+
284
+ .about-container::before {
285
+ content: '';
286
+ position: absolute;
287
+ top: 0; left: 0; right: 0; bottom: 0;
288
+ background: radial-gradient(circle at 30% 30%, rgba(0, 240, 255, 0.08), transparent 70%);
289
+ pointer-events: none;
290
+ }
291
+
292
+ .about-container:hover {
293
+ transform: translateY(-8px);
294
+ box-shadow: 0 35px 100px rgba(0, 0, 0, 0.7), 0 0 60px rgba(0, 240, 255, 0.2);
295
+ border-color: rgba(0, 240, 255, 0.35);
296
+ }
297
+
298
+ .about-title {
299
+ font-size: 2.6rem;
300
+ font-weight: 900;
301
+ background: linear-gradient(90deg, #8be9ff, #00f0ff, #8be9ff);
302
+ -webkit-background-clip: text;
303
+ background-clip: text;
304
+ -webkit-text-fill-color: transparent;
305
+ text-shadow: 0 0 25px rgba(0, 240, 255, 0.4);
306
+ margin: 0;
307
+ letter-spacing: 1.2px;
308
+ text-align: center;
309
+ }
310
+
311
+ .neon-underline {
312
+ height: 4px;
313
+ width: 100px;
314
+ background: var(--accent);
315
+ margin: 18px auto 0;
316
+ border-radius: 2px;
317
+ box-shadow: 0 0 20px var(--accent), 0 0 40px rgba(0, 240, 255, 0.5);
318
+ animation: pulseGlow 2.2s ease-in-out infinite alternate;
319
+ }
320
+
321
+ .about-text {
322
+ font-size: 1.15rem;
323
+ line-height: 1.8;
324
+ color: #d0f8ff;
325
+ text-align: center;
326
+ margin: 0 auto 50px;
327
+ max-width: 900px;
328
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
329
+ }
330
+
331
+ .values-grid {
332
+ display: grid;
333
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
334
+ gap: 24px;
335
+ }
336
+
337
+ .value-card {
338
+ background: rgba(15, 30, 60, 0.4);
339
+ border-radius: 16px;
340
+ padding: 24px 20px;
341
+ text-align: center;
342
+ border: 1px solid rgba(0, 240, 255, 0.1);
343
+ transition: all 0.5s ease;
344
+ backdrop-filter: blur(6px);
345
+ }
346
+
347
+ .value-card:hover {
348
+ transform: translateY(-10px) scale(1.03);
349
+ background: rgba(20, 40, 80, 0.6);
350
+ border-color: rgba(0, 240, 255, 0.3);
351
+ box-shadow: 0 20px 40px rgba(0, 240, 255, 0.15);
352
+ }
353
+
354
+ .value-card h4 {
355
+ margin: 12px 0 8px;
356
+ font-size: 1.3rem;
357
+ color: #e6ffff;
358
+ font-weight: 700;
359
+ }
360
+
361
+ .value-card p {
362
+ margin: 0;
363
+ font-size: 0.95rem;
364
+ color: #b8e0ff;
365
+ }
366
+
367
+ /* ====================== CONTACT LUXE ====================== */
368
+ .contact-section {
369
+ padding: 80px 20px;
370
+ display: flex;
371
+ justify-content: center;
372
+ }
373
+
374
+ .contact-container {
375
+ display: flex;
376
+ gap: 40px;
377
+ max-width: 1400px;
378
+ width: 100%;
379
+ flex-wrap: wrap;
380
+ justify-content: center;
381
+ align-items: stretch;
382
+ }
383
+
384
+ .contact-card {
385
+ flex: 1;
386
+ min-width: 380px;
387
+ max-width: 480px;
388
+ background: linear-gradient(145deg, rgba(10, 25, 50, 0.55), rgba(5, 15, 35, 0.4));
389
+ border-radius: 20px;
390
+ border: 1px solid rgba(0, 240, 255, 0.15);
391
+ backdrop-filter: blur(14px);
392
+ padding: 36px;
393
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 30px rgba(0, 240, 255, 0.1);
394
+ transition: all 0.6s ease;
395
+ }
396
+
397
+ .contact-card:hover {
398
+ transform: translateY(-12px) scale(1.02);
399
+ box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6), 0 0 50px rgba(0, 240, 255, 0.2);
400
+ border-color: rgba(0, 240, 255, 0.3);
401
+ }
402
+
403
+ .contact-title {
404
+ font-size: 2rem;
405
+ font-weight: 700;
406
+ background: linear-gradient(90deg, #8be9ff, #00f0ff, #8be9ff);
407
+ -webkit-background-clip: text;
408
+ -webkit-text-fill-color: transparent;
409
+ text-shadow: 0 0 20px rgba(0, 240, 255, 0.3);
410
+ margin: 0;
411
+ text-align: center;
412
+ }
413
+
414
+ .neon-line {
415
+ height: 3px;
416
+ width: 80px;
417
+ background: var(--accent);
418
+ margin: 16px auto 0;
419
+ border-radius: 2px;
420
+ box-shadow: 0 0 15px var(--accent), 0 0 30px rgba(0, 240, 255, 0.4);
421
+ animation: pulseGlow 2s ease-in-out infinite alternate;
422
+ }
423
+
424
+ .info-line {
425
+ display: flex;
426
+ align-items: center;
427
+ gap: 16px;
428
+ margin-bottom: 18px;
429
+ font-size: 1.05rem;
430
+ color: #d0f8ff;
431
+ transition: all 0.3s ease;
432
+ }
433
+
434
+ .info-line i {
435
+ font-size: 1.3rem;
436
+ color: var(--accent);
437
+ text-shadow: 0 0 10px rgba(0, 240, 255, 0.4);
438
+ }
439
+
440
+ .info-line:hover {
441
+ color: #ffffff;
442
+ transform: translateX(6px);
443
+ }
444
+
445
+ .info-line:hover i {
446
+ transform: scale(1.2);
447
+ color: #8be9ff;
448
+ }
449
+
450
+ .social-icons {
451
+ display: flex;
452
+ gap: 18px;
453
+ margin-top: 24px;
454
+ justify-content: center;
455
+ }
456
+
457
+ .social-icons a {
458
+ width: 48px;
459
+ height: 48px;
460
+ border-radius: 50%;
461
+ background: rgba(0, 240, 255, 0.1);
462
+ color: var(--accent);
463
+ display: flex;
464
+ align-items: center;
465
+ justify-content: center;
466
+ font-size: 1.3rem;
467
+ transition: all 0.4s ease;
468
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
469
+ }
470
+
471
+ .social-icons a:hover {
472
+ transform: translateY(-6px) scale(1.15);
473
+ background: rgba(0, 240, 255, 0.25);
474
+ color: white;
475
+ box-shadow: 0 15px 30px rgba(0, 240, 255, 0.3);
476
+ }
477
+
478
+ .map-container {
479
+ flex: 1;
480
+ min-width: 380px;
481
+ max-width: 600px;
482
+ height: 420px;
483
+ border-radius: 20px;
484
+ overflow: hidden;
485
+ position: relative;
486
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 40px rgba(0, 240, 255, 0.15);
487
+ border: 1px solid rgba(0, 240, 255, 0.15);
488
+ transition: all 0.6s ease;
489
+ }
490
+
491
+ .map-container:hover {
492
+ transform: translateY(-10px);
493
+ box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6), 0 0 60px rgba(0, 240, 255, 0.25);
494
+ }
495
+
496
+ .map-overlay {
497
+ position: absolute;
498
+ top: 20px;
499
+ right: 20px;
500
+ width: 50px;
501
+ height: 50px;
502
+ pointer-events: none;
503
+ }
504
+
505
+ .map-pin {
506
+ position: absolute;
507
+ top: 50%; left: 50%;
508
+ transform: translate(-50%, -50%);
509
+ font-size: 1.8rem;
510
+ color: #ff3366;
511
+ text-shadow: 0 0 20px rgba(255, 51, 102, 0.8);
512
+ animation: pulsePin 2s infinite;
513
+ }
514
+
515
+ .pulse-ring {
516
+ position: absolute;
517
+ top: 50%; left: 50%;
518
+ width: 40px; height: 40px;
519
+ border: 3px solid #ff3366;
520
+ border-radius: 50%;
521
+ transform: translate(-50%, -50%);
522
+ animation: pulseRing 2s infinite;
523
+ }
524
+
525
+ @keyframes pulseRing { 0% { transform: translate(-50%, -50%) scale(0.8); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(2.5); opacity: 0; } }
526
+ @keyframes pulsePin { 0%, 100% { transform: translate(-50%, -50%) scale(1); } 50% { transform: translate(-50%, -70%) scale(1.1); } }
527
+ @keyframes pulseGlow { from { box-shadow: 0 0 15px var(--accent), 0 0 30px rgba(0, 240, 255, 0.4); } to { box-shadow: 0 0 25px var(--accent), 0 0 50px rgba(0, 240, 255, 0.6); } }
528
+
529
+ /* ====================== PAGE ACTIONS - TradeLux FUTURISTE LUXE ====================== */
530
+ .actions-page {
531
+ padding: 40px 40px 100px;
532
+ width: 100%;
533
+ max-width: none;
534
+ margin: 0;
535
+ position: relative;
536
+ z-index: 10;
537
+ }
538
+
539
+ /* Titre principal */
540
+ .page-title {
541
+ text-align: center;
542
+ margin-bottom: 70px;
543
+ margin-top : auto;
544
+ }
545
+ .glow-title {
546
+ font-size: 4rem;
547
+ font-weight: 900;
548
+ background: linear-gradient(90deg, var(--accent), var(--accent-2), var(--accent));
549
+ -webkit-background-clip: text;
550
+ background-clip: text;
551
+ -webkit-text-fill-color: transparent;
552
+ text-shadow:
553
+ 0 0 20px rgba(0,240,255,0.6),
554
+ 0 0 40px rgba(0,240,255,0.4),
555
+ 0 0 80px rgba(0,240,255,0.2);
556
+ margin: 0;
557
+ letter-spacing: 1.5px;
558
+ animation: titlePulse 3s ease-in-out infinite alternate;
559
+ }
560
+
561
+ .neon-underline {
562
+ height: 5px;
563
+ width: 160px;
564
+ background: var(--accent);
565
+ margin: 24px auto 0;
566
+ border-radius: 3px;
567
+ box-shadow:
568
+ 0 0 20px var(--accent),
569
+ 0 0 40px rgba(0,240,255,0.7),
570
+ 0 0 60px rgba(0,240,255,0.4);
571
+ animation: pulseGlow 1.8s ease-in-out infinite alternate;
572
+ }
573
+
574
+ /* Conteneur principal */
575
+ .actions-container {
576
+ display: flex;
577
+ flex-direction: column;
578
+ gap: 40px;
579
+ }
580
+
581
+ .actions-container > .metrics-panel,
582
+ .actions-container > .advanced-control-panel{
583
+ grid-column: 1 / -1; /* ces div prennent toute la largeur */
584
+ }
585
+
586
+ /* ====================== ACTIONS NAVBAR ====================== */
587
+ .actions-navbar {
588
+ position: sticky;
589
+ top: 96px; /* ajuste selon ta navbar principale */
590
+ z-index: 1500;
591
+
592
+ width: 100%;
593
+ padding: 18px 36px;
594
+ margin-bottom: 40px;
595
+
596
+ background: linear-gradient(
597
+ 145deg,
598
+ rgba(8,20,40,0.85),
599
+ rgba(4,12,30,0.75)
600
+ );
601
+
602
+ backdrop-filter: blur(22px);
603
+ border-bottom: 1.5px solid rgba(0,240,255,0.25);
604
+
605
+ box-shadow:
606
+ 0 20px 60px rgba(0,0,0,0.65),
607
+ 0 0 40px rgba(0,240,255,0.15);
608
+ }
609
+
610
+ .actions-navbar-inner {
611
+ display: flex;
612
+ align-items: center;
613
+ justify-content: space-between;
614
+ gap: 28px;
615
+ }
616
+
617
+ /* Liste horizontale */
618
+ .stock-bar {
619
+ display: flex;
620
+ flex-wrap: wrap;
621
+ gap: 14px;
622
+ overflow: visible;
623
+ position: relative;
624
+ z-index: 10;
625
+ padding-bottom: 6px;
626
+ }
627
+
628
+ .stock-bar::-webkit-scrollbar {
629
+ height: 6px;
630
+ }
631
+ .stock-bar::-webkit-scrollbar-thumb {
632
+ background: rgba(0,240,255,0.3);
633
+ border-radius: 10px;
634
+ }
635
+
636
+ @media (max-width: 900px) {
637
+ .stock-bar {
638
+ flex-wrap: nowrap;
639
+ overflow-x: auto;
640
+ }
641
+
642
+ .stock-item {
643
+ flex: 0 0 auto;
644
+ }
645
+ }s
646
+
647
+ /* === LISTE ACTIONS === */
648
+ .stock-list {
649
+ display: flex;
650
+ flex-direction: column;
651
+ gap: 14px;
652
+ max-height: 75%;
653
+ overflow: visible;
654
+ padding-right: 6px;
655
+ }
656
+
657
+ /* Scroll discret */
658
+ .stock-list::-webkit-scrollbar {
659
+ width: 6px;
660
+ }
661
+ .stock-list::-webkit-scrollbar-thumb {
662
+ background: rgba(0,240,255,0.25);
663
+ border-radius: 10px;
664
+ }
665
+ .stock-list::-webkit-scrollbar-track {
666
+ background: transparent;
667
+ }
668
+
669
+ /* === ACTION ITEM === */
670
+ .stock-item {
671
+ flex-shrink: 0;
672
+ padding: 14px 18px;
673
+ text-align: center;
674
+ font-size: 1.05rem;
675
+ font-weight: 700;
676
+ letter-spacing: 0.6px;
677
+ position:relative;
678
+ z-index: 100;
679
+
680
+ background: linear-gradient(
681
+ 135deg,
682
+ rgba(15,35,70,0.55),
683
+ rgba(10,25,55,0.45)
684
+ );
685
+
686
+ border-radius: 16px;
687
+ border: 1.5px solid rgba(0,240,255,0.18);
688
+ cursor: pointer;
689
+
690
+ transition:
691
+ transform 0.25s ease,
692
+ box-shadow 0.25s ease,
693
+ border-color 0.25s ease,
694
+ background 0.25s ease;
695
+ }
696
+
697
+ .stock-item:hover {
698
+ z-index: 1000;
699
+ transform: translateY(-6px) scale(1.05);
700
+ background: linear-gradient(
701
+ 135deg,
702
+ rgba(25,55,100,0.7),
703
+ rgba(15,40,85,0.6)
704
+ );
705
+ border-color: rgba(0,240,255,0.45);
706
+ box-shadow: 0 10px 30px rgba(0,240,255,0.25);
707
+ }
708
+
709
+ /* === ACTION ACTIVE === */
710
+ .stock-item.active {
711
+ z-index: 1000;
712
+ background: linear-gradient(
713
+ 135deg,
714
+ var(--accent),
715
+ var(--accent-2)
716
+ );
717
+ color: #001018;
718
+ font-weight: 900;
719
+ border-color: rgba(0,240,255,0.9);
720
+
721
+ box-shadow:
722
+ 0 0 25px rgba(0,240,255,0.7),
723
+ 0 0 60px rgba(0,240,255,0.4);
724
+ }
725
+
726
+ /* Panneaux */
727
+ .ai-panel, .text-panel{
728
+ background: linear-gradient(145deg, rgba(10,25,50,0.75), rgba(5,15,35,0.6));
729
+ border-radius: 24px;
730
+ border: 1.5px solid rgba(0,240,255,0.22);
731
+ padding: 36px;
732
+ backdrop-filter: blur(18px);
733
+ -webkit-backdrop-filter: blur(18px);
734
+ box-shadow:
735
+ 0 25px 70px rgba(0,0,0,0.6),
736
+ 0 0 40px rgba(0,240,255,0.15),
737
+ inset 0 1px 0 rgba(255,255,255,0.06);
738
+ transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
739
+ position: relative;
740
+ overflow: hidden;
741
+
742
+ }
743
+
744
+ .ai-panel{
745
+ padding: 10px;
746
+ }
747
+
748
+ .text-panel::before .ai-panel::before{
749
+ content: '';
750
+ position: absolute;
751
+ top: 0; left: 0; right: 0; bottom: 0;
752
+ background: radial-gradient(circle at 20% 20%, rgba(0,240,255,0.12), transparent 70%);
753
+ pointer-events: none;
754
+ opacity: 0;
755
+ transition: opacity 0.5s ease;
756
+ }
757
+
758
+ .metrics-panel:hover::before { opacity: 1; }
759
+
760
+ .metrics-panel:hover {
761
+ transform: translateY(-10px) scale(1.02);
762
+ box-shadow:
763
+ 0 40px 100px rgba(0,0,0,0.7),
764
+ 0 0 60px rgba(0,240,255,0.3),
765
+ inset 0 1px 0 rgba(255,255,255,0.12);
766
+ border-color: rgba(0,240,255,0.45);
767
+ }
768
+
769
+ .advanced-control-panel{
770
+ background: linear-gradient(145deg, rgba(10,25,50,0.75), rgba(5,15,35,0.6));
771
+ border-radius: 24px;
772
+ border: 1.5px solid rgba(0,240,255,0.22);
773
+ padding: 36px;
774
+ backdrop-filter: blur(18px);
775
+ -webkit-backdrop-filter: blur(18px);
776
+ box-shadow:
777
+ 0 25px 70px rgba(0,0,0,0.6),
778
+ 0 0 40px rgba(0,240,255,0.15),
779
+ inset 0 1px 0 rgba(255,255,255,0.06);
780
+ transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
781
+ position: relative;
782
+ overflow: hidden;
783
+ width : 1100px;
784
+ }
785
+
786
+ /* Titre des panneaux */
787
+ .panel-title {
788
+ color: var(--accent-2);
789
+ font-size: 1.55rem;
790
+ font-weight: 800;
791
+ margin: 0 0 22px;
792
+ text-shadow: 0 0 18px rgba(139,233,255,0.4);
793
+ letter-spacing: 0.8px;
794
+ position: relative;
795
+ }
796
+ .panel-title::after {
797
+ content: '';
798
+ position: absolute;
799
+ bottom: -10px; left: 0;
800
+ width: 55px; height: 4px;
801
+ background: linear-gradient(90deg, var(--accent), transparent);
802
+ border-radius: 2px;
803
+ box-shadow: 0 0 15px var(--accent);
804
+ }
805
+
806
+ /* Dropdowns */
807
+ .lux-dropdown {
808
+ background: rgba(5,15,35,0.92) !important;
809
+ border: 2px solid rgba(0,240,255,0.28) !important;
810
+ color: #e6ffff !important;
811
+ border-radius: 16px;
812
+ padding: 14px 18px;
813
+ font-size: 1.08rem;
814
+ font-weight: 600;
815
+ box-shadow: 0 10px 30px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.1);
816
+ transition: all 0.35s ease;
817
+ min-width: 180px;
818
+ width: auto; /* largeur automatique selon le contenu */
819
+ }
820
+
821
+ .lux-dropdown .Select-control {
822
+ background: rgba(5,15,35,0.9) !important;
823
+ color: #00f0ff !important;
824
+ border: 2px solid rgba(0,240,255,0.28) !important;
825
+ border-radius: 16px;
826
+ }
827
+
828
+ .lux-dropdown .Select-control .Select-value .Select-value-label {
829
+ color: #e6ffff !important; /* couleur du texte sélectionné */
830
+ }
831
+
832
+ .lux-dropdown:focus {
833
+ outline: none;
834
+ border-color: var(--accent) !important;
835
+ box-shadow: 0 0 25px rgba(0,240,255,0.5), inset 0 1px 0 rgba(255,255,255,0.15);
836
+ }
837
+
838
+ .scrollable-dropdown .Select-menu-outer {
839
+ position: absolute !important;
840
+ top: 100%;
841
+ left: 0;
842
+ z-index: 9999;
843
+ width : 150px;
844
+ overflow-y: auto;
845
+ background: rgba(5,15,35,0.98) !important;
846
+ border: 2px solid rgba(0,240,255,0.28) !important;
847
+ border-radius: 16px;
848
+ box-shadow: 0 15px 40px rgba(0,0,0,0.5);
849
+ }
850
+
851
+ .scrollable-dropdown .Select-option {
852
+ color: #e6ffff !important;
853
+ background: rgba(10,25,55,0.7) !important;
854
+ padding: 13px 20px;
855
+ border-bottom: 1px solid rgba(0,240,255,0.1);
856
+ transition: all 0.3s ease;
857
+ }
858
+
859
+ .scrollable-dropdown .Select-option.is-focused,
860
+ .scrollable-dropdown .Select-option:hover {
861
+ background: rgba(0,240,255,0.25) !important;
862
+ color: white !important;
863
+ text-shadow: 0 0 12px rgba(0,240,255,0.7);
864
+ font-weight: 700;
865
+ }
866
+
867
+ /* Métriques */
868
+ .metrics-grid { gap: 20px; }
869
+
870
+ .metric-item {
871
+ display: flex;
872
+ justify-content: space-between;
873
+ align-items: center;
874
+ padding: 18px 24px;
875
+ background: linear-gradient(135deg, rgba(15,35,70,0.55), rgba(10,25,55,0.45));
876
+ border-radius: 18px;
877
+ border: 1.5px solid rgba(0,240,255,0.18);
878
+ transition: all 0.4s ease;
879
+ position: relative;
880
+ overflow: hidden;
881
+ }
882
+
883
+ .metric-item::before {
884
+ content: '';
885
+ position: absolute;
886
+ top: 0; left: 0; width: 5px; height: 100%;
887
+ background: var(--accent);
888
+ opacity: 0;
889
+ transition: opacity 0.3s ease;
890
+ }
891
+
892
+ .metric-label { color: #b8e0ff; font-weight: 600; font-size: 1rem; }
893
+ .metric-value { color: #e6ffff; font-weight: 800; font-size: 1.4rem; text-shadow: 0 0 12px rgba(230,255,255,0.4); }
894
+ .metric-change.up { color: #7cffb2; font-weight: 800; text-shadow: 0 0 18px rgba(124,255,178,0.7); animation: pulseUp 1.6s infinite; }
895
+ .metric-change.down { color: #ff7b7b; font-weight: 800; text-shadow: 0 0 18px rgba(255,123,123,0.7); animation: pulseDown 1.6s infinite; }
896
+
897
+ @keyframes pulseUp { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
898
+ @keyframes pulseDown { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
899
+
900
+ /* GRAPHIQUE LARGE */
901
+ .graph-panel {
902
+ background: linear-gradient(145deg, rgba(8,20,40,0.92), rgba(3,10,25,0.78));
903
+ border-radius: 32px;
904
+ border: 2px solid rgba(0,240,255,0.3);
905
+ padding: 44px;
906
+ backdrop-filter: blur(22px);
907
+ box-shadow: 0 35px 100px rgba(0,0,0,0.75), 0 0 60px rgba(0,240,255,0.25), inset 0 2px 0 rgba(255,255,255,0.1);
908
+ transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
909
+ }
910
+
911
+ .lux-graph {
912
+ height: 620px !important;
913
+ width: 100% !important;
914
+ border-radius: 24px;
915
+
916
+ box-shadow: 0 25px 60px rgba(0,0,0,0.6), inset 0 0 40px rgba(0,240,255,0.18);
917
+ background: rgba(0,0,0,0.5) !important;
918
+ }
919
+
920
+ /* ====================== RESPONSIVE ====================== */
921
+ @media (max-width: 1600px) {
922
+ .actions-container { grid-template-columns: 1fr 1fr; }
923
+ .graph-panel { grid-column: 1 / span 2; }
924
+ }
925
+
926
+ @media (max-width: 1000px) {
927
+ .actions-container { grid-template-columns: 1fr; }
928
+ .glow-title, .hero h1 { font-size: 3.2rem; }
929
+ .lux-graph { height: 500px !important; }
930
+ .hero { padding: 180px 20px 80px; }
931
+ .about-section { padding: 200px 20px 100px; }
932
+ }
933
+
934
+ @media (max-width: 900px) {
935
+ .navbar { padding: 12px 20px; }
936
+ .page-content.with-ticker { padding-top: 160px; }
937
+ .ticker-wrap { display: none; }
938
+ .contact-container, .about-container { gap: 30px; padding: 30px 20px; }
939
+ .contact-card, .map-container { min-width: 100%; }
940
+ .map-container { height: 350px; }
941
+ }
942
+
943
+ @media (max-width: 600px) {
944
+ .actions-page { padding: 110px 20px 70px; }
945
+ .control-panel, .metrics-panel, .graph-panel { padding: 28px; }
946
+ .glow-title, .hero h1 { font-size: 2.6rem; }
947
+ .hero { padding: 140px 15px 60px; }
948
+ .hero p { font-size: 1.1rem; }
949
+ .about-section { padding: 160px 15px 80px; }
950
+ }
951
+
952
+ /* Ligne double AI + Stats */
953
+ .dual-panel-row {
954
+ display: grid;
955
+ grid-template-columns: 1fr; /* mobile */
956
+ gap: 40px;
957
+ }
958
+
959
+ /* Écrans larges */
960
+ @media (min-width: 1200px) {
961
+ .dual-panel-row {
962
+ grid-template-columns: 1.3fr 1fr;
963
+ align-items: stretch;
964
+ }
965
+ }
966
+
967
+ /* ======================
968
+ TOP STATS - TABLEAU STYLE LUXE
969
+ ====================== */
970
+
971
+ /* Style général du tableau */
972
+ .lux-table {
973
+ width: 100%;
974
+ border-collapse: separate;
975
+ border-spacing: 0 8px;
976
+ background: linear-gradient(145deg, rgba(10,25,50,0.6), rgba(5,15,35,0.45));
977
+ border-radius: 20px;
978
+ overflow: hidden;
979
+ box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 30px rgba(0,240,255,0.1);
980
+ font-family: 'Inter', sans-serif;
981
+ font-size: 1.05rem;
982
+ }
983
+
984
+ /* Corps du tableau */
985
+ .lux-table tbody tr {
986
+ transition: all 0.3s ease;
987
+ background: linear-gradient(135deg, rgba(15,35,70,0.5), rgba(10,25,55,0.35));
988
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
989
+ border-left: 1.5px solid rgba(0,240,255,0.12);
990
+ border-right: 1.5px solid rgba(0,240,255,0.12);
991
+ }
992
+
993
+ /* Cellules */
994
+ .lux-table td {
995
+ padding: 18px 14px;
996
+ text-align: center;
997
+ font-weight: 600;
998
+ color: #eaf6ff;
999
+ white-space: nowrap;
1000
+ transition: color 0.3s ease;
1001
+ }
1002
+
1003
+ /* Couleur selon valeur */
1004
+ .lux-table .up { color: #7cffb2; font-weight: 700; }
1005
+ .lux-table .down { color: #ff7b7b; font-weight: 700; }
1006
+
1007
+ .metric-value .up { color: #7cffb2; font-weight: 700; }
1008
+ .metric-value .down { color: #ff7b7b; font-weight: 700; }
1009
+
1010
+
1011
+ /* Responsive : réduire padding sur mobile */
1012
+ @media (max-width: 900px) {
1013
+ .lux-table td {
1014
+ padding: 12px 8px;
1015
+ font-size: 0.95rem;
1016
+ }
1017
+ }
1018
+
1019
+ /* lignes alternées discrètes */
1020
+ .lux-table tbody tr:nth-child(even) {
1021
+ background: linear-gradient(135deg, rgba(15,35,70,0.45), rgba(10,25,55,0.3));
1022
+ }
1023
+
1024
+ .table-container {
1025
+ width: 100%;
1026
+ overflow-x: auto; /* scroll horizontal si trop large */
1027
+ max-height: 500px;
1028
+ overflow-y: auto;
1029
+ -webkit-overflow-scrolling: touch; /* smooth scroll sur mobile */
1030
+ }
1031
+
1032
+ .table-container table {
1033
+ width: 100%;
1034
+ min-width: 800px; /* largeur minimale pour forcer le scroll si écran trop petit */
1035
+ border-collapse: collapse;
1036
+ table-layout: fixed;
1037
+ }
1038
+
Interface Graphique/pages/__pycache__/actions_page.cpython-312.pyc ADDED
Binary file (13.2 kB). View file
 
Interface Graphique/pages/__pycache__/actions_page.cpython-314.pyc ADDED
Binary file (13.8 kB). View file
 
Interface Graphique/pages/__pycache__/analyse.cpython-312.pyc ADDED
Binary file (1.05 kB). View file
 
Interface Graphique/pages/__pycache__/analyse_page.cpython-312.pyc ADDED
Binary file (6.49 kB). View file
 
Interface Graphique/pages/__pycache__/home.cpython-312.pyc ADDED
Binary file (5.75 kB). View file
 
Interface Graphique/pages/__pycache__/home.cpython-314.pyc ADDED
Binary file (5.66 kB). View file
 
Interface Graphique/pages/actions_page.py ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dash import html, dcc, Input, Output, State, callback, register_page,no_update,ctx,ALL
2
+ import plotly.graph_objects as go
3
+ import pandas as pd
4
+ import numpy as np
5
+ from tensorflow.keras.models import load_model
6
+
7
+ register_page(__name__, path="/actions_page", name="Actions")
8
+
9
+ df_report = pd.read_csv("Data/data_report.csv")
10
+ df_cleaned = pd.read_csv("Data/ALL_CLEANED.csv", parse_dates=["date"])
11
+ df_features = pd.read_csv("Data/ALL_FEATURES.csv", parse_dates=["date"])
12
+ available_symbols = sorted(df_cleaned["symbol"].unique())
13
+
14
+ lstm_model = load_model("Modèle IA/global_lstm_returns.keras")
15
+ symbol_to_id = {
16
+ "AAPL": 0,
17
+ "AMZN": 1,
18
+ "BTC-USD": 2,
19
+ "GOOGL": 3,
20
+ "META": 4,
21
+ "MSFT": 5,
22
+ "NVDA": 6,
23
+ "TSLA": 7,
24
+ }
25
+
26
+ def prepare_lstm_inputs(df_features, symbol, n_timesteps=60):
27
+ """
28
+ Prépare les deux entrées pour le LSTM :
29
+ - Séquence temporelle (Close)
30
+ - ID du symbol
31
+ """
32
+ seq_input = df_features["Close"].tail(n_timesteps).values.reshape(1, n_timesteps, 1)
33
+
34
+ symbol_id = symbol_to_id[symbol]
35
+ extra_input = np.array([[symbol_id]])
36
+
37
+ return [seq_input, extra_input]
38
+
39
+ def predict_lstm(df_features_symbol, symbol):
40
+ """
41
+ Retourne signal, confiance et backtest
42
+ """
43
+ inputs = prepare_lstm_inputs(df_features_symbol, symbol)
44
+ if inputs is None:
45
+ return "Pas assez de données", "N/A", "N/A"
46
+
47
+ pred_price = lstm_model.predict(inputs)[0][0]
48
+ if (pred_price > 1/3):
49
+ signal = "Acheter"
50
+ elif (pred_price < -1/3):
51
+ signal = "Vendre"
52
+ else:
53
+ signal = "Garder"
54
+ confidence = abs(pred_price)*1000
55
+ backtest = "Gain moyen 6 mois : +3%"
56
+
57
+ return signal, f"{confidence:.1f}%", backtest, f"{pred_price:.3f}"
58
+
59
+ def filter_period(df, period):
60
+ """Filtre df selon la période comme yfinance."""
61
+ days_map = {
62
+ "1mo": 30,
63
+ "2mo": 60,
64
+ "3mo": 90,
65
+ "6mo": 182,
66
+ "9mo": 273,
67
+ "1y": 365,
68
+ "2y": 730,
69
+ "3y": 1095,
70
+ "5y": 1825,
71
+ }
72
+ if period not in days_map:
73
+ return df
74
+
75
+ cutoff = pd.Timestamp.today() - pd.Timedelta(days=days_map[period])
76
+ return df[df["date"] >= cutoff]
77
+
78
+ symbol_to_name = {
79
+ "AAPL": "Apple",
80
+ "AMZN": "Amazon",
81
+ "BTC-USD": "Bitcoin",
82
+ "GOOGL": "Google",
83
+ "META": "Meta",
84
+ "MSFT": "Microsoft",
85
+ "NVDA": "NVIDIA",
86
+ "TSLA": "Tesla"
87
+ }
88
+
89
+ stock_items = []
90
+ for symbol in available_symbols:
91
+ display_name = symbol_to_name.get(symbol, symbol) # fallback au symbole si pas de nom
92
+ stock_items.append(html.Div(
93
+ display_name,
94
+ id={'type': 'stock-item', 'index': symbol}, # on garde le symbol pour le callback
95
+ n_clicks=0,
96
+ className="stock-item active" if symbol == "AAPL" else "stock-item"
97
+ ))
98
+
99
+ # === LAYOUT ===
100
+ layout = html.Div(className="actions-page", children=[
101
+ #Store permettant la valeur par défaut du graph
102
+ dcc.Store(id="selected-stock", data="AAPL"),
103
+
104
+ # Titre animé
105
+ html.Div(className="page-title", children=[
106
+ html.H1("Analyse d'Actifs en Temps Réel", className="glow-title"),
107
+ html.Div(className="neon-underline")
108
+ ]),
109
+ # Conteneur principal
110
+ html.Div(className="actions-navbar",children=[
111
+ html.Div(className="actions-navbar-inner",children=[
112
+ html.Div(
113
+ className="stock-bar",
114
+ children=stock_items
115
+ ),
116
+ dcc.Dropdown(
117
+ searchable=False,
118
+ maxHeight=100,
119
+ id='period-dropdown',
120
+ options=[
121
+ {'label': '1 mois', 'value': '1mo'},
122
+ {'label': '2 mois', 'value': '2mo'},
123
+ {'label': '3 mois', 'value': '3mo'},
124
+ {'label': '6 mois', 'value': '6mo'},
125
+ {'label': '9 mois', 'value': '9mo'},
126
+ {'label': '1 an', 'value': '1y'},
127
+ {'label': '2 ans', 'value': '2y'},
128
+ {'label': '3 ans', 'value': '3y'},
129
+ {'label': '5 ans', 'value': '5y'},
130
+ ],
131
+ value='6mo',
132
+ className="lux-dropdown scrollable-dropdown"
133
+ )
134
+ ])
135
+ ]),
136
+ html.Div(className="actions-container", children=[
137
+ html.Div(className="dual-panel-row",children=[
138
+ # --- Recommandations (prédictions) ---
139
+ html.Div(className="ai-panel", children=[
140
+ #Signal du modèle
141
+ html.Div(className="text-panel", children=[
142
+ html.H3("Prévisions de l'IA",className="panel-title", style={"padding-left": "36px"}),
143
+ html.Table(
144
+ className="lux-table split-table",
145
+ children=[
146
+ html.Thead(
147
+ html.Tr([
148
+ html.Th("Signal"),
149
+ html.Th("Prédiction"),
150
+ html.Th("Confiance"),
151
+ ])
152
+ ),
153
+ html.Tbody([
154
+ html.Tr([
155
+ html.Td(id="ai-signal", className="metric-value", children="Chargement..."),
156
+ html.Td(id="ai-predict", className="metric-value", children="Chargement..."),
157
+ html.Td(id="ai-confidence", className="metric-value", children="Chargement..."),
158
+ ])
159
+ ])
160
+ ]
161
+ ),
162
+ # Backtest / Performance passée
163
+ html.H4("Performance passée", className="panel-title"),
164
+ html.Div(id="ai-backtest", className="metric-value", children="Chargement...", style={"margin-bottom": "24px"}) ,
165
+ html.H3("Attention : Les prédictions ne constituent pas un conseil financier", className="panel-title"),
166
+ ]),
167
+ ]),
168
+ # === MÉTRIQUES EN TEMPS RÉEL ===
169
+ html.Div(className="text-panel", children=[
170
+ html.H3("Résumé rapide : Top Stats", className="panel-title"),
171
+ dcc.Loading(html.Div(id='live-metrics', className="metrics-grid"), type="cube")
172
+ ])
173
+ ]),
174
+ # --- GRAPHIQUE ---
175
+ html.Div(className="graph-panel", children=[
176
+ html.H3("Graphique des Prix", className="panel-title"),
177
+ dcc.Loading(
178
+ dcc.Graph(id='stock-graph', className="lux-graph"),
179
+ type="dot"
180
+ ),
181
+ dcc.Interval(
182
+ id='interval-graph-update',
183
+ interval=60*1000,
184
+ n_intervals=0
185
+ )
186
+ ]),
187
+ ])
188
+ ])
189
+ def get_interval(period):
190
+ if "y" in period:
191
+ return "5d"
192
+ else:
193
+ return "1d"
194
+
195
+ # === CALLBACKS ==
196
+ @callback(
197
+ Output("selected-stock", "data"),
198
+ Output({"type": "stock-item", "index": ALL}, "className"),
199
+ Input({"type": "stock-item", "index": ALL}, "n_clicks"),
200
+ State({"type": "stock-item", "index": ALL}, "id"),
201
+ prevent_initial_call=True
202
+ )
203
+ def select_single_stock(n_clicks, ids):
204
+ if not ctx.triggered:
205
+ return no_update, no_update
206
+
207
+ selected = ctx.triggered_id["index"]
208
+
209
+ classes = [
210
+ "stock-item active" if item["index"] == selected else "stock-item"
211
+ for item in ids
212
+ ]
213
+
214
+ return selected, classes
215
+ @callback(
216
+ Output('stock-graph', 'figure'),
217
+ Output('live-metrics', 'children'),
218
+ Output('ai-signal', 'children'),
219
+ Output('ai-signal', 'className'),
220
+ Output('ai-predict', 'children'),
221
+ Output('ai-predict', 'className'),
222
+ Output('ai-confidence', 'children'),
223
+ Output('ai-backtest', 'children'),
224
+ Input('interval-graph-update', 'n_intervals'),
225
+ Input("selected-stock", "data"),
226
+ Input('period-dropdown', 'value'),
227
+ )
228
+ def update_graph_and_metrics(n, symbol, period):
229
+
230
+ fig = go.Figure()
231
+
232
+ metrics = []
233
+ ai_signal, ai_confidence, ai_backtest, ai_prediction = "N/A", "N/A", "N/A","N/A"
234
+
235
+ if not symbol:
236
+ fig.add_annotation(
237
+ text="Aucune action sélectionnée", x=0.5, y=0.5, showarrow=False
238
+ )
239
+ return fig, [html.Div("Aucune action sélectionnée", className="metric-item error")]
240
+
241
+ ticker_symbol = symbol
242
+ # Filtrer les données pour ce ticker
243
+ hist_graph = df_cleaned[df_cleaned["symbol"] == ticker_symbol].sort_values("date").copy()
244
+ if hist_graph.empty:
245
+ fig.add_annotation(
246
+ text=f"Aucune donnée pour {ticker_symbol}", x=0.5, y=0.5, showarrow=False
247
+ )
248
+ return fig, [html.Div(f"Aucune donnée pour {ticker_symbol}", className="metric-item error")]
249
+
250
+ # Filtrage par période
251
+ hist_graph = filter_period(hist_graph, period)
252
+ if hist_graph.empty:
253
+ fig.add_annotation(
254
+ text=f"Aucune donnée pour la période sélectionnée", x=0.5, y=0.5, showarrow=False
255
+ )
256
+ return fig, [html.Div("Aucune donnée pour la période sélectionnée", className="metric-item error")]
257
+
258
+ # Couleurs simples : vert pour hausse, rouge pour baisse
259
+ increasing_color = "green"
260
+ decreasing_color = "red"
261
+
262
+ # Ajout du graphique
263
+ fig.add_trace(go.Candlestick(
264
+ x=hist_graph["date"],
265
+ open=hist_graph["Open"],
266
+ high=hist_graph["High"],
267
+ low=hist_graph["Low"],
268
+ close=hist_graph["Close"],
269
+ name=ticker_symbol,
270
+ increasing_line_color=increasing_color,
271
+ decreasing_line_color=decreasing_color,
272
+ increasing_fillcolor="rgba(0,255,0,0.6)",
273
+ decreasing_fillcolor="rgba(255,0,0,0.6)"
274
+ ))
275
+
276
+ hist_metric = df_features[df_features["symbol"] == ticker_symbol].sort_values("date").copy()
277
+ if hist_metric.empty:
278
+ fig.add_annotation(
279
+ text=f"Aucune donnée pour {ticker_symbol}", x=0.5, y=0.5, showarrow=False
280
+ )
281
+ return fig, [html.Div(f"Aucune donnée pour {ticker_symbol}", className="metric-item error")]
282
+
283
+ # Filtrage par période
284
+ hist_metric = filter_period(hist_metric, period)
285
+ if hist_metric.empty:
286
+ fig.add_annotation(
287
+ text=f"Aucune donnée pour la période sélectionnée", x=0.5, y=0.5, showarrow=False
288
+ )
289
+ return fig, [html.Div("Aucune donnée pour la période sélectionnée", className="metric-item error")]
290
+
291
+ # Métriques
292
+ price = hist_metric["Close"].iloc[-1]
293
+ high = hist_metric["High"].iloc[-1]
294
+ low = hist_metric["Low"].iloc[-1]
295
+ volume = hist_metric["Volume"].iloc[-1]
296
+ yesterday_price = hist_metric["Close"].iloc[-2]
297
+ change_pct = (price - yesterday_price) / yesterday_price * 100
298
+
299
+ change_class = "up" if change_pct >= 0 else "down"
300
+
301
+ company = symbol_to_name.get(ticker_symbol, ticker_symbol)
302
+
303
+ metrics = html.Div(className="text-panel", children=[
304
+ html.Table(
305
+ className="lux-table split-table",
306
+ children=[
307
+ html.Thead(
308
+ html.Tr([
309
+ html.Th("Entreprise"),
310
+ html.Th("Prix actuel"),
311
+ html.Th("Var. vs hier"),
312
+ html.Th(""),
313
+ ])
314
+ ),
315
+ html.Tbody([
316
+ # Ligne 1
317
+ html.Tr([
318
+ html.Td(company),
319
+ html.Td(f"${price:,.2f}"),
320
+ html.Td(
321
+ f"{change_pct:+.2f}%",
322
+ className=change_class
323
+ ),
324
+ html.Td(""),
325
+ ]),
326
+ # Ligne 2
327
+ html.Tr([
328
+ html.Td(f"High : {high:,.2f}"),
329
+ html.Td(f"Low : {low:,.2f}"),
330
+ html.Td(f"Volume : {volume:,.0f}"),
331
+ ]),
332
+ ])
333
+ ]
334
+ )
335
+ ])
336
+
337
+ ai_signal, ai_confidence, ai_backtest, ai_prediction = predict_lstm(hist_metric, symbol)
338
+
339
+ if(ai_signal == "Acheter"):
340
+ signal_class = "metric-value up"
341
+ predict_class = "metric-value up"
342
+ elif(ai_signal == "Vendre"):
343
+ signal_class = "metric-value down"
344
+ predict_class = "metric-value down"
345
+ else:
346
+ signal_class = "metric-value"
347
+ predict_class = "metric-value"
348
+
349
+ fig.update_layout(
350
+ template="plotly_dark",
351
+ paper_bgcolor="rgba(0,0,0,0)",
352
+ plot_bgcolor="rgba(0,0,0,0)",
353
+ font=dict(color="#e6ffff"),
354
+ xaxis=dict(showgrid=True, gridcolor="rgba(0,240,255,0.1)"),
355
+ yaxis=dict(showgrid=True, gridcolor="rgba(0,240,255,0.1)"),
356
+ margin=dict(l=40, r=40, t=40, b=40),
357
+ height=500
358
+ )
359
+
360
+ return fig, metrics, ai_signal,signal_class, ai_prediction,predict_class, ai_confidence, ai_backtest
Interface Graphique/pages/analyse_page.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dash import html, dcc, Input, Output, State, callback, register_page,no_update,ctx,ALL
2
+ import plotly.graph_objects as go
3
+ import pandas as pd
4
+ import numpy as np
5
+
6
+ register_page(__name__, path="/data", name="Data")
7
+ df_features = pd.read_csv("Data/ALL_FEATURES.csv", parse_dates=["date"])
8
+
9
+ symbol_to_name = {
10
+ "AAPL": "Apple",
11
+ "AMZN": "Amazon",
12
+ "BTC-USD": "Bitcoin",
13
+ "GOOGL": "Google",
14
+ "META": "Meta",
15
+ "MSFT": "Microsoft",
16
+ "NVDA": "NVIDIA",
17
+ "TSLA": "Tesla"
18
+ }
19
+
20
+ available_symbols = sorted(df_features["symbol"].unique())
21
+ stock_items = []
22
+ for symbol in available_symbols:
23
+ display_name = symbol_to_name.get(symbol, symbol) # fallback au symbole si pas de nom
24
+ stock_items.append(html.Div(
25
+ display_name,
26
+ id={'type': 'stock-item', 'index': symbol}, # on garde le symbol pour le callback
27
+ n_clicks=0,
28
+ className="stock-item active" if symbol == "AAPL" else "stock-item"
29
+ ))
30
+
31
+ layout = html.Div(className="data-page", children = [
32
+ dcc.Store(id="selected-stock", data="AAPL"),
33
+ # Titre animé
34
+ html.Div(className="page-title", children=[
35
+ html.H1("Données utilisées par le modèle", className="glow-title"),
36
+ html.Div(className="neon-underline")
37
+ ]),
38
+ html.Div(className="actions-navbar",children=[
39
+ html.Div(className="actions-navbar-inner",children=[
40
+ html.Div(
41
+ className="stock-bar",
42
+ children=stock_items,
43
+ ),
44
+ ])
45
+ ]),
46
+ html.Div(className="text-panel", children=[
47
+ html.H4("Données Utilisées", className="panel-title"),
48
+ html.Div(className="table-container",children=[
49
+ html.Table(
50
+ className="lux-table split-table",
51
+ children=[
52
+ html.Thead(
53
+ html.Tr([
54
+ html.Th("Date"),
55
+ html.Th("Open"),
56
+ html.Th("High"),
57
+ html.Th("Low"),
58
+ html.Th("Close"),
59
+ html.Th("Volume"),
60
+ html.Th("Volatility"),
61
+ html.Th("RSI"),
62
+ html.Th("MA 20"),
63
+ ])
64
+ ),
65
+ html.Tbody(id="features-table-body") ,
66
+ ]
67
+ )
68
+ ]),
69
+
70
+ ]),
71
+ ])
72
+
73
+ def generate_table_rows(df, max_rows=60):
74
+ """
75
+ Génère les lignes HTML du tableau pour Dash.
76
+ Arrondit certaines colonnes pour plus de lisibilité.
77
+ """
78
+ df = df.tail(max_rows).copy().iloc[::-1]
79
+
80
+ # Arrondir les colonnes pour plus de lisibilité
81
+ for col in ["Open", "High", "Low", "Close", "MA_20"]:
82
+ if col in df.columns:
83
+ df[col] = df[col].round(2)
84
+ if "RSI_14" in df.columns:
85
+ df["RSI_14"] = df["RSI_14"].round(1)
86
+ if "Volume" in df.columns:
87
+ df["Volume"] = df["Volume"].astype(int)
88
+ if "date" in df.columns:
89
+ df["date"] = df["date"].dt.date
90
+ if "volatility_10" in df.columns:
91
+ df["volatility_10"] = df["volatility_10"].round(4)
92
+
93
+ rows = []
94
+ close_values = df["Close"].tolist() if "Close" in df.columns else []
95
+ for i, (_, row) in enumerate(df.iterrows()):
96
+ if i < len(close_values) - 1:
97
+ next_close = close_values[i + 1]
98
+ current_close = row["Close"]
99
+ if current_close > next_close:
100
+ close_class = "metric-value up"
101
+ elif current_close < next_close:
102
+ close_class = "metric-value down"
103
+ else:
104
+ close_class = ""
105
+ else:
106
+ close_class = ""
107
+ rows.append(
108
+ html.Tr([
109
+ html.Td(row.get("date", "-")),
110
+ html.Td(f"{row.get('Open', '-')}$"),
111
+ html.Td(f"{row.get('High', '-')}$"),
112
+ html.Td(f"{row.get('Low', '-')}$"),
113
+ html.Td(f"{row.get('Close', '-')}$", className=close_class),
114
+ html.Td(row.get("Volume", "-")),
115
+ html.Td(row.get("volatility_10", "-")),
116
+ html.Td(row.get("RSI_14", "-")),
117
+ html.Td(row.get("MA_20", "-")),
118
+ ])
119
+ )
120
+ return rows
121
+
122
+
123
+ @callback(
124
+ Output("features-table-body", "children"),
125
+ Input("selected-stock", "data") # on récupère l'action sélectionnée
126
+ )
127
+ def update_features_table(symbol):
128
+ # Si symbol est None, on met AAPL par défaut
129
+ if not symbol:
130
+ symbol = "AAPL"
131
+
132
+ # Filtrer le DataFrame pour ce symbole
133
+ df_symbol = df_features[df_features["symbol"] == symbol].sort_values("date")
134
+ if df_symbol.empty:
135
+ return [html.Tr([html.Td("Pas de données", colSpan=8)])]
136
+
137
+ # Retourner les 60 dernières lignes
138
+ return generate_table_rows(df_symbol, max_rows=60)
139
+
140
+
Interface Graphique/pages/home.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dash import html
2
+ import dash
3
+
4
+ dash.register_page(__name__, path="/")
5
+
6
+ layout = html.Div([
7
+ # ====================== HERO ======================
8
+ html.Div(className="hero", children=[
9
+ html.H1("Bienvenue dans la Galaxie des Marchés Futuristes"),
10
+ ]),
11
+
12
+ # ====================== À PROPOS DE NOUS ======================
13
+ html.Div(className="about-section", children=[
14
+ html.Div(className="about-container", children=[
15
+
16
+ # Titre avec effet néon
17
+ html.Div(className="about-header", children=[
18
+ html.H2("À propos de nous", className="about-title"),
19
+ html.Div(className="neon-underline")
20
+ ]),
21
+
22
+ # Texte principal
23
+ html.P("""
24
+ Notre plateforme boursière futuriste combine innovation, intelligence artificielle et visualisation avancée
25
+ pour offrir une expérience unique d’analyse financière. Inspirée par la précision et l’esthétique du monde spatial,
26
+ notre mission est de guider les investisseurs vers une compréhension plus claire et plus intuitive des marchés.
27
+ """, className="about-text"),
28
+
29
+ # Cartes de valeurs
30
+ html.Div(className="values-grid", children=[
31
+ html.Div(className="value-card", children=[
32
+ html.I(className="fas fa-rocket", style={"fontSize": "2.2rem", "marginBottom": "12px", "color": "var(--accent)"}),
33
+ html.H4("Innovation"),
34
+ html.P("Technologies de pointe pour demain.")
35
+ ]),
36
+ html.Div(className="value-card", children=[
37
+ html.I(className="fas fa-brain", style={"fontSize": "2.2rem", "marginBottom": "12px", "color": "var(--accent-2)"}),
38
+ html.H4("Intelligence Artificielle"),
39
+ html.P("Prédictions précises, analyses automatisées.")
40
+ ]),
41
+ html.Div(className="value-card", children=[
42
+ html.I(className="fas fa-shield-alt", style={"fontSize": "2.2rem", "marginBottom": "12px", "color": "#00f0ff"}),
43
+ html.H4("Sécurité"),
44
+ html.P("Données chiffrées, confiance absolue.")
45
+ ]),
46
+ html.Div(className="value-card", children=[
47
+ html.I(className="fas fa-eye", style={"fontSize": "2.2rem", "marginBottom": "12px", "color": "#8be9ff"}),
48
+ html.H4("Vision Futuriste"),
49
+ html.P("Une interface inspirée de l’espace.")
50
+ ]),
51
+ ])
52
+ ])
53
+ ]),
54
+
55
+ # ====================== CONTACT SECTION ======================
56
+ html.Div(id="contact-section", className="contact-section", children=[
57
+ html.Div(className="contact-container", children=[
58
+
59
+ # --- CARTE CONTACT ---
60
+ html.Div(className="contact-card", children=[
61
+ html.Div(className="contact-header", children=[
62
+ html.H2("Contact & Réseaux", className="contact-title"),
63
+ html.Div(className="neon-line")
64
+ ]),
65
+
66
+ html.Div(className="contact-body", children=[
67
+ html.Div(className="info-line", children=[
68
+ html.I(className="fas fa-envelope"),
69
+ html.Span("contact.ETU@univ-lemans.fr")
70
+ ]),
71
+ html.Div(className="info-line", children=[
72
+ html.I(className="fas fa-phone"),
73
+ html.Span("+33 7 56 32 98 10")
74
+ ]),
75
+ html.Div(className="info-line", children=[
76
+ html.I(className="fas fa-location-dot"),
77
+ html.Span([
78
+ "ENSIM, 1 Rue Aristote",
79
+ html.Br(),
80
+ "72000 Le Mans, France"
81
+ ])
82
+ ]),
83
+
84
+ html.Div(className="social-icons", children=[
85
+ html.A(html.I(className="fab fa-instagram"), href="https://instagram.com", target="_blank", title="Instagram"),
86
+ html.A(html.I(className="fab fa-facebook-f"), href="https://facebook.com", target="_blank", title="Facebook"),
87
+ html.A(html.I(className="fab fa-linkedin-in"), href="https://linkedin.com", target="_blank", title="LinkedIn"),
88
+ html.A(html.I(className="fab fa-x-twitter"), href="https://x.com", target="_blank", title="X (Twitter)"),
89
+ ])
90
+ ])
91
+ ]),
92
+
93
+ # --- CARTE GOOGLE MAPS ---
94
+ html.Div(className="map-container", children=[
95
+ html.Iframe(
96
+ src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2671.877541649405!2d0.15757102527180605!3d48.01906203587245!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x47e2886a4fa0b1ad%3A0xf8aeb2cc9cd5f2e!2s1+Rue+Aristote,+72000+Le+Mans!5e0!3m2!1sfr!2sfr!4v1715610936412!5m2!1sfr!2sfr",
97
+ width="100%",
98
+ height="100%",
99
+ style={"border": "0", "borderRadius": "16px"}
100
+ ),
101
+ html.Div(className="map-overlay", children=[
102
+ html.Div(className="pulse-ring"),
103
+ html.I(className="fas fa-map-marker-alt map-pin")
104
+ ])
105
+ ])
106
+ ])
107
+ ])
108
+ ])
Interface Graphique/services/__pycache__/market_data.cpython-311.pyc ADDED
Binary file (5.55 kB). View file
 
Interface Graphique/services/market_data.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Optional
3
+ import pandas as pd
4
+ import datetime as dt
5
+ import time
6
+
7
+ try:
8
+ import requests # for Alpha Vantage fallback
9
+ except Exception:
10
+ requests = None
11
+
12
+ try:
13
+ import yfinance as yf # lightweight polling, near real-time for popular tickers
14
+ except Exception:
15
+ yf = None
16
+
17
+
18
+ def fetch_candles_yf(symbol: str, period: str = '1d', interval: str = '1m') -> pd.DataFrame:
19
+ if yf is None:
20
+ raise RuntimeError("yfinance is not installed. Please install yfinance.")
21
+ ticker = yf.Ticker(symbol)
22
+ df = ticker.history(period=period, interval=interval, auto_adjust=False)
23
+ df = df.rename(columns={
24
+ 'Open': 'Open', 'High': 'High', 'Low': 'Low', 'Close': 'Close', 'Volume': 'Volume'
25
+ })
26
+ # Ensure index is datetime and sorted
27
+ # yfinance can return either Datetime or Date in index/columns. Normalize to Date index
28
+ if 'Datetime' in df.columns:
29
+ df = df.rename(columns={'Datetime': 'Date'})
30
+ df = df.reset_index().rename(columns={'Date': 'Date'})
31
+ if 'Date' not in df.columns:
32
+ df = df.rename(columns={'index': 'Date'})
33
+ df = df.set_index('Date')
34
+ df = df.sort_index()
35
+ return df[['Open', 'High', 'Low', 'Close']]
36
+
37
+
38
+ def _map_interval_to_alpha_vantage(interval: str) -> Optional[str]:
39
+ mapping = {
40
+ '1m': '1min',
41
+ '5m': '5min',
42
+ '15m': '15min',
43
+ '60m': '60min'
44
+ }
45
+ return mapping.get(interval)
46
+
47
+
48
+ def fetch_candles_alpha_vantage(symbol: str, interval: str, api_key: str) -> pd.DataFrame:
49
+ if requests is None:
50
+ raise RuntimeError("requests n'est pas installé. Veuillez installer requests.")
51
+ av_interval = _map_interval_to_alpha_vantage(interval) or '1min'
52
+ params = {
53
+ 'function': 'TIME_SERIES_INTRADAY',
54
+ 'symbol': symbol,
55
+ 'interval': av_interval,
56
+ 'apikey': api_key,
57
+ 'outputsize': 'compact'
58
+ }
59
+ url = 'https://www.alphavantage.co/query'
60
+ resp = requests.get(url, params=params, timeout=15)
61
+ resp.raise_for_status()
62
+ data = resp.json()
63
+ key = f'Time Series ({av_interval})'
64
+ if key not in data:
65
+ # Alpha Vantage rate limit or unsupported symbol
66
+ raise RuntimeError(f"Alpha Vantage réponse invalide: {list(data.keys())[:3]}")
67
+ ts = data[key]
68
+ rows = []
69
+ for ts_str, ohlc in ts.items():
70
+ # parse timestamps as local naive datetime
71
+ try:
72
+ ts_dt = dt.datetime.fromisoformat(ts_str)
73
+ except Exception:
74
+ # fallback format
75
+ ts_dt = dt.datetime.strptime(ts_str, '%Y-%m-%d %H:%M:%S')
76
+ rows.append({
77
+ 'Date': ts_dt,
78
+ 'Open': float(ohlc['1. open']),
79
+ 'High': float(ohlc['2. high']),
80
+ 'Low': float(ohlc['3. low']),
81
+ 'Close': float(ohlc['4. close'])
82
+ })
83
+ df = pd.DataFrame(rows).set_index('Date').sort_index()
84
+ return df[['Open', 'High', 'Low', 'Close']]
85
+
86
+
87
+ def fetch_latest_candles(symbol: str, period: str = '1d', interval: str = '1m') -> pd.DataFrame:
88
+ # If Alpha Vantage key is present and symbol looks supported, try AV first
89
+ av_key = os.getenv('ALPHAVANTAGE_API_KEY')
90
+ # Simple heuristic: AV does not support indices like ^GSPC, ^FCHI directly
91
+ looks_index = symbol.startswith('^')
92
+ if av_key and not looks_index:
93
+ try:
94
+ return fetch_candles_alpha_vantage(symbol, interval=interval, api_key=av_key)
95
+ except Exception:
96
+ # fall back to yfinance below
97
+ pass
98
+ # Retry yfinance a couple of times in case of DNS hiccups
99
+ last_err = None
100
+ for _ in range(2):
101
+ try:
102
+ return fetch_candles_yf(symbol, period=period, interval=interval)
103
+ except Exception as e:
104
+ last_err = e
105
+ time.sleep(1.0)
106
+ # final raise
107
+ raise last_err if last_err else RuntimeError('Unknown data fetch error')
108
+
109
+
Modèle IA/global_lstm_returns.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d5ac4c599c20327ec6ed61a2c12dce8d6703379692fc0ae32c1cf8f5661a2b80
3
+ size 745183