datamk commited on
Commit
40cb3dd
·
verified ·
1 Parent(s): 854eb1c

Upload 12 files

Browse files
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,22 @@
1
- ---
2
- title: Trading Pulse
3
- emoji: 🌍
4
- colorFrom: green
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: trading-news-app
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Stock Sentiment Dashboard
3
+ emoji: 📈
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ app_port: 3001
8
+ pinned: false
9
+ license: isc
10
+ ---
11
+
12
+ # Bull & Bear | Stock Sentiment Dashboard
13
+
14
+ A real-time dashboard tracking sentiment for Indian stocks using RSS feeds and news analysis.
15
+
16
+ ## Deployment to Hugging Face Spaces
17
+
18
+ 1. Create a new Space on [Hugging Face](https://huggingface.co/new-space).
19
+ 2. Select **Docker** as the SDK.
20
+ 3. Push these files to your Space repository.
21
+
22
+ The application will be available at your Space's URL.
data/news_sentiment.json CHANGED
The diff for this file is too large to render. See raw diff
 
data/sentiment_trends.json CHANGED
@@ -1,4 +1,48 @@
1
  [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  {
3
  "time": "2026-02-17T22:43:48.070Z",
4
  "score": 2.230277185501066
@@ -154,49 +198,5 @@
154
  {
155
  "time": "2026-03-18T19:30:14.558Z",
156
  "score": 1.6160058737151248
157
- },
158
- {
159
- "time": "2026-03-18T19:35:14.639Z",
160
- "score": 1.6067746686303388
161
- },
162
- {
163
- "time": "2026-03-18T19:40:14.019Z",
164
- "score": 1.5944158706833211
165
- },
166
- {
167
- "time": "2026-03-18T19:45:14.006Z",
168
- "score": 1.5812407680945346
169
- },
170
- {
171
- "time": "2026-03-18T19:50:13.749Z",
172
- "score": 1.6642174871418074
173
- },
174
- {
175
- "time": "2026-03-18T19:55:13.882Z",
176
- "score": 1.6035372144436257
177
- },
178
- {
179
- "time": "2026-03-18T20:00:18.082Z",
180
- "score": 1.6701337295690937
181
- },
182
- {
183
- "time": "2026-03-18T20:05:14.160Z",
184
- "score": 1.6355489171023152
185
- },
186
- {
187
- "time": "2026-03-18T20:10:16.429Z",
188
- "score": 1.615958240119314
189
- },
190
- {
191
- "time": "2026-03-18T20:15:14.782Z",
192
- "score": 1.6445108289768484
193
- },
194
- {
195
- "time": "2026-03-18T20:20:15.280Z",
196
- "score": 1.6149253731343283
197
- },
198
- {
199
- "time": "2026-03-18T20:25:12.859Z",
200
- "score": 1.604026845637584
201
  }
202
  ]
 
1
  [
2
+ {
3
+ "time": "2026-02-17T21:47:06.919Z",
4
+ "score": 2.100418410041841
5
+ },
6
+ {
7
+ "time": "2026-02-17T21:50:05.156Z",
8
+ "score": 2.1838842975206614
9
+ },
10
+ {
11
+ "time": "2026-02-17T21:55:04.627Z",
12
+ "score": 2.178794178794179
13
+ },
14
+ {
15
+ "time": "2026-02-17T22:00:04.836Z",
16
+ "score": 2.1621052631578945
17
+ },
18
+ {
19
+ "time": "2026-02-17T22:05:04.721Z",
20
+ "score": 2.1461377870563676
21
+ },
22
+ {
23
+ "time": "2026-02-17T22:10:04.944Z",
24
+ "score": 2.1596638655462184
25
+ },
26
+ {
27
+ "time": "2026-02-17T22:15:06.180Z",
28
+ "score": 2.214723926380368
29
+ },
30
+ {
31
+ "time": "2026-02-17T22:20:04.427Z",
32
+ "score": 2.2378947368421054
33
+ },
34
+ {
35
+ "time": "2026-02-17T22:25:04.187Z",
36
+ "score": 2.259336099585062
37
+ },
38
+ {
39
+ "time": "2026-02-17T22:30:04.101Z",
40
+ "score": 2.236625514403292
41
+ },
42
+ {
43
+ "time": "2026-02-17T22:35:04.402Z",
44
+ "score": 2.1740976645435244
45
+ },
46
  {
47
  "time": "2026-02-17T22:43:48.070Z",
48
  "score": 2.230277185501066
 
198
  {
199
  "time": "2026-03-18T19:30:14.558Z",
200
  "score": 1.6160058737151248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
  ]
public/app.js CHANGED
@@ -1,26 +1,14 @@
1
  let allNews = [];
 
2
  let currentFilter = 'all';
3
  let currentSource = 'all';
 
 
4
 
5
  const IMPACT_KEYWORDS = {
6
- high: ['crash', 'crisis', 'urgent', 'breaking', 'collapsed', 'warns', 'surge', 'plunge', 'lockdown', 'rating', 'result', 'focus',
7
- 'zoom', 'soar', 'rally', 'breakout', 'outperform', 'upper circuit', '52-week high', 'gap up', 'momentum',
8
- 'explosive growth', 'all-time high', 'bulls charge', 'fii inflow', 'dii inflow', 'bulk deal', 'block deal',
9
- 'stake increase', 'fresh longs', 'short covering', 'promoter buying', 'fii buy', 'institutional support',
10
- 'net buyers', 'order win', 'multibagger', 'guidance up', 'profit jump', 'ebitda expansion', 'dividend',
11
- 'bonus issue', 'stock split', 'buyback', 'turnaround', 'debt-free', 'revenue growth', 'target raised',
12
- 'brokerage upgrade', 'positive outlook', 'bottomed out', 'golden cross', 'to the moon', 'market favorite',
13
- 'accumulation', 'bull run', 'buying spree', 'strong buy', 'attractive valuation', 'value pick'],
14
  pos: ['profit', 'growth', 'record', 'dividend', 'buyback', 'partnership', 'acquired', 'expansion', 'jump', 'bullish', 'buy'],
15
- neg: ['loss', 'fall', 'slump', 'down', 'decline', 'investigation', 'scam', 'penalty', 'lawsuit', 'bearish', 'sell',
16
- 'slide', 'tumble', 'tanked', 'drift lower', 'lower circuit', '52-week low', 'gap down', 'death cross',
17
- 'correction', 'bear grip', 'blood bath', 'sell-off', 'drastic drop', 'fii outflow', 'dii outflow',
18
- 'stake sale', 'promoter pledging', 'dumped', 'bulk sell', 'block sell', 'net sellers', 'fresh shorts',
19
- 'short buildup', 'liquidation', 'exit strategy', 'profit miss', 'revenue de-growth', 'margin pressure',
20
- 'earnings miss', 'guidance cut', 'debt concerns', 'default', 'downgrade', 'brokerage cut',
21
- 'regulatory scrutiny', 'audit issues', 'corporate governance lapse', 'pledged shares', 'inventory pile-up',
22
- 'slowdown', 'bear run', 'avoid', 'overvalued', 'expensive valuation', 'dead cat bounce', 'value trap',
23
- 'panic selling', 'bottomless pit', 'bulls retreat', 'market correction', 'gloomy outlook', 'wait and watch']
24
  };
25
 
26
  async function fetchNews() {
@@ -28,8 +16,11 @@ async function fetchNews() {
28
  const response = await fetch('/api/news');
29
  const data = await response.json();
30
  allNews = data.news;
 
31
 
32
  renderDashboard();
 
 
33
  renderIndices(data.indices);
34
  } catch (error) {
35
  console.error('Error fetching news:', error);
@@ -129,6 +120,79 @@ function filterBySentiment(sentiment) {
129
  renderFeed();
130
  }
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
  function openSupportModal() {
134
  document.getElementById('support-modal').classList.add('active');
@@ -145,6 +209,8 @@ window.addEventListener('popstate', function (e) {
145
  }
146
  });
147
 
 
 
148
  fetchNews();
149
  setInterval(fetchNews, 30000);
150
  setInterval(fetchIndicesOnly, 10000);
 
1
  let allNews = [];
2
+ let sentimentTrend = [];
3
  let currentFilter = 'all';
4
  let currentSource = 'all';
5
+ let isSidebarHidden = true;
6
+ let pulseChart = null;
7
 
8
  const IMPACT_KEYWORDS = {
9
+ high: ['crash', 'crisis', 'urgent', 'breaking', 'collapsed', 'warns', 'surge', 'plunge', 'lockdown', 'rating', 'result', 'focus'],
 
 
 
 
 
 
 
10
  pos: ['profit', 'growth', 'record', 'dividend', 'buyback', 'partnership', 'acquired', 'expansion', 'jump', 'bullish', 'buy'],
11
+ neg: ['loss', 'fall', 'slump', 'down', 'decline', 'investigation', 'scam', 'penalty', 'lawsuit', 'bearish', 'sell']
 
 
 
 
 
 
 
 
12
  };
13
 
14
  async function fetchNews() {
 
16
  const response = await fetch('/api/news');
17
  const data = await response.json();
18
  allNews = data.news;
19
+ sentimentTrend = data.trend;
20
 
21
  renderDashboard();
22
+ renderFeedStatus(data.status);
23
+ renderPulseChart();
24
  renderIndices(data.indices);
25
  } catch (error) {
26
  console.error('Error fetching news:', error);
 
120
  renderFeed();
121
  }
122
 
123
+ function toggleSidebar() {
124
+ isSidebarHidden = !isSidebarHidden;
125
+ document.body.classList.toggle('sidebar-hidden', isSidebarHidden);
126
+ document.getElementById('toggle-icon').textContent = isSidebarHidden ? '▸' : '◂';
127
+ }
128
+
129
+ function renderFeedStatus(status) {
130
+ const sidebar = document.getElementById('sidebar');
131
+ const existingStatus = document.querySelector('.status-card');
132
+ if (existingStatus) existingStatus.remove();
133
+
134
+ const statusHtml = `
135
+ <div class="card status-card">
136
+ <div class="status-header">
137
+ <h3>Feed Monitoring</h3>
138
+ ${currentSource !== 'all' ? '<button class="clear-filter" onclick="filterBySource(\'all\')">Clear Filter</button>' : ''}
139
+ </div>
140
+ <div class="status-list">
141
+ ${Object.entries(status).map(([url, info]) => `
142
+ <div class="status-item ${currentSource === info.displayName ? 'active' : ''}"
143
+ onclick="filterBySource('${info.displayName}')">
144
+ <span class="status-dot" style="background: ${info.status === 'Success' ? 'var(--accent-green)' : 'var(--accent-red)'}"></span>
145
+ <span class="status-name">${info.displayName}</span>
146
+ <span class="status-label">${info.status}</span>
147
+ </div>
148
+ `).join('')}
149
+ </div>
150
+ </div>
151
+ `;
152
+ sidebar.insertAdjacentHTML('beforeend', statusHtml);
153
+ }
154
+
155
+ function filterBySource(source) {
156
+ currentSource = source;
157
+ renderFeed();
158
+ fetchNews();
159
+ }
160
+
161
+ function renderPulseChart() {
162
+ const ctx = document.getElementById('sentiment-chart');
163
+ if (!ctx || !sentimentTrend.length) return;
164
+
165
+ if (pulseChart) pulseChart.destroy();
166
+
167
+ pulseChart = new Chart(ctx, {
168
+ type: 'line',
169
+ data: {
170
+ labels: sentimentTrend.map(t => new Date(t.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })),
171
+ datasets: [{
172
+ label: 'Sentiment Velocity',
173
+ data: sentimentTrend.map(t => t.score),
174
+ borderColor: '#52afff',
175
+ backgroundColor: 'rgba(82, 175, 255, 0.1)',
176
+ borderWidth: 2,
177
+ tension: 0.4,
178
+ fill: true,
179
+ pointRadius: 0
180
+ }]
181
+ },
182
+ options: {
183
+ responsive: true,
184
+ maintainAspectRatio: false,
185
+ plugins: { legend: { display: false } },
186
+ scales: {
187
+ x: { display: false },
188
+ y: {
189
+ grid: { color: 'rgba(255,255,255,0.05)' },
190
+ ticks: { color: '#94a3b8', font: { size: 10 } }
191
+ }
192
+ }
193
+ }
194
+ });
195
+ }
196
 
197
  function openSupportModal() {
198
  document.getElementById('support-modal').classList.add('active');
 
209
  }
210
  });
211
 
212
+ document.body.classList.add('sidebar-hidden');
213
+ document.getElementById('toggle-icon').textContent = '▸';
214
  fetchNews();
215
  setInterval(fetchNews, 30000);
216
  setInterval(fetchIndicesOnly, 10000);
public/index.html CHANGED
@@ -9,7 +9,7 @@
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
11
  <link rel="stylesheet" href="style.css">
12
-
13
  </head>
14
 
15
  <body>
@@ -28,6 +28,9 @@
28
  </div>
29
 
30
  <div class="stats">
 
 
 
31
  <div class="live-status">
32
  <span class="live-pulse"></span>
33
  <span>LIVE</span>
@@ -35,25 +38,60 @@
35
  </div>
36
  </header>
37
 
38
- <section class="feed-section">
39
- <div class="feed-header">
40
- <div class="header-main">
41
- <h2 class="support-link" onclick="openSupportModal()">❤ Support</h2>
42
- <div class="filter-controls">
43
- <button class="filter-btn active" data-sentiment="all"
44
- onclick="filterBySentiment('all');">All</button>
45
- <button class="filter-btn" data-sentiment="Positive"
46
- onclick="filterBySentiment('Positive');">Bullish</button>
47
- <button class="filter-btn" data-sentiment="Negative"
48
- onclick="filterBySentiment('Negative');">Bearish</button>
 
 
 
 
 
 
 
 
 
 
 
49
  </div>
 
50
  </div>
51
- </div>
52
 
53
- <div id="news-feed" class="news-feed">
54
- <div class="loader">Synchronizing with market feeds...</div>
55
- </div>
56
- </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  </main>
58
 
59
  <div id="support-modal" class="support-modal" onclick="closeSupportModal()">
 
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
11
  <link rel="stylesheet" href="style.css">
12
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
13
  </head>
14
 
15
  <body>
 
28
  </div>
29
 
30
  <div class="stats">
31
+ <button class="sidebar-toggle" onclick="toggleSidebar();" title="Toggle Sidebar">
32
+ <span id="toggle-icon">◂</span>
33
+ </button>
34
  <div class="live-status">
35
  <span class="live-pulse"></span>
36
  <span>LIVE</span>
 
38
  </div>
39
  </header>
40
 
41
+ <div class="content-grid">
42
+ <aside class="sidebar" id="sidebar">
43
+ <div class="card gauge-card">
44
+ <h3>Market Signal (NIFTY)</h3>
45
+ <!-- TradingView Widget BEGIN -->
46
+ <div class="tradingview-widget-container">
47
+ <div class="tradingview-widget-container__widget"></div>
48
+ <script type="text/javascript"
49
+ src="https://s3.tradingview.com/external-embedding/embed-widget-technical-analysis.js"
50
+ async>
51
+ {
52
+ "interval": "1m",
53
+ "width": "100%",
54
+ "isTransparent": true,
55
+ "height": 450,
56
+ "symbol": "NSE:NIFTY",
57
+ "showIntervalTabs": true,
58
+ "displayMode": "single",
59
+ "locale": "en",
60
+ "colorTheme": "dark"
61
+ }
62
+ </script>
63
  </div>
64
+ <!-- TradingView Widget END -->
65
  </div>
 
66
 
67
+ <div class="card pulse-card">
68
+ <h3>Market Pulse (24h)</h3>
69
+ <div id="sentiment-chart-container">
70
+ <canvas id="sentiment-chart"></canvas>
71
+ </div>
72
+ </div>
73
+ </aside>
74
+
75
+ <section class="feed-section">
76
+ <div class="feed-header">
77
+ <div class="header-main">
78
+ <h2 class="support-link" onclick="openSupportModal()">❤ Support</h2>
79
+ <div class="filter-controls">
80
+ <button class="filter-btn active" data-sentiment="all"
81
+ onclick="filterBySentiment('all');">All</button>
82
+ <button class="filter-btn" data-sentiment="Positive"
83
+ onclick="filterBySentiment('Positive');">Bullish</button>
84
+ <button class="filter-btn" data-sentiment="Negative"
85
+ onclick="filterBySentiment('Negative');">Bearish</button>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <div id="news-feed" class="news-feed">
91
+ <div class="loader">Synchronizing with market feeds...</div>
92
+ </div>
93
+ </section>
94
+ </div>
95
  </main>
96
 
97
  <div id="support-modal" class="support-modal" onclick="closeSupportModal()">