Stefan commited on
Commit
6937a88
·
1 Parent(s): 9201b91

Add aplication files

Browse files
Files changed (9) hide show
  1. .DS_Store +0 -0
  2. Dockerfile +18 -0
  3. app/.DS_Store +0 -0
  4. app/__init__.py +0 -0
  5. app/data-formatted.csv +0 -0
  6. app/models.py +0 -0
  7. app/routes.py +262 -0
  8. main.py +26 -0
  9. requirements.txt +28 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use the official Python image
2
+ FROM python:3.11-slim
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy requirements and install dependencies
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Copy the rest of the application
12
+ COPY . .
13
+
14
+ # Expose the port FastAPI will run on
15
+ EXPOSE 7860
16
+
17
+ # Run the FastAPI app with Uvicorn
18
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
app/.DS_Store ADDED
Binary file (6.15 kB). View file
 
app/__init__.py ADDED
File without changes
app/data-formatted.csv ADDED
The diff for this file is too large to render. See raw diff
 
app/models.py ADDED
File without changes
app/routes.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import Counter
2
+ from fastapi import APIRouter
3
+ import pandas as pd
4
+ import numpy as np
5
+ import requests
6
+ from bs4 import BeautifulSoup
7
+ import os
8
+
9
+ router = APIRouter()
10
+ data = pd.read_csv('app/data-formatted.csv')
11
+ HF_API_KEY = os.getenv("HF_API_KEY")
12
+
13
+
14
+ def getBerzaNews(symbol):
15
+ url = f'https://www.mse.mk/en/symbol/{symbol}'
16
+ response = requests.get(url)
17
+ content = BeautifulSoup(response.text, 'html.parser')
18
+
19
+ # finding links to news
20
+ aElements = content.find_all('a', href=True)
21
+ newsLinks = [link['href'] for link in aElements if link['href'].startswith('/en/news')]
22
+
23
+ news = []
24
+ for link in newsLinks:
25
+ response = requests.get(f'https://www.mse.mk{link}')
26
+ content = BeautifulSoup(response.text, 'html.parser')
27
+ try:
28
+ # print(content.find(id='content').text)
29
+ # print('-----------------------------------')
30
+ news.append(content.find(id='content').text)
31
+ except Exception as e:
32
+ continue
33
+
34
+ return news
35
+
36
+
37
+
38
+ def analyzeSentiment(symbol):
39
+ API_URL = "https://api-inference.huggingface.co/models/mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis"
40
+ headers = {"Authorization": f"Bearer {HF_API_KEY}"}
41
+
42
+ def query(text):
43
+ payload = {"inputs": text}
44
+ response = requests.post(API_URL, headers=headers, json=payload)
45
+ if response.status_code != 200:
46
+ print(f"Error: {response.status_code}, {response.text}")
47
+ return None
48
+ return response.json()
49
+
50
+ def getMax(result):
51
+ # Ensure the response has the expected structure
52
+ if not result or not isinstance(result, list):
53
+ return "neutral" # Default to neutral if the response is invalid
54
+
55
+ result = result[0] if isinstance(result[0], list) else result
56
+ max_label = max(result, key=lambda x: x['score'])['label']
57
+ return max_label
58
+
59
+ # Fetch news articles
60
+ texts = getBerzaNews(symbol)
61
+ if not texts:
62
+ return 'No news for ' + symbol
63
+
64
+ # Collect sentiment labels
65
+ sentiment_labels = []
66
+ for text in texts:
67
+ try:
68
+ result = query(text) # Query the Hugging Face API
69
+ if result:
70
+ sentiment_label = getMax(result) # Get the label
71
+ sentiment_labels.append(sentiment_label)
72
+ except Exception as e:
73
+ print(f"Error processing text: {e}")
74
+ continue
75
+
76
+ if not sentiment_labels:
77
+ return 'No news for ' + symbol
78
+
79
+ # Count occurrences of each sentiment
80
+ sentiment_counts = Counter(sentiment_labels)
81
+
82
+ # Find the sentiment with the most occurrences
83
+ most_common = sentiment_counts.most_common()
84
+
85
+ if len(most_common) == 0:
86
+ return "neutral" # Default to neutral if no sentiments found
87
+
88
+ # Check for ties
89
+ max_count = most_common[0][1]
90
+ top_sentiments = [sentiment for sentiment, count in most_common if count == max_count]
91
+
92
+ if len(top_sentiments) > 1:
93
+ return "neutral" # Return neutral in case of ties
94
+ return top_sentiments[0]
95
+
96
+ def predict_future_price(input_data):
97
+ input_data = input_data.drop(columns=['DATE'])
98
+ data_to_dictionary = input_data.to_dict(orient='list')
99
+
100
+ url = 'https://stefan155-das-lstm-model-api.hf.space/predict/'
101
+
102
+ payload = {
103
+ "input_data": data_to_dictionary,
104
+ }
105
+
106
+ response = requests.post(url, json=payload)
107
+ return response.json()['prediction']
108
+
109
+
110
+ # Function to resample data for timeframes
111
+ def resample_data(data, timeframe):
112
+ data["DATE"] = pd.to_datetime(data["DATE"]) # Ensure DATE is in datetime format
113
+ data = data.set_index("DATE") # Set DATE as the index
114
+
115
+ # Select only numeric columns for resampling
116
+ numeric_columns = data.select_dtypes(include=["number"]).columns
117
+ non_numeric_columns = data.select_dtypes(exclude=["number"]).columns
118
+
119
+ if timeframe == "1D":
120
+ resampled_data = data[numeric_columns].asfreq("D").fillna(method="ffill")
121
+ elif timeframe == "1W":
122
+ resampled_data = data[numeric_columns].resample("W").mean().fillna(0)
123
+ elif timeframe == "1M":
124
+ resampled_data = data[numeric_columns].resample("M").mean().fillna(0)
125
+ else:
126
+ raise ValueError("Invalid timeframe. Choose '1D', '1W', or '1M'.")
127
+
128
+ # Reset the index to bring DATE back as a column
129
+ resampled_data = resampled_data.reset_index()
130
+
131
+ # Reattach non-numeric columns (e.g., COMPANY)
132
+ if not non_numeric_columns.empty:
133
+ non_numeric_data = data[non_numeric_columns].reset_index().drop_duplicates(subset="DATE")
134
+ resampled_data = resampled_data.merge(non_numeric_data, on="DATE", how="left")
135
+
136
+ return resampled_data
137
+
138
+
139
+ # Function to calculate technical indicators
140
+ def calculate_technical_indicators(data, column="PRICE OF LAST TRANSACTION"):
141
+ data = data.sort_values(by="DATE").reset_index(drop=True)
142
+
143
+ # Oscillators
144
+ delta = data[column].diff()
145
+ gain = np.where(delta > 0, delta, 0)
146
+ loss = np.where(delta < 0, -delta, 0)
147
+ avg_gain = pd.Series(gain).rolling(window=14).mean()
148
+ avg_loss = pd.Series(loss).rolling(window=14).mean()
149
+ rs = avg_gain / avg_loss
150
+ data["RSI"] = 100 - (100 / (1 + rs))
151
+
152
+ # MACD
153
+ data["EMA12"] = data[column].ewm(span=12, adjust=False).mean()
154
+ data["EMA26"] = data[column].ewm(span=26, adjust=False).mean()
155
+ data["MACD"] = data["EMA12"] - data["EMA26"]
156
+
157
+ # Stochastic Oscillator
158
+ data["L14"] = data[column].rolling(window=14).min()
159
+ data["H14"] = data[column].rolling(window=14).max()
160
+ data["Stochastic"] = (data[column] - data["L14"]) / (data["H14"] - data["L14"]) * 100
161
+
162
+ # Williams %R
163
+ data["Williams %R"] = (data["H14"] - data[column]) / (data["H14"] - data["L14"]) * -100
164
+
165
+ # Rate of Change
166
+ data["ROC"] = data[column].pct_change(periods=12)
167
+
168
+ # Moving Averages
169
+ for window in [10, 20, 50]:
170
+ data[f"SMA{window}"] = data[column].rolling(window=window).mean()
171
+ data[f"EMA{window}"] = data[column].ewm(span=window, adjust=False).mean()
172
+
173
+ # Meters
174
+ oscillators_meter = "STRONG BUY" if data["RSI"].iloc[-1] > 70 else "NEUTRAL"
175
+ moving_averages_meter = "STRONG BUY" if data[f"SMA10"].iloc[-1] < data[column].iloc[-1] else "SELL"
176
+
177
+ return data, oscillators_meter, moving_averages_meter
178
+
179
+
180
+
181
+
182
+ @router.get("/stock-data/{ticker}")
183
+ async def get_stock_data(ticker: str):
184
+ print(f"Fetching data for ticker: {ticker}")
185
+
186
+ stock_data = data[data["COMPANY"] == ticker]
187
+ if stock_data.empty:
188
+ print("No data found for the given ticker.")
189
+ return {"error": "Ticker not found"}
190
+
191
+ # Process for each timeframe
192
+ timeframes = ["1D", "1W", "1M"]
193
+ timeframe_results = {}
194
+ for timeframe in timeframes:
195
+ try:
196
+ resampled_data = resample_data(stock_data, timeframe)
197
+
198
+ # Replace NaN/Inf/-Inf in resampled data
199
+ resampled_data.replace([np.inf, -np.inf, np.nan], 0, inplace=True)
200
+
201
+ indicators_data, oscillators_meter, moving_averages_meter = calculate_technical_indicators(resampled_data)
202
+
203
+ # Replace NaN/Inf/-Inf in indicators_data
204
+ indicators_data.replace([np.inf, -np.inf, np.nan], 0, inplace=True)
205
+
206
+ price_prediction = predict_future_price(indicators_data)
207
+ market_news_evaluation = analyzeSentiment(ticker)
208
+ # market_news_evaluation = 'neutral'
209
+
210
+
211
+ timeframe_results[timeframe] = {
212
+ "Price Prediction": price_prediction,
213
+ "Market News Evaluation": market_news_evaluation,
214
+ "GraphData": indicators_data[["DATE", "PRICE OF LAST TRANSACTION"]].to_dict(orient="records"),
215
+ "Oscillators": {
216
+ "RSI": indicators_data["RSI"].iloc[-1],
217
+ "MACD": indicators_data["MACD"].iloc[-1],
218
+ "Stochastic Oscillator": indicators_data["Stochastic"].iloc[-1],
219
+ "Williams %R": indicators_data["Williams %R"].iloc[-1],
220
+ "Rate of Change": indicators_data["ROC"].iloc[-1],
221
+ "METER": oscillators_meter,
222
+ },
223
+ "Moving Averages": {
224
+ "SMA10": indicators_data["SMA10"].iloc[-1],
225
+ "EMA10": indicators_data["EMA10"].iloc[-1],
226
+ "SMA20": indicators_data["SMA20"].iloc[-1],
227
+ "EMA20": indicators_data["EMA20"].iloc[-1],
228
+ "SMA50": indicators_data["SMA50"].iloc[-1],
229
+ "METER": moving_averages_meter,
230
+ },
231
+ }
232
+ except Exception as e:
233
+ print(f"Error processing timeframe {timeframe}: {e}")
234
+ timeframe_results[timeframe] = {"error": str(e)}
235
+
236
+ # Replace NaN/Inf/-Inf in stock_data
237
+ stock_data.replace([np.inf, -np.inf, np.nan], 0, inplace=True)
238
+
239
+ # reverse stock data
240
+ stock_data = stock_data.iloc[::-1]
241
+
242
+ # Construct response
243
+ response = {
244
+ "Ticker": ticker,
245
+ "Company Name": stock_data["COMPANY"].iloc[0],
246
+ "Current Price": stock_data["AVERAGE PRICE"].iloc[-1],
247
+ "MAX Price": stock_data["PRICE OF LAST TRANSACTION"].max(),
248
+ "MIN Price": stock_data["PRICE OF LAST TRANSACTION"].min(),
249
+ "Volume": stock_data["QUANTITY"].sum() if "QUANTITY" in stock_data.columns else None,
250
+ "REVENUE": stock_data[
251
+ "REVENUE IN BEST DENARS"].sum() if "REVENUE IN BEST DENARS" in stock_data.columns else None,
252
+ "AVERAGE PRICE": stock_data["AVERAGE PRICE"].iloc[-1],
253
+ "Timeframes": timeframe_results,
254
+ }
255
+
256
+ # Ensure JSON compliance
257
+ response = {
258
+ key: (float(value) if isinstance(value, (np.float64, np.int64)) else value)
259
+ for key, value in response.items()
260
+ }
261
+
262
+ return response
main.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from app.routes import router as api_router
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+
5
+
6
+ # Initialize the FastAPI app
7
+ app = FastAPI(
8
+ title="API for the DAS Homework",
9
+ description="This api is is used to serve for the DAS Homework web application",
10
+ version="1.0.0"
11
+ )
12
+
13
+ app.add_middleware(
14
+ CORSMiddleware,
15
+ allow_origins=["https://das-prototype.web.app"],
16
+ allow_credentials=True,
17
+ allow_methods=["*"], # Allow all HTTP methods (e.g., GET, POST)
18
+ allow_headers=["*"], # Allow all headers
19
+ )
20
+
21
+ # Include the API router
22
+ app.include_router(api_router)
23
+
24
+ @app.get("/")
25
+ async def root():
26
+ return {"message": "API for the DAS Homework"}
requirements.txt ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==2.1.0
2
+ annotated-types==0.7.0
3
+ anyio==4.8.0
4
+ beautifulsoup4==4.12.3
5
+ certifi==2024.12.14
6
+ charset-normalizer==3.4.1
7
+ click==8.1.8
8
+ fastapi==0.115.6
9
+ h11==0.14.0
10
+ idna==3.10
11
+ markdown-it-py==3.0.0
12
+ mdurl==0.1.2
13
+ numpy==2.0.2
14
+ packaging==24.2
15
+ pandas==2.2.3
16
+ pydantic==2.10.5
17
+ pydantic_core==2.27.2
18
+ python-dateutil==2.9.0.post0
19
+ pytz==2024.2
20
+ requests==2.32.3
21
+ six==1.17.0
22
+ sniffio==1.3.1
23
+ soupsieve==2.6
24
+ starlette==0.41.3
25
+ typing_extensions==4.12.2
26
+ tzdata==2024.2
27
+ urllib3==2.3.0
28
+ uvicorn==0.34.0