feliponi commited on
Commit
2293f58
·
1 Parent(s): 012aaeb

Release 0.002

Browse files
__pycache__/stocks.cpython-311.pyc CHANGED
Binary files a/__pycache__/stocks.cpython-311.pyc and b/__pycache__/stocks.cpython-311.pyc differ
 
analysis/__pycache__/fundamental.cpython-311.pyc ADDED
Binary file (1.46 kB). View file
 
analysis/__pycache__/sentiment.cpython-311.pyc ADDED
Binary file (4.84 kB). View file
 
analysis/__pycache__/technical.cpython-311.pyc ADDED
Binary file (2.65 kB). View file
 
analysis/fundamental.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # analysis/fundamental.py
2
+ from data.api_client import YahooFinanceClient
3
+
4
+ class FundamentalAnalyzer:
5
+ @staticmethod
6
+ def analyze(ticker):
7
+ info = YahooFinanceClient.get_company_info(ticker)
8
+ return {
9
+ 'trailingPE': float(info.get('trailingPE', 0)),
10
+ 'sectorPE': float(info.get('sectorPE', 0)) if info.get('sectorPE') else None,
11
+ 'revenueGrowth': float(info.get('revenueGrowth', 0)),
12
+ 'profitMargins': float(info.get('profitMargins', 0)),
13
+ 'debtToEquity': float(info.get('debtToEquity', 0)),
14
+ 'shortName': info.get('shortName')
15
+ }
analysis/sentiment.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
4
+ from scipy.stats import zscore
5
+
6
+ class SentimentAnalyzer:
7
+ def __init__(self):
8
+ self.models = {
9
+ 'finbert': AutoModelForSequenceClassification.from_pretrained("yiyanghkust/finbert-tone"),
10
+ 'financial_sentiment': AutoModelForSequenceClassification.from_pretrained("ahmedrachid/FinancialBERT-Sentiment-Analysis")
11
+ }
12
+ self.tokenizers = {
13
+ 'finbert': AutoTokenizer.from_pretrained("yiyanghkust/finbert-tone"),
14
+ 'financial_sentiment': AutoTokenizer.from_pretrained("ahmedrachid/FinancialBERT-Sentiment-Analysis")
15
+ }
16
+ self.max_length = 512 # Limite do modelo
17
+
18
+ def chunk_text(self, text, tokenizer):
19
+ tokens = tokenizer.encode(text, truncation=False)
20
+ return [tokens[i:i+self.max_length] for i in range(0, len(tokens), self.max_length)]
21
+
22
+ def preprocess_text(self, item):
23
+ title = str(item.get('title', '')).strip()
24
+ content = str(item.get('content', '')).strip()
25
+ text = f"{title} {content}".strip()
26
+ return text if text else None
27
+
28
+
29
+ def analyze(self, news):
30
+ if not news:
31
+ return {'negative': 0.33, 'neutral': 0.33, 'positive': 0.33}
32
+
33
+ sentiment_scores = []
34
+
35
+ for item in news:
36
+ if not isinstance(item, dict):
37
+ continue
38
+
39
+ text = self.preprocess_text(item)
40
+ if not text:
41
+ continue
42
+
43
+ tokenizer = self.tokenizers['financial_sentiment']
44
+ model = self.models['financial_sentiment']
45
+
46
+ tokenized_chunks = self.chunk_text(text, tokenizer)
47
+ chunk_scores = []
48
+
49
+ for chunk in tokenized_chunks:
50
+ inputs = tokenizer.decode(chunk, skip_special_tokens=True)
51
+ inputs = tokenizer(inputs, return_tensors="pt", truncation=True, max_length=self.max_length)
52
+ outputs = model(**inputs)
53
+ probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)
54
+ chunk_scores.append(probabilities.detach().numpy()[0])
55
+
56
+ if chunk_scores:
57
+ sentiment_scores.append(np.mean(chunk_scores, axis=0))
58
+
59
+ if not sentiment_scores:
60
+ return {'negative': 0.33, 'neutral': 0.33, 'positive': 0.33}
61
+
62
+ # Filtro de outliers
63
+ filtered_scores = [s for s in sentiment_scores if np.abs(zscore(s)).max() < 2]
64
+ avg_sentiment = np.mean(filtered_scores, axis=0) if filtered_scores else np.mean(sentiment_scores, axis=0)
65
+
66
+ return {'negative': float(avg_sentiment[0]), 'neutral': float(avg_sentiment[1]), 'positive': float(avg_sentiment[2])}
analysis/technical.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # analysis/technical.py
2
+ import numpy as np
3
+ from data.api_client import YahooFinanceClient
4
+
5
+ class TechnicalAnalyzer:
6
+ @staticmethod
7
+ def calculate_rsi(data, window=14):
8
+ delta = data['Close'].diff()
9
+ gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
10
+ loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
11
+ rs = gain / loss
12
+ return 100 - (100 / (1 + rs))
13
+
14
+ @staticmethod
15
+ def calculate_sma(data, window):
16
+ return data['Close'].rolling(window=window).mean()
17
+
18
+ def analyze(self, ticker):
19
+ data = YahooFinanceClient.download_data(ticker, period='1y')
20
+ if data.empty or data.shape[0] < 50:
21
+ return None
22
+
23
+ sma_50 = self.calculate_sma(data, 50).iloc[-1].item()
24
+ current_price = data['Close'].iloc[-1].item()
25
+ rsi = self.calculate_rsi(data).iloc[-1].item()
26
+
27
+ return {
28
+ 'price': current_price,
29
+ 'sma_50': sma_50,
30
+ 'price_vs_sma': (current_price / sma_50) - 1,
31
+ 'rsi': rsi if not np.isnan(rsi) else 50,
32
+ 'trend': 'bullish' if current_price > sma_50 else 'bearish'
33
+ }
app.py CHANGED
@@ -1,7 +1,9 @@
 
1
  import gradio as gr
2
  from datetime import datetime, timedelta
3
- import stocks as st
4
-
 
5
 
6
  class GradioInterface:
7
  def __init__(self, pipeline):
@@ -12,20 +14,19 @@ class GradioInterface:
12
  'rsi_lower': 30,
13
  'sma_short': 50,
14
  'sma_long': 200,
15
- 'max_loss_percent': 0.02,
16
- 'take_profit_percent': 0.05,
17
- 'position_size': 0.1,
18
- 'atr_period': 14,
19
  'atr_multiplier': 3,
20
- 'confidence_threshold' : 0.7,
21
- 'sentiment_threshold' : 0.5
22
  }
23
-
24
  def create_settings_interface(self):
25
  with gr.Blocks() as settings_interface:
26
  with gr.Row():
27
  with gr.Column():
28
- # Parâmetros da Estratégia de Trading
29
  gr.Markdown("### Parameters for Trading Strategy")
30
  inputs = {}
31
  inputs['rsi_period'] = gr.Number(value=14, label="RSI Period", minimum=1)
@@ -33,68 +34,41 @@ class GradioInterface:
33
  inputs['rsi_lower'] = gr.Number(value=30, label="RSI Lower Limit", minimum=0, maximum=100)
34
  inputs['sma_short'] = gr.Number(value=50, label="SMA Short (period)")
35
  inputs['sma_long'] = gr.Number(value=200, label="SMA Long (period)")
36
- inputs['max_loss_percent'] = gr.Slider(0, 0.5, value=0.02, step=0.01, label="Stop Loss (%)")
37
- inputs['take_profit_percent'] = gr.Slider(0, 0.5, value=0.05, step=0.01, label="Take Profit (%)")
38
- inputs['position_size'] = gr.Slider(0.01, 1.0, value=0.1, step=0.01, label="Position Size(%)")
39
  inputs['atr_period'] = gr.Number(value=14, label="ATR Period")
40
  inputs['atr_multiplier'] = gr.Number(value=3, label="ATR Multiplier")
41
- inputs['confidence_threshold'] = gr.Number(value=70, label="Confidence Threshold(%)", minimum=0, maximum=100)
42
- inputs['sentiment_threshold'] = gr.Number(value=50, label="Sentiment Threshold(%)", minimum=0, maximum=100)
43
- save_btn = gr.Button("Save Configuration")
44
- # Adicionando a explicação em Markdown
45
  gr.Markdown("""
46
  ## 📊 Explanation of Trading Strategy Parameters
47
-
48
  These parameters configure technical indicators to assist in buy and sell decisions for assets.
49
-
50
  ### **📉 RSI (Relative Strength Index)**
51
- The RSI is used to measure the strength of an asset's movement.
52
-
53
- - **`rsi_period` (14)** → Number of periods to calculate the RSI (default: 14).
54
- - **`rsi_upper` (70)** → If the RSI is greater than this value, it may indicate overbought conditions (sell signal).
55
- - **`rsi_lower` (30)** → If the RSI is less than this value, it may indicate oversold conditions (buy signal).
56
-
57
- ---
58
-
59
  ### **📈 Simple Moving Averages (SMA)**
60
- Indicators that smooth prices over time.
61
-
62
- - **`sma_short` (50)** → Short-term moving average, used to capture short-term trends.
63
- - **`sma_long` (200)** → Long-term moving average, used to capture long-term trends.
64
-
65
- ---
66
-
67
  ### **📉 Risk Management**
68
-
69
- - **`max_loss_percent` (0.02)** → Stop Loss (loss limit). If the price falls more than 2%, the position is closed.
70
- - **`take_profit_percent` (0.05)** → Take Profit (profit limit). If the price rises 5%, the position is closed.
71
- - **`position_size` (0.1)** → Proportion of total capital to be used in a trade (10% of the balance).
72
-
73
- ---
74
-
75
  ### **📊 ATR (Average True Range) - Volatility**
76
- The ATR is used to measure the volatility of an asset.
77
-
78
  - **`atr_period` (14)** → Number of periods to calculate the ATR.
79
- - **`atr_multiplier` (3)** → ATR multiplier, often used to define dynamic stop loss.
80
-
81
- ---
82
-
83
- ## **🚀 How Do These Parameters Affect the Strategy?**
84
-
85
- - **If `rsi_lower` is lower (e.g., 20), the strategy will buy in more oversold regions.**
86
- - **If `max_loss_percent` is too small, it may close trades prematurely.**
87
- - **If `atr_multiplier` is larger, the stop loss will be wider, allowing for more volatility.**
88
- - **If `sma_short` and `sma_long` are far apart, entries will be more conservative.**
89
  """)
90
-
91
  save_btn.click(
92
  self.save_settings,
93
  inputs=[v for v in inputs.values()],
94
  outputs=None
95
  )
96
  return settings_interface
97
-
98
  def save_settings(self, *args):
99
  params = [
100
  'rsi_period', 'rsi_upper', 'rsi_lower',
@@ -102,57 +76,54 @@ class GradioInterface:
102
  'take_profit_percent', 'position_size',
103
  'atr_period', 'atr_multiplier', 'confidence_threshold', 'sentiment_threshold'
104
  ]
105
-
106
  self.strategy_params = dict(zip(params, args))
107
- print("Parâmetros atualizados:", self.strategy_params)
108
  return gr.Info("Settings saved!")
109
-
110
  def create_main_interface(self):
111
  with gr.Blocks() as main_interface:
112
  with gr.Row():
113
  with gr.Column():
114
  ticker_input = gr.Text(label="Ticker (ex: VALE)", placeholder="Insert a stock ticker based on Yahoo Finance")
115
- api_key_input = gr.Textbox(label="API Key (required)", placeholder="Insert your API Key https://newsapi.org/")
116
  fetch_new = gr.Dropdown([True, False], label="Check last news information online (Requires API)?", value=False)
117
- initial_investment = gr.Number(10000, label="Initial Investiment (USD)")
 
118
  years_back = gr.Number(5, label="Historical Data (years back)")
119
- commission = gr.Number(0.001, label="Trade Commission (%)")
120
  run_btn = gr.Button("Execute Analysis")
121
  with gr.Column():
122
  plot_output = gr.Plot()
123
  with gr.Row():
124
- # Adicionar uma saída para os resultados
125
  output_md = gr.Markdown()
126
  with gr.Row():
127
- # Adicionar uma saída para os resultados
128
  output_ops = gr.Markdown()
129
-
130
  run_btn.click(
131
  self.run_full_analysis,
132
  inputs=[ticker_input, fetch_new, initial_investment, years_back, commission, api_key_input],
133
  outputs=[output_md, output_ops, plot_output]
134
  )
135
  return main_interface
136
-
137
  def run_full_analysis(self, ticker, fetch_new, initial_investment, years_back, commission, api_key):
138
  # Atualizar os parâmetros da pipeline
139
- self.pipeline.set_sentiment_threshold(float(self.strategy_params['sentiment_threshold'])/100)
140
- self.pipeline.set_confidence_threshold(float(self.strategy_params['confidence_threshold'])/100)
141
-
142
  # Executar análise
143
  result = self.pipeline.analyze_company(
144
  ticker=ticker,
145
  news_api_key=api_key,
146
  fetch_new=fetch_new
147
  )
148
-
149
  if not result:
150
- return "Something wrong is not right :)", None
151
-
152
  # Configurar simulação
153
  end_date = datetime.now()
154
- start_date = end_date - timedelta(days=int(years_back*365))
155
-
156
  # Criar estratégia personalizada com os parâmetros
157
  custom_strategy_params = {
158
  'rsi_period': int(self.strategy_params['rsi_period']),
@@ -160,27 +131,18 @@ class GradioInterface:
160
  'rsi_lower': int(self.strategy_params['rsi_lower']),
161
  'sma_short': int(self.strategy_params['sma_short']),
162
  'sma_long': int(self.strategy_params['sma_long']),
163
- 'max_loss_percent': float(self.strategy_params['max_loss_percent']),
164
- 'take_profit_percent': float(self.strategy_params['take_profit_percent']),
165
- 'position_size': float(self.strategy_params['position_size']),
166
  'atr_period': int(self.strategy_params['atr_period']),
167
  'atr_multiplier': int(self.strategy_params['atr_multiplier']),
168
- 'confidence_threshold' : float(self.strategy_params['confidence_threshold'])/100,
169
- 'sentiment_threshold' : float(self.strategy_params['sentiment_threshold'])/100
170
  }
171
-
172
- # Criar uma instância de Progress
173
- progress = gr.Progress()
174
 
175
- # Atualizar progresso
176
- progress(0.3, desc="Preparing simulation...")
177
-
178
  # Executar simulação
179
- bt_integration = st.BacktraderIntegration(analysis_result=result,strategy_params=custom_strategy_params)
180
  bt_integration.add_data_feed(ticker, start_date, end_date)
181
-
182
- progress(0.6, desc="Executing simulation...")
183
-
184
  final_value, operation_logs = bt_integration.run_simulation(
185
  initial_cash=initial_investment,
186
  commission=commission
@@ -204,23 +166,12 @@ class GradioInterface:
204
  date = parts[0].strip()
205
  details = ", ".join(parts[1:])
206
  formatted_logs.append(f"- 📈 **Result** ({date}): {details}")
207
- elif "Final Portfolio Value" in log:
208
- continue
209
-
210
-
211
  # Adicionar seção de logs na saída
212
  output_ops = "### Log :\n\n" + "\n".join(formatted_logs)
213
-
214
- progress(0.9, desc="Generating final result...")
215
-
216
- # Extrair os valores do JSON de sentimento
217
- sentiment = result['sentiment']['sentiment']
218
- negative_sentiment = sentiment.get('negative', 0.0)
219
- neutral_sentiment = sentiment.get('neutral', 0.0)
220
- positive_sentiment = sentiment.get('positive', 0.0)
221
 
222
-
223
  # Gerar saída formatada em Markdown
 
224
  output = f"""
225
  ## Recommendation: {result['recommendation']}
226
 
@@ -228,58 +179,43 @@ class GradioInterface:
228
 
229
  ## Simulation Results:
230
  - **Initial Investment**: ${initial_investment:.2f}
231
- - **Simulation Summary**: {(final_value/initial_investment-1)*100:.2f}%
232
  - **Final Portfolio Value**: ${final_value:.2f}
233
 
234
  ### Details:
235
 
236
- - **Negative Sentiment**: {negative_sentiment:.2%}
237
- - **Neutral Sentiment**: {neutral_sentiment:.2%}
238
- - **Positive Sentiment**: {positive_sentiment:.2%}
239
 
240
  - **RSI**: {result['technical']['rsi']:.1f}
241
  - **Price vs SMA50**: {result['technical']['price_vs_sma']:.2%}
242
  - **P/E Ratio**: {result['fundamental'].get('trailingPE', 'N/A')}
243
  """
244
-
245
- # Gerar gráfico simples (exemplo)
246
  plot = self.generate_simple_plot(bt_integration)
247
-
248
  return output, output_ops, plot
249
 
250
- # Função para gerar um gráfico simples
251
  def generate_simple_plot(self, bt_integration):
252
- import matplotlib.pyplot as plt
253
- import matplotlib.dates as mdates
254
-
255
  plt.figure(figsize=(12, 6))
256
-
257
- # Get data feed from Backtrader
258
  datafeed = bt_integration.cerebro.datas[0]
259
-
260
- # Extract dates and closing prices
261
  dates = [datetime.fromordinal(int(date)) for date in datafeed.datetime.array]
262
  closes = datafeed.close.array
263
-
264
- # Plot
265
  plt.plot(dates, closes, label='Close Price', linewidth=1.5)
266
-
267
- # Format dates
268
  plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
269
  plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=3))
270
- plt.gcf().autofmt_xdate() # Rotate dates
271
-
272
- # Add labels and grid
273
  plt.title("Historical Price Data")
274
- plt.xlabel("Data")
275
  plt.ylabel("Price (USD)")
276
  plt.legend()
277
  plt.grid(True, alpha=0.3)
278
-
279
  return plt.gcf()
280
- # Configuração da interface completa
281
- pipeline = st.AnalysisPipeline()
282
 
 
 
283
  interface = GradioInterface(pipeline)
284
 
285
  demo = gr.TabbedInterface(
@@ -289,5 +225,4 @@ demo = gr.TabbedInterface(
289
  )
290
 
291
  if __name__ == "__main__":
292
- #demo.launch(share=True)
293
  demo.launch(share=True)
 
1
+ # app.py
2
  import gradio as gr
3
  from datetime import datetime, timedelta
4
+ from stocks import AnalysisPipeline, BacktraderIntegration
5
+ import matplotlib.pyplot as plt
6
+ import matplotlib.dates as mdates
7
 
8
  class GradioInterface:
9
  def __init__(self, pipeline):
 
14
  'rsi_lower': 30,
15
  'sma_short': 50,
16
  'sma_long': 200,
17
+ 'max_loss_percent': 5,
18
+ 'take_profit_percent': 5,
19
+ 'position_size': 10,
20
+ 'atr_period': 7,
21
  'atr_multiplier': 3,
22
+ 'confidence_threshold': 35,
23
+ 'sentiment_threshold': 25
24
  }
25
+
26
  def create_settings_interface(self):
27
  with gr.Blocks() as settings_interface:
28
  with gr.Row():
29
  with gr.Column():
 
30
  gr.Markdown("### Parameters for Trading Strategy")
31
  inputs = {}
32
  inputs['rsi_period'] = gr.Number(value=14, label="RSI Period", minimum=1)
 
34
  inputs['rsi_lower'] = gr.Number(value=30, label="RSI Lower Limit", minimum=0, maximum=100)
35
  inputs['sma_short'] = gr.Number(value=50, label="SMA Short (period)")
36
  inputs['sma_long'] = gr.Number(value=200, label="SMA Long (period)")
37
+ inputs['max_loss_percent'] = gr.Slider(1, 100, value=5, step=5, label="Stop Loss (%)")
38
+ inputs['take_profit_percent'] = gr.Slider(1, 100, value=5, step=5, label="Take Profit (%)")
39
+ inputs['position_size'] = gr.Slider(1, 100, value=5, step=5, label="Position Size(%)")
40
  inputs['atr_period'] = gr.Number(value=14, label="ATR Period")
41
  inputs['atr_multiplier'] = gr.Number(value=3, label="ATR Multiplier")
42
+ inputs['confidence_threshold'] = gr.Slider(1, 100, value=30, step=5, label="Confidence Threshold(%)")
43
+ inputs['sentiment_threshold'] = gr.Slider(1, 100, value=25, step=5, label="Sentiment Threshold(%)")
44
+ save_btn = gr.Button("Save Configuration")
45
+
46
  gr.Markdown("""
47
  ## 📊 Explanation of Trading Strategy Parameters
 
48
  These parameters configure technical indicators to assist in buy and sell decisions for assets.
 
49
  ### **📉 RSI (Relative Strength Index)**
50
+ - **`rsi_period` (14)** Number of periods to calculate the RSI.
51
+ - **`rsi_upper` (70)** → Overbought conditions (sell signal).
52
+ - **`rsi_lower` (30)** → Oversold conditions (buy signal).
 
 
 
 
 
53
  ### **📈 Simple Moving Averages (SMA)**
54
+ - **`sma_short` (50)** Short-term moving average.
55
+ - **`sma_long` (200)** → Long-term moving average.
 
 
 
 
 
56
  ### **📉 Risk Management**
57
+ - **`max_loss_percent` (0.02)** → Stop Loss (loss limit).
58
+ - **`take_profit_percent` (0.05)** → Take Profit (profit limit).
59
+ - **`position_size` (0.1)** → Proportion of total capital to be used in a trade.
 
 
 
 
60
  ### **📊 ATR (Average True Range) - Volatility**
 
 
61
  - **`atr_period` (14)** → Number of periods to calculate the ATR.
62
+ - **`atr_multiplier` (3)** → ATR multiplier for dynamic stop loss.
 
 
 
 
 
 
 
 
 
63
  """)
64
+
65
  save_btn.click(
66
  self.save_settings,
67
  inputs=[v for v in inputs.values()],
68
  outputs=None
69
  )
70
  return settings_interface
71
+
72
  def save_settings(self, *args):
73
  params = [
74
  'rsi_period', 'rsi_upper', 'rsi_lower',
 
76
  'take_profit_percent', 'position_size',
77
  'atr_period', 'atr_multiplier', 'confidence_threshold', 'sentiment_threshold'
78
  ]
 
79
  self.strategy_params = dict(zip(params, args))
80
+ print("Updated parameters:", self.strategy_params)
81
  return gr.Info("Settings saved!")
82
+
83
  def create_main_interface(self):
84
  with gr.Blocks() as main_interface:
85
  with gr.Row():
86
  with gr.Column():
87
  ticker_input = gr.Text(label="Ticker (ex: VALE)", placeholder="Insert a stock ticker based on Yahoo Finance")
 
88
  fetch_new = gr.Dropdown([True, False], label="Check last news information online (Requires API)?", value=False)
89
+ api_key_input = gr.Textbox(label="API Key", placeholder="Insert your API Key https://newsapi.org/")
90
+ initial_investment = gr.Number(10000, label="Initial Investment (USD)")
91
  years_back = gr.Number(5, label="Historical Data (years back)")
92
+ commission = gr.Number(2, label="Trade Commission (%)", minimum=0, maximum=100)
93
  run_btn = gr.Button("Execute Analysis")
94
  with gr.Column():
95
  plot_output = gr.Plot()
96
  with gr.Row():
 
97
  output_md = gr.Markdown()
98
  with gr.Row():
 
99
  output_ops = gr.Markdown()
100
+
101
  run_btn.click(
102
  self.run_full_analysis,
103
  inputs=[ticker_input, fetch_new, initial_investment, years_back, commission, api_key_input],
104
  outputs=[output_md, output_ops, plot_output]
105
  )
106
  return main_interface
107
+
108
  def run_full_analysis(self, ticker, fetch_new, initial_investment, years_back, commission, api_key):
109
  # Atualizar os parâmetros da pipeline
110
+ self.pipeline.set_sentiment_threshold(float(self.strategy_params['sentiment_threshold']) / 100)
111
+ self.pipeline.set_confidence_threshold(float(self.strategy_params['confidence_threshold']) / 100)
112
+
113
  # Executar análise
114
  result = self.pipeline.analyze_company(
115
  ticker=ticker,
116
  news_api_key=api_key,
117
  fetch_new=fetch_new
118
  )
119
+
120
  if not result:
121
+ return "Something went wrong. Please check your inputs.", None, None
122
+
123
  # Configurar simulação
124
  end_date = datetime.now()
125
+ start_date = end_date - timedelta(days=int(years_back * 365))
126
+
127
  # Criar estratégia personalizada com os parâmetros
128
  custom_strategy_params = {
129
  'rsi_period': int(self.strategy_params['rsi_period']),
 
131
  'rsi_lower': int(self.strategy_params['rsi_lower']),
132
  'sma_short': int(self.strategy_params['sma_short']),
133
  'sma_long': int(self.strategy_params['sma_long']),
134
+ 'max_loss_percent': float(self.strategy_params['max_loss_percent'])/100,
135
+ 'take_profit_percent': float(self.strategy_params['take_profit_percent'])/100,
136
+ 'position_size': float(self.strategy_params['position_size'])/100,
137
  'atr_period': int(self.strategy_params['atr_period']),
138
  'atr_multiplier': int(self.strategy_params['atr_multiplier']),
139
+ 'confidence_threshold': float(self.strategy_params['confidence_threshold'])/100,
140
+ 'sentiment_threshold': float(self.strategy_params['sentiment_threshold'])/100
141
  }
 
 
 
142
 
 
 
 
143
  # Executar simulação
144
+ bt_integration = BacktraderIntegration(analysis_result=result, strategy_params=custom_strategy_params)
145
  bt_integration.add_data_feed(ticker, start_date, end_date)
 
 
 
146
  final_value, operation_logs = bt_integration.run_simulation(
147
  initial_cash=initial_investment,
148
  commission=commission
 
166
  date = parts[0].strip()
167
  details = ", ".join(parts[1:])
168
  formatted_logs.append(f"- 📈 **Result** ({date}): {details}")
169
+
 
 
 
170
  # Adicionar seção de logs na saída
171
  output_ops = "### Log :\n\n" + "\n".join(formatted_logs)
 
 
 
 
 
 
 
 
172
 
 
173
  # Gerar saída formatada em Markdown
174
+ sentiment = result['sentiment']
175
  output = f"""
176
  ## Recommendation: {result['recommendation']}
177
 
 
179
 
180
  ## Simulation Results:
181
  - **Initial Investment**: ${initial_investment:.2f}
182
+ - **Simulation Summary**: {(final_value / initial_investment - 1) * 100:.2f}%
183
  - **Final Portfolio Value**: ${final_value:.2f}
184
 
185
  ### Details:
186
 
187
+ - **Negative Sentiment**: {sentiment.get('negative', 0.0):.2%}
188
+ - **Neutral Sentiment**: {sentiment.get('neutral', 0.0):.2%}
189
+ - **Positive Sentiment**: {sentiment.get('positive', 0.0):.2%}
190
 
191
  - **RSI**: {result['technical']['rsi']:.1f}
192
  - **Price vs SMA50**: {result['technical']['price_vs_sma']:.2%}
193
  - **P/E Ratio**: {result['fundamental'].get('trailingPE', 'N/A')}
194
  """
195
+
196
+ # Gerar gráfico simples
197
  plot = self.generate_simple_plot(bt_integration)
198
+
199
  return output, output_ops, plot
200
 
 
201
  def generate_simple_plot(self, bt_integration):
 
 
 
202
  plt.figure(figsize=(12, 6))
 
 
203
  datafeed = bt_integration.cerebro.datas[0]
 
 
204
  dates = [datetime.fromordinal(int(date)) for date in datafeed.datetime.array]
205
  closes = datafeed.close.array
 
 
206
  plt.plot(dates, closes, label='Close Price', linewidth=1.5)
 
 
207
  plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
208
  plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=3))
209
+ plt.gcf().autofmt_xdate()
 
 
210
  plt.title("Historical Price Data")
211
+ plt.xlabel("Date")
212
  plt.ylabel("Price (USD)")
213
  plt.legend()
214
  plt.grid(True, alpha=0.3)
 
215
  return plt.gcf()
 
 
216
 
217
+ # Configuração da interface completa
218
+ pipeline = AnalysisPipeline()
219
  interface = GradioInterface(pipeline)
220
 
221
  demo = gr.TabbedInterface(
 
225
  )
226
 
227
  if __name__ == "__main__":
 
228
  demo.launch(share=True)
data/__init__.py ADDED
File without changes
data/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (144 Bytes). View file
 
data/__pycache__/api_client.cpython-311.pyc ADDED
Binary file (2.33 kB). View file
 
data/__pycache__/database.cpython-311.pyc ADDED
Binary file (4.25 kB). View file
 
data/api_client.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # data/api_client.py
2
+ import yfinance as yf
3
+ from newsapi import NewsApiClient
4
+ from datetime import datetime, timedelta
5
+
6
+ class YahooFinanceClient:
7
+ @staticmethod
8
+ def download_data(ticker, start_str=None, end_str=None, period="6mo"):
9
+ if period:
10
+ return yf.download(ticker, period=period, progress=False)
11
+ else:
12
+ return yf.download(ticker, start=start_str, end=end_str, progress=False)
13
+
14
+ @staticmethod
15
+ def get_company_info(ticker):
16
+ company = yf.Ticker(ticker)
17
+ return company.info
18
+
19
+ class NewsAPIClient:
20
+ def __init__(self, api_key):
21
+ self.client = NewsApiClient(api_key=api_key)
22
+
23
+ def get_news(self, shortName, days_back=15):
24
+ from_date = (datetime.now() - timedelta(days=days_back)).strftime('%Y-%m-%d')
25
+ return self.client.get_everything(q=shortName, from_param=from_date, language='en', sort_by='relevancy', page_size=20)['articles']
26
+
data/database.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # data/database.py
2
+ from sqlalchemy import create_engine, Column, Integer, String, JSON
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+ from sqlalchemy.orm import sessionmaker
5
+ from datetime import datetime
6
+
7
+ Base = declarative_base()
8
+
9
+ class CompanyData(Base):
10
+ __tablename__ = 'company_data'
11
+ id = Column(Integer, primary_key=True)
12
+ ticker = Column(String)
13
+ data_type = Column(String)
14
+ data = Column(JSON)
15
+ date = Column(String)
16
+
17
+ class DatabaseManager:
18
+ def __init__(self, db_url='sqlite:///financial_data.db'):
19
+ self.engine = create_engine(db_url)
20
+ self.Session = sessionmaker(bind=self.engine)
21
+ Base.metadata.create_all(self.engine)
22
+
23
+ def save_data(self, ticker, data_type, data):
24
+ session = self.Session()
25
+ try:
26
+ new_entry = CompanyData(
27
+ ticker=ticker,
28
+ data_type=data_type,
29
+ data=data,
30
+ date=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
31
+ )
32
+ session.add(new_entry)
33
+ session.commit()
34
+ except Exception as e:
35
+ print(f"Error saving data: {e}")
36
+ finally:
37
+ session.close()
38
+
39
+ def get_historical_data(self, ticker):
40
+ session = self.Session()
41
+ try:
42
+ financials = session.query(CompanyData).filter(
43
+ CompanyData.ticker == ticker,
44
+ CompanyData.data_type == 'financials'
45
+ ).order_by(CompanyData.date.desc()).first()
46
+
47
+ news = session.query(CompanyData).filter(
48
+ CompanyData.ticker == ticker,
49
+ CompanyData.data_type == 'news'
50
+ ).order_by(CompanyData.date.desc()).all()
51
+
52
+ return {
53
+ 'financials': financials.data if financials else None,
54
+ 'news': [n.data for n in news]
55
+ }
56
+ finally:
57
+ session.close()
stocks.py CHANGED
@@ -1,158 +1,26 @@
1
- import yfinance as yf
2
- import pandas as pd
3
  import numpy as np
4
- import torch
5
  import json
6
- from datetime import datetime, timedelta
7
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
8
- from sqlalchemy import create_engine, Column, Integer, String, JSON
9
- from sqlalchemy.ext.declarative import declarative_base
10
- from sqlalchemy.orm import sessionmaker
11
- from newsapi import NewsApiClient
12
  from functools import lru_cache
13
 
14
- import backtrader as bt
15
-
 
 
 
 
 
16
 
17
- # 1. Configuração do Banco de Dados (FORA de qualquer classe)
18
- Base = declarative_base()
19
- engine = create_engine('sqlite:///financial_data.db')
20
- Session = sessionmaker(bind=engine)
21
 
22
- # 2. Modelo de Dados (usa a Base declarada acima)
23
- class CompanyData(Base):
24
- __tablename__ = 'company_data'
25
- id = Column(Integer, primary_key=True)
26
- ticker = Column(String)
27
- data_type = Column(String)
28
- data = Column(JSON)
29
- date = Column(String)
30
-
31
- # 3. Criar tabelas (após definir todos os modelos)
32
- Base.metadata.create_all(engine)
33
-
34
- # 4. Class for Financial Analyst
35
- class FinancialAnalyst:
36
- def __init__(self):
37
- self.models = {}
38
- self.tokenizers = {}
39
- # 2. LM Models for Financial Analysis
40
- FINANCIAL_MODELS = {
41
- 'finbert': {
42
- 'model': "ProsusAI/finbert",
43
- 'tokenizer': "ProsusAI/finbert"
44
- },
45
- 'financial_sentiment': {
46
- 'model': "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis",
47
- 'tokenizer': "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis"
48
- }
49
- }
50
-
51
- for name, config in FINANCIAL_MODELS.items():
52
- try:
53
- self.tokenizers[name] = AutoTokenizer.from_pretrained(config['tokenizer'])
54
- # Use cache to avoid downloading the model multiple times
55
- self.models[name] = self._load_model(config['model'])
56
- print(f"Model {name} loaded successfully")
57
- except Exception as e:
58
- print(f"Error loading model {name}: {e}")
59
- if name == 'financial_sentiment':
60
- print("Using FinBERT as the fallback for financial sentiment analysis")
61
- self.models[name] = self.models['finbert']
62
- self.tokenizers[name] = self.tokenizers['finbert']
63
-
64
- @lru_cache(maxsize=2) # Cache for 2 models
65
- def _load_model(self, model_name):
66
- return AutoModelForSequenceClassification.from_pretrained(model_name)
67
-
68
- # 4. Method for saving data in the database
69
- def save_data(ticker, data_type, data):
70
- session = Session()
71
- try:
72
- new_entry = CompanyData(
73
- ticker=ticker,
74
- data_type=data_type,
75
- data=data,
76
- date=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
77
- )
78
- session.add(new_entry)
79
- session.commit()
80
- except Exception as e:
81
- print(f"Error to save data in the database: {e}")
82
- finally:
83
- session.close()
84
- # 4.1 Method for getting historical data from the database
85
- def get_historical_data(ticker):
86
- session = Session()
87
- try:
88
- financials = session.query(CompanyData).filter(
89
- CompanyData.ticker == ticker,
90
- CompanyData.data_type == 'financials'
91
- ).order_by(CompanyData.date.desc()).first()
92
-
93
- news = session.query(CompanyData).filter(
94
- CompanyData.ticker == ticker,
95
- CompanyData.data_type == 'news'
96
- ).order_by(CompanyData.date.desc()).all()
97
 
98
- return {
99
- 'financials': financials.data if financials else None,
100
- 'news': [n.data for n in news]
101
- }
102
- finally:
103
- session.close()
104
-
105
- # 5. Technical Analysis
106
- def calculate_rsi(data, window=14):
107
- delta = data['Close'].diff()
108
- gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
109
- loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
110
- rs = gain / loss
111
- rsi = 100 - (100 / (1 + rs))
112
- return rsi.iloc[-1]
113
-
114
- # 5.1 Technical Analysis
115
- def technical_analysis(ticker):
116
- try:
117
- # Colecting data from Yahoo Finance
118
- data = yf.download(ticker, period="6mo", progress=False)
119
-
120
- # Check if there is enough data
121
- if data.empty or data.shape[0] < 50: # At least 50 days of data
122
- print(f"Insuficient data for {ticker}")
123
- return None
124
-
125
- # Remove missing values
126
- data = data.dropna()
127
-
128
- # Calculate SMA50
129
- sma_50 = data['Close'].rolling(50).mean().iloc[-1].item()
130
- current_price = data['Close'].iloc[-1].item()
131
-
132
- # Calculate RSI
133
- delta = data['Close'].diff().dropna()
134
- gain = delta.where(delta > 0, 0.0)
135
- loss = -delta.where(delta < 0, 0.0)
136
-
137
- avg_gain = gain.rolling(14).mean()
138
- avg_loss = loss.rolling(14).mean()
139
-
140
- rs = avg_gain / avg_loss
141
- rsi = (100 - (100 / (1 + rs))).iloc[-1].item()
142
-
143
- return {
144
- 'price': current_price,
145
- 'sma_50': sma_50,
146
- 'price_vs_sma': (current_price / sma_50) - 1,
147
- 'rsi': rsi if not np.isnan(rsi) else 50,
148
- 'trend': 'bullish' if current_price > sma_50 else 'bearish'
149
- }
150
-
151
- except Exception as e:
152
- print(f"Error in the thecnical analysis: {e}")
153
- return None
154
-
155
- # 6. Confidence Calculator
156
  class ConfidenceCalculator:
157
  def __init__(self):
158
  self.weights = {
@@ -160,18 +28,18 @@ class ConfidenceCalculator:
160
  'technical': 0.3,
161
  'fundamental': 0.3
162
  }
163
- # 6.1 Method for calculating the confidence
164
  def calculate(self, sentiment, technical, fundamental):
165
- sentiment_score = sentiment['confidence']
166
  technical_score = self._normalize_technical(technical)
167
  fundamental_score = self._normalize_fundamental(fundamental)
168
-
169
  weighted_score = (
170
  sentiment_score * self.weights['sentiment'] +
171
  technical_score * self.weights['technical'] +
172
  fundamental_score * self.weights['fundamental']
173
  )
174
-
175
  return {
176
  'total_confidence': weighted_score,
177
  'components': {
@@ -180,172 +48,104 @@ class ConfidenceCalculator:
180
  'fundamental': fundamental_score
181
  }
182
  }
183
- # 6.2 Method for normalizing the technical analysis
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  def _normalize_technical(self, tech):
185
  if tech is None:
186
  return 0.5
187
- rsi_score = 1 - abs(tech['rsi'] - 50)/50
188
  price_score = np.tanh(tech['price_vs_sma'] * 100)
189
- return 0.6*rsi_score + 0.4*price_score
190
- # 6.3 Method for normalizing the fundamental analysis
191
  def _normalize_fundamental(self, fund):
192
  if not fund:
193
  return 0.5
194
-
195
  pe_ratio = fund.get('pe_ratio', 0)
196
  sector_pe = fund.get('sector_pe')
197
  revenue_growth = fund.get('revenue_growth', 0)
198
-
199
- # Tratar casos onde sector_pe é None
200
  if sector_pe is None:
201
- pe_score = 0.5 # Pontuação neutra
202
  else:
203
  pe_score = 1 if pe_ratio < sector_pe else 0.5
204
-
205
  growth_score = min(revenue_growth / 20, 1)
206
-
207
  return 0.5 * pe_score + 0.5 * growth_score
208
 
209
- # 7. Analysis Pipeline
210
  class AnalysisPipeline:
211
  def __init__(self, sentiment_threshold=0.6, confidence_threshold=0.7):
212
- self.analyst = FinancialAnalyst()
213
  self.confidence_calc = ConfidenceCalculator()
214
- self.sentiment_threshold = sentiment_threshold # Novo parâmetro
215
- self.confidence_threshold = confidence_threshold # Novo parâmetro
216
-
217
  def set_sentiment_threshold(self, sentiment_threshold):
218
  self.sentiment_threshold = sentiment_threshold
219
 
220
  def set_confidence_threshold(self, confidence_threshold):
221
  self.confidence_threshold = confidence_threshold
222
-
223
- # 7.1 Method for getting the fundamental data
224
- def get_fundamental_data(self, ticker):
225
- try:
226
- company = yf.Ticker(ticker)
227
- info = company.info
228
-
229
- # ensure that the data is valid
230
- return {
231
- 'trailingPE': float(info.get('trailingPE', 0)),
232
- 'sectorPE': float(info.get('sectorPE', 0)) if info.get('sectorPE') else None,
233
- 'revenueGrowth': float(info.get('revenueGrowth', 0)),
234
- 'profitMargins': float(info.get('profitMargins', 0)),
235
- 'debtToEquity': float(info.get('debtToEquity', 0))
236
- }
237
- except Exception as e:
238
- print(f"Error while performing the fundamental analysis: {e}")
239
- return {}
240
- # 7.2 Method for getting the news
241
- def get_news(self, ticker, api_key=None, fetch_new=True):
242
- if fetch_new and api_key:
243
- try:
244
- newsapi = NewsApiClient(api_key=api_key)
245
- from_date = (datetime.now() - timedelta(days=5)).strftime('%Y-%m-%d')
246
- news = newsapi.get_everything(q=ticker, from_param=from_date, language='en', sort_by='relevancy')
247
- articles = news['articles']
248
- save_data(ticker, 'news', articles)
249
- return articles
250
- except Exception as e:
251
- print(f"Error while fetching information online: {e}")
252
- return self._get_news_from_db(ticker)
253
- else:
254
- return self._get_news_from_db(ticker)
255
- # 7.3 Method for getting the news from the database
256
- def _get_news_from_db(self, ticker):
257
- session = Session()
258
- try:
259
- news_records = session.query(CompanyData).filter(
260
- CompanyData.ticker == ticker,
261
- CompanyData.data_type == 'news'
262
- ).order_by(CompanyData.date.desc()).all()
263
-
264
- news = []
265
- for record in news_records:
266
- if isinstance(record.data, list):
267
- news.extend(record.data)
268
- elif isinstance(record.data, dict):
269
- news.append(record.data)
270
- return news[-5:] # Últimas 5 notícias
271
- except Exception as e:
272
- print(f"Error to fetch information from the local database: {e}")
273
- return []
274
- finally:
275
- session.close()
276
- # 7.4 Method for analyzing the sentiment
277
- def analyze_sentiment(self, news):
278
- try:
279
- if not news:
280
- return {
281
- 'sentiment': {'negative': 0.33, 'neutral': 0.33, 'positive': 0.33},
282
- 'confidence': 0.5
283
- }
284
-
285
- sentiment_scores = []
286
- for item in news:
287
- text = f"{item.get('title', '')} {item.get('description', '')}".strip()
288
- if not text:
289
- continue
290
-
291
- inputs = self.analyst.tokenizers['financial_sentiment'](text, return_tensors="pt", truncation=True, max_length=512)
292
- outputs = self.analyst.models['financial_sentiment'](**inputs)
293
- probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)
294
- sentiment_scores.append(probabilities.detach().numpy()[0])
295
-
296
- if not sentiment_scores:
297
- return {
298
- 'sentiment': {'negative': 0.33, 'neutral': 0.33, 'positive': 0.33},
299
- 'confidence': 0.5
300
- }
301
-
302
- avg_sentiment = np.mean(sentiment_scores, axis=0)
303
- labels = ["negative", "neutral", "positive"]
304
- sentiment = {labels[i]: float(avg_sentiment[i]) for i in range(3)}
305
-
306
- return {
307
- 'sentiment': sentiment,
308
- 'confidence': max(sentiment.values())
309
- }
310
- except Exception as e:
311
- print(f"Error while sentimental analysis: {e}")
312
- return {
313
- 'sentiment': {'negative': 0.33, 'neutral': 0.33, 'positive': 0.33},
314
- 'confidence': 0.5
315
- }
316
- # 7.5 Method for analyzing the company
317
  def analyze_company(self, ticker, news_api_key=None, fetch_new=True):
318
  try:
319
- # Collecting historical data
320
- fundamental = self.get_fundamental_data(ticker)
321
- if fetch_new:
322
- save_data(ticker, 'financials', fundamental)
323
-
324
- # Collicting news
325
- news = self.get_news(ticker, news_api_key, fetch_new)
326
 
327
- # Technical analysis
328
- technical = technical_analysis(ticker)
329
 
 
 
 
 
 
 
 
 
 
330
  if not fundamental or not news or technical is None:
331
- print(f"Insuficient data for: {ticker}")
332
  return None
333
-
334
  # Sentiment analysis
335
- sentiment = self.analyze_sentiment(news)
336
-
337
  # Confidence calculation
338
  confidence = self.confidence_calc.calculate(
339
- sentiment,
340
- technical,
341
  self._prepare_fundamental(fundamental)
342
  )
343
-
344
- # Generate recommendation
345
  recommendation = self.generate_recommendation(
346
  sentiment, technical, fundamental, confidence
347
  )
348
-
349
  return {
350
  'recommendation': recommendation,
351
  'confidence': confidence,
@@ -353,326 +153,96 @@ class AnalysisPipeline:
353
  'fundamental': fundamental,
354
  'sentiment': sentiment
355
  }
356
-
357
  except Exception as e:
358
- print(f"Erro na análise: {e}")
359
  return None
360
- # 7.6 Method for preparing the fundamental data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  def _prepare_fundamental(self, fundamental):
362
  return {
363
  'pe_ratio': fundamental.get('trailingPE', 0),
364
- 'sector_pe': fundamental.get('sectorPE'), # Pode ser None
365
  'revenue_growth': fundamental.get('revenueGrowth', 0)
366
  }
367
- # 7.7 Method for generating the recommendation
368
  def generate_recommendation(self, sentiment, technical, fundamental, confidence):
369
  pe_ratio = fundamental.get('trailingPE', 0)
370
  sector_pe = fundamental.get('sectorPE')
371
-
372
- # Low confidence condition - NEUTRAL
373
  if confidence['total_confidence'] < 0.4:
374
  return 'NEUTRAL'
375
-
376
- # Rules based on fundamental analysis
377
  if sector_pe is not None and sector_pe > 0:
378
  if pe_ratio < sector_pe * 0.7:
379
  return 'BUY'
380
  elif pe_ratio > sector_pe * 1.3:
381
  return 'SELL'
382
-
383
- # Rules based on sentiment and confidence
384
- if confidence['total_confidence'] > self.confidence_threshold and sentiment['sentiment']['positive'] > self.sentiment_threshold:
385
  return 'BUY'
386
-
387
- # Fallback based on technical analysis
388
  if technical and 'trend' in technical:
389
  return 'HOLD' if technical['trend'] == 'bullish' else 'SELL'
390
-
391
- # Final fallback
392
- return 'NEUTRAL'
393
-
394
- class BacktraderIntegration:
395
- def __init__(self, analysis_result=None, strategy_params=None):
396
- self.cerebro = bt.Cerebro()
397
- self.analysis = analysis_result
398
- self.strategy_params = strategy_params or {}
399
- self.setup_environment()
400
-
401
- def setup_environment(self):
402
- # Basic configuration of the broker
403
- self.cerebro.broker.setcash(100000.0) # Valor padrão será atualizado
404
- self.cerebro.broker.setcommission(commission=0.001)
405
-
406
- # Custom Strategy
407
- if self.analysis:
408
- self.cerebro.addstrategy(self.CustomStrategy, analysis=self.analysis, **self.strategy_params)
409
- else:
410
- self.cerebro.addstrategy(self.CustomStrategy)
411
-
412
- def add_data_feed(self, ticker, start_date, end_date):
413
- # Convert datetime to string
414
- start_str = start_date.strftime("%Y-%m-%d")
415
- end_str = end_date.strftime("%Y-%m-%d")
416
-
417
- # Download data from Yahoo Finance
418
- df = yf.download(ticker, start=start_str, end=end_str, progress=False)
419
-
420
- # adjust the columns
421
- if isinstance(df.columns, pd.MultiIndex):
422
- df.columns = df.columns.droplevel(1) # remove the multi-index
423
-
424
- # minimum columns expected
425
- expected_columns = ["Open", "High", "Low", "Close", "Volume"]
426
 
427
- # Make sure that the columns are correct
428
- if not all(col in df.columns for col in expected_columns):
429
- raise ValueError(f"Colunas do DataFrame incorretas: {df.columns}")
430
-
431
- # Creates the data feed
432
- data = bt.feeds.PandasData(dataname=df)
433
- self.cerebro.adddata(data)
434
-
435
- # 7.8 Method for running the simulation
436
- def run_simulation(self, initial_cash, commission):
437
- self.cerebro.broker.setcash(initial_cash)
438
- self.cerebro.broker.setcommission(commission=commission)
439
- print(f'\nStarting Portfolio Value: {self.cerebro.broker.getvalue():.2f}')
440
- self.cerebro.run()
441
-
442
- # Coletar logs de todas as estratégias
443
- operation_logs = []
444
- for strategy in self.cerebro.runstrats:
445
- operation_logs.extend(strategy[0].operation_logs)
446
-
447
- print(f'Final Portfolio Value: {self.cerebro.broker.getvalue():.2f}')
448
- return self.cerebro.broker.getvalue(), operation_logs # Retorna ambos valores
449
- # 7.9 Custom Strategy
450
- class CustomStrategy(bt.Strategy):
451
- params = (
452
- ('analysis', None),
453
- ('rsi_period', 14),
454
- ('rsi_upper', 70),
455
- ('rsi_lower', 30),
456
- ('sma_short', 50),
457
- ('sma_long', 200),
458
- ('max_loss_percent', 0.02),
459
- ('take_profit_percent', 0.05),
460
- ('position_size', 0.1),
461
- ('atr_period', 14),
462
- ('atr_multiplier', 3),
463
- ('sentiment_threshold', 0.6), # Novo parâmetro
464
- ('confidence_threshold', 0.7) # Novo parâmetro
465
- )
466
-
467
- def __init__(self):
468
- # Parâmetros agora são acessados via self.params
469
- self.operation_logs = [] # Lista para armazenar os logs
470
- self.recommendation = self.params.analysis['recommendation'] if self.params.analysis else 'HOLD'
471
- self.technical_analysis = self.params.analysis['technical'] if self.params.analysis else None
472
- self.sentiment_analysis = self.params.analysis['sentiment'] if self.params.analysis else None
473
- self.confidence = self.params.analysis['confidence']['total_confidence'] if self.params.analysis else 0.5
474
-
475
- # Indicadores usando parâmetros dinâmicos
476
- self.rsi = bt.indicators.RSI(
477
- self.data.close,
478
- period=self.params.rsi_period
479
- )
480
-
481
- self.sma_short = bt.indicators.SMA(
482
- self.data.close,
483
- period=self.params.sma_short
484
- )
485
-
486
- self.sma_long = bt.indicators.SMA(
487
- self.data.close,
488
- period=self.params.sma_long
489
- )
490
-
491
-
492
- # Technical Indicators
493
- self.rsi = bt.indicators.RSI(self.data.close, period=self.p.rsi_period)
494
- self.sma_short = bt.indicators.SMA(self.data.close, period=self.p.sma_short)
495
- self.sma_long = bt.indicators.SMA(self.data.close, period=self.p.sma_long)
496
-
497
- # Volatility Indicator
498
- self.atr = bt.indicators.ATR(self.data, period=self.p.atr_period)
499
-
500
- # Trading management
501
- self.order = None
502
- self.stop_price = None
503
- self.take_profit_price = None
504
- self.buy_price = None
505
- self.entry_date = None
506
-
507
-
508
- def log(self, txt, dt=None):
509
- dt = dt or self.datas[0].datetime.date(0)
510
- log_entry = f'{dt.isoformat()}, {txt}'
511
- self.operation_logs.append(log_entry) # Armazena na lista
512
- print(log_entry) # Mantém o print original
513
-
514
- def notify_order(self, order):
515
- if order.status in [order.Submitted, order.Accepted]:
516
- return
517
-
518
- if order.status in [order.Completed]:
519
- if order.isbuy():
520
- self.log(f'BUY EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm {order.executed.comm:.2f}')
521
- self.buy_price = order.executed.price
522
- self.entry_date = self.datas[0].datetime.date(0)
523
- else:
524
- self.log(f'SELL EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm {order.executed.comm:.2f}')
525
-
526
- self.order = None
527
-
528
- def notify_trade(self, trade):
529
- if not trade.isclosed:
530
- return
531
-
532
- self.log(f'TRADE PROFIT, GROSS: {trade.pnl:.2f}, NET: {trade.pnlcomm:.2f}')
533
-
534
- def calculate_position_size(self):
535
- portfolio_value = self.broker.getvalue()
536
- return int((portfolio_value * self.p.position_size) / self.data.close[0])
537
-
538
- def next(self):
539
- # Prevent multiple orders
540
- if self.order:
541
- return
542
-
543
- current_price = self.data.close[0]
544
- portfolio_value = self.broker.getvalue()
545
-
546
- # Usar parâmetros dinâmicos nas regras
547
- stop_loss = current_price * (1 - self.params.max_loss_percent)
548
- take_profit = current_price * (1 + self.params.take_profit_percent)
549
-
550
- # Analyze prior analysis for additional confirmation
551
- analysis_confirmation = self._analyze_prior_research()
552
-
553
- # No open position - look for entry
554
- if not self.position:
555
- # Enhanced entry conditions
556
- # Condições com parâmetros ajustáveis
557
- entry_conditions = (
558
- current_price > self.sma_long[0] and
559
- self.rsi[0] < self.params.rsi_lower and
560
- bool(self.params.analysis['confidence']['total_confidence'] > self.p.confidence_threshold)
561
- )
562
-
563
- if entry_conditions:
564
- # Calculate position size
565
- size = self.calculate_position_size()
566
-
567
- # Place buy order
568
- self.order = self.buy(size=size)
569
-
570
- # Calculate stop loss and take profit
571
- stop_loss = current_price * (1 - self.p.max_loss_percent)
572
- take_profit = current_price * (1 + self.p.take_profit_percent)
573
-
574
- # Alternative stop loss using ATR for volatility
575
- atr_stop = current_price - (self.atr[0] * self.p.atr_multiplier)
576
- self.stop_price = max(stop_loss, atr_stop)
577
- self.take_profit_price = take_profit
578
-
579
- # Manage existing position
580
- else:
581
- # Exit conditions
582
- exit_conditions = (
583
- current_price < self.stop_price or # Stop loss triggered
584
- current_price > self.take_profit_price or # Take profit reached
585
- self.rsi[0] > self.p.rsi_upper or # Overbought condition
586
- current_price < self.sma_short[0] or # Trend change
587
- not analysis_confirmation # Loss of analysis confirmation
588
- )
589
-
590
- if exit_conditions:
591
- self.close() # Close entire position
592
- self.stop_price = None
593
- self.take_profit_price = None
594
-
595
- def _analyze_prior_research(self):
596
- # Integrate multiple analysis aspects
597
- if not self.p.analysis:
598
- return True
599
-
600
- # Sentiment analysis check
601
- sentiment_positive = (
602
- self.sentiment_analysis and
603
- self.sentiment_analysis['sentiment']['positive'] > self.p.sentiment_threshold
604
- )
605
-
606
- # Technical analysis check
607
- technical_bullish = (
608
- self.technical_analysis and
609
- self.technical_analysis['trend'] == 'bullish'
610
- )
611
 
612
- # Confidence check
613
- high_confidence = bool(self.confidence > self.p.confidence_threshold)
 
614
 
615
- # Combine conditions
616
- return sentiment_positive and technical_bullish and high_confidence
617
 
618
- def stop(self):
619
- # Final report when backtest completes
620
- self.log('Final Portfolio Value: %.2f' % self.broker.getvalue())
621
 
622
- # 8. Main
623
- if __name__ == "__main__":
624
- pipeline = AnalysisPipeline()
625
-
626
- print("\n=== Analysis of Stock-Market ===")
627
-
628
- # 1. Requesting the company ticker
629
- ticker = input("Type the company ticker (ex: AAPL): ").strip().upper()
630
-
631
- # 2. Requesting if the user wants to fetch new data
632
- while True:
633
- fetch_new = input("Would you like to have new data from internet? (y/n): ").lower()
634
- if fetch_new in ['y', 'n', 'yes', 'no', 'no']:
635
- fetch_new_bool = fetch_new in ['y', 'no']
636
- break
637
- print("Not a valid option! Type y or n")
638
-
639
- initial_investment = float(input("Inicial Investment (USD): "))
640
  years_back = int(input("Historical Period (years): "))
641
  commission = float(input("Commission per trade: "))
642
-
643
- # 3. API Key for NewsAPI
644
- news_api_key = 'INSIRA SUA CHAVE AQUI'
645
-
646
- # 4. Running the analysis
647
- print(f"\nRunning the analysis with Machine Learning {ticker}...")
648
  result = pipeline.analyze_company(
649
  ticker=ticker,
650
  news_api_key=news_api_key if news_api_key else None,
651
  fetch_new=fetch_new_bool
652
  )
653
-
654
- # 5. Showing the results
655
  if result:
656
-
657
- # Running the simulation with Backtrader
658
  end_date = datetime.now()
659
- start_date = end_date - timedelta(days=years_back*365)
660
-
661
  bt_integration = BacktraderIntegration(result)
662
  bt_integration.add_data_feed(ticker, start_date, end_date)
663
  final_value = bt_integration.run_simulation(initial_investment, commission)
664
-
665
  print("\n=== Analysis Result ===")
666
  print(f"Recommendation: {result['recommendation']}")
667
  print(f"Confidence: {result['confidence']['total_confidence']:.2%}")
668
- print(f"Return of the Simulation: {(final_value/initial_investment-1)*100:.2f}%")
669
  print("\nDetails:")
670
- print(f"1. Sentiment: {json.dumps(result['sentiment']['sentiment'], indent=2)}")
671
  print(f"2. Technical Analysis: RSI {result['technical']['rsi']:.1f}, Price vs SMA50: {result['technical']['price_vs_sma']:.2%}")
672
- print(f"3. Fundamental: P/E {result['fundamental'].get('trailingPE', 'N/A')} vs Sctor {result['fundamental'].get('sectorPE', 'N/A')}")
673
  print(f"4. Confidence Components: {json.dumps(result['confidence']['components'], indent=2)}")
674
  else:
675
- print("\nIt was not possible to run the analysis, please check:")
676
  print("- Internet connection")
677
- print("- Ticker value")
678
- print("- Historical data availability")
 
1
+ # stocks.py
2
+ from datetime import datetime, timedelta
3
  import numpy as np
 
4
  import json
 
 
 
 
 
 
5
  from functools import lru_cache
6
 
7
+ # Importações dos novos módulos
8
+ from data.database import DatabaseManager
9
+ from data.api_client import NewsAPIClient
10
+ from analysis.technical import TechnicalAnalyzer
11
+ from analysis.fundamental import FundamentalAnalyzer
12
+ from analysis.sentiment import SentimentAnalyzer
13
+ from strategies.backtrader import BacktraderIntegration
14
 
15
+ # Database configuration
16
+ db_manager = DatabaseManager()
 
 
17
 
18
+ # Analyzers configuration
19
+ technical_analyzer = TechnicalAnalyzer()
20
+ fundamental_analyzer = FundamentalAnalyzer()
21
+ sentiment_analyzer = SentimentAnalyzer()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
+ # Confidence calculator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  class ConfidenceCalculator:
25
  def __init__(self):
26
  self.weights = {
 
28
  'technical': 0.3,
29
  'fundamental': 0.3
30
  }
31
+
32
  def calculate(self, sentiment, technical, fundamental):
33
+ sentiment_score = self._normalie_sentiment(sentiment)
34
  technical_score = self._normalize_technical(technical)
35
  fundamental_score = self._normalize_fundamental(fundamental)
36
+
37
  weighted_score = (
38
  sentiment_score * self.weights['sentiment'] +
39
  technical_score * self.weights['technical'] +
40
  fundamental_score * self.weights['fundamental']
41
  )
42
+
43
  return {
44
  'total_confidence': weighted_score,
45
  'components': {
 
48
  'fundamental': fundamental_score
49
  }
50
  }
51
+
52
+ def _normalie_sentiment(self, sentiment):
53
+ """
54
+ Normaliza o sentimento em um único score de confiança.
55
+
56
+ Parâmetros:
57
+ sentiment (dict): {'negative': float, 'neutral': float, 'positive': float}
58
+
59
+ Retorno:
60
+ float: score normalizado entre 0 e 1.
61
+ """
62
+ # Definição dos pesos para cada categoria
63
+ weight_positive = 1.0 # Sentimento positivo contribui mais
64
+ weight_neutral = 0.5 # Neutro tem impacto médio
65
+ weight_negative = 0.0 # Negativo reduz a confiança
66
+
67
+ # Cálculo do score ponderado
68
+ sentiment_score = (
69
+ sentiment['positive'] * weight_positive +
70
+ sentiment['neutral'] * weight_neutral +
71
+ sentiment['negative'] * weight_negative
72
+ )
73
+
74
+ # Garantindo que o score fique entre 0 e 1
75
+ return max(0.0, min(1.0, sentiment_score))
76
+
77
+
78
  def _normalize_technical(self, tech):
79
  if tech is None:
80
  return 0.5
81
+ rsi_score = 1 - abs(tech['rsi'] - 50) / 50
82
  price_score = np.tanh(tech['price_vs_sma'] * 100)
83
+ return 0.6 * rsi_score + 0.4 * price_score
84
+
85
  def _normalize_fundamental(self, fund):
86
  if not fund:
87
  return 0.5
88
+
89
  pe_ratio = fund.get('pe_ratio', 0)
90
  sector_pe = fund.get('sector_pe')
91
  revenue_growth = fund.get('revenue_growth', 0)
92
+
 
93
  if sector_pe is None:
94
+ pe_score = 0.5
95
  else:
96
  pe_score = 1 if pe_ratio < sector_pe else 0.5
97
+
98
  growth_score = min(revenue_growth / 20, 1)
 
99
  return 0.5 * pe_score + 0.5 * growth_score
100
 
101
+ # Analysis pipeline
102
  class AnalysisPipeline:
103
  def __init__(self, sentiment_threshold=0.6, confidence_threshold=0.7):
 
104
  self.confidence_calc = ConfidenceCalculator()
105
+ self.sentiment_threshold = sentiment_threshold
106
+ self.confidence_threshold = confidence_threshold
107
+
108
  def set_sentiment_threshold(self, sentiment_threshold):
109
  self.sentiment_threshold = sentiment_threshold
110
 
111
  def set_confidence_threshold(self, confidence_threshold):
112
  self.confidence_threshold = confidence_threshold
113
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  def analyze_company(self, ticker, news_api_key=None, fetch_new=True):
115
  try:
116
+ # fundamental analysis
117
+ fundamental = fundamental_analyzer.analyze(ticker)
 
 
 
 
 
118
 
119
+ shortName = fundamental['shortName'].split()[0]
 
120
 
121
+ if fetch_new:
122
+ db_manager.save_data(ticker, 'financials', fundamental)
123
+
124
+ # collecting last news
125
+ news = self._get_news(shortName, ticker, news_api_key, fetch_new)
126
+
127
+ # technical analysis
128
+ technical = technical_analyzer.analyze(ticker)
129
+
130
  if not fundamental or not news or technical is None:
131
+ print(f"Insufficient data for: {ticker}")
132
  return None
133
+
134
  # Sentiment analysis
135
+ sentiment = sentiment_analyzer.analyze(news)
136
+
137
  # Confidence calculation
138
  confidence = self.confidence_calc.calculate(
139
+ sentiment,
140
+ technical,
141
  self._prepare_fundamental(fundamental)
142
  )
143
+
144
+ # Recommendation generation
145
  recommendation = self.generate_recommendation(
146
  sentiment, technical, fundamental, confidence
147
  )
148
+
149
  return {
150
  'recommendation': recommendation,
151
  'confidence': confidence,
 
153
  'fundamental': fundamental,
154
  'sentiment': sentiment
155
  }
156
+
157
  except Exception as e:
158
+ print(f"Error in analysis: {e}")
159
  return None
160
+
161
+ def _get_news(self, shortName, ticker, api_key, fetch_new):
162
+ if fetch_new and api_key:
163
+ try:
164
+ news_client = NewsAPIClient(api_key)
165
+ articles = news_client.get_news(shortName)
166
+
167
+ # Flatten the list of articles
168
+ db_manager.save_data(ticker, 'news', articles)
169
+ return articles
170
+ except Exception as e:
171
+ print(f"Error fetching news: {e}")
172
+ return db_manager.get_historical_data(ticker)['news']
173
+ else:
174
+ return db_manager.get_historical_data(ticker)['news']
175
+
176
  def _prepare_fundamental(self, fundamental):
177
  return {
178
  'pe_ratio': fundamental.get('trailingPE', 0),
179
+ 'sector_pe': fundamental.get('sectorPE'),
180
  'revenue_growth': fundamental.get('revenueGrowth', 0)
181
  }
182
+
183
  def generate_recommendation(self, sentiment, technical, fundamental, confidence):
184
  pe_ratio = fundamental.get('trailingPE', 0)
185
  sector_pe = fundamental.get('sectorPE')
186
+
 
187
  if confidence['total_confidence'] < 0.4:
188
  return 'NEUTRAL'
189
+
 
190
  if sector_pe is not None and sector_pe > 0:
191
  if pe_ratio < sector_pe * 0.7:
192
  return 'BUY'
193
  elif pe_ratio > sector_pe * 1.3:
194
  return 'SELL'
195
+
196
+ if confidence['total_confidence'] > self.confidence_threshold and sentiment['positive'] > self.sentiment_threshold:
 
197
  return 'BUY'
198
+
 
199
  if technical and 'trend' in technical:
200
  return 'HOLD' if technical['trend'] == 'bullish' else 'SELL'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
+ return 'NEUTRAL'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
+ # Main function
205
+ if __name__ == "__main__":
206
+ pipeline = AnalysisPipeline()
207
 
208
+ print("\n=== Stock Market Analysis ===")
209
+ ticker = input("Enter the company ticker (e.g., AAPL): ").strip().upper()
210
 
211
+ fetch_new = input("Fetch new data from the internet? (y/n): ").lower()
212
+ fetch_new_bool = fetch_new in ['y', 'yes']
 
213
 
214
+ initial_investment = float(input("Initial Investment (USD): "))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  years_back = int(input("Historical Period (years): "))
216
  commission = float(input("Commission per trade: "))
217
+
218
+ news_api_key = 'YOUR_NEWSAPI_KEY_HERE'
219
+
220
+ print(f"\nRunning analysis for {ticker}...")
 
 
221
  result = pipeline.analyze_company(
222
  ticker=ticker,
223
  news_api_key=news_api_key if news_api_key else None,
224
  fetch_new=fetch_new_bool
225
  )
226
+
 
227
  if result:
 
 
228
  end_date = datetime.now()
229
+ start_date = end_date - timedelta(days=years_back * 365)
230
+
231
  bt_integration = BacktraderIntegration(result)
232
  bt_integration.add_data_feed(ticker, start_date, end_date)
233
  final_value = bt_integration.run_simulation(initial_investment, commission)
234
+
235
  print("\n=== Analysis Result ===")
236
  print(f"Recommendation: {result['recommendation']}")
237
  print(f"Confidence: {result['confidence']['total_confidence']:.2%}")
238
+ print(f"Simulation Return: {(final_value / initial_investment - 1) * 100:.2f}%")
239
  print("\nDetails:")
240
+ print(f"1. Sentiment: {json.dumps(result['sentiment'], indent=2)}")
241
  print(f"2. Technical Analysis: RSI {result['technical']['rsi']:.1f}, Price vs SMA50: {result['technical']['price_vs_sma']:.2%}")
242
+ print(f"3. Fundamental: P/E {result['fundamental'].get('trailingPE', 'N/A')} vs Sector {result['fundamental'].get('sectorPE', 'N/A')}")
243
  print(f"4. Confidence Components: {json.dumps(result['confidence']['components'], indent=2)}")
244
  else:
245
+ print("\nUnable to complete analysis. Please check:")
246
  print("- Internet connection")
247
+ print("- Ticker symbol")
248
+ print("- Availability of historical data")
strategies/__pycache__/backtrader.cpython-311.pyc ADDED
Binary file (13 kB). View file
 
strategies/backtrader.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # strategies/backtrader.py
2
+ import backtrader as bt
3
+ import sys
4
+ import os
5
+ import pandas as pd
6
+
7
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
8
+ from data.api_client import YahooFinanceClient
9
+
10
+
11
+ class Logger:
12
+ def __init__(self):
13
+ self.logs = []
14
+
15
+ def add_log(self, log_entry):
16
+ self.logs.append(log_entry)
17
+
18
+ def get_logs(self):
19
+ return self.logs.copy() # Return a copy to prevent direct modification
20
+
21
+ class CustomStrategy(bt.Strategy):
22
+ params = (
23
+ ('analysis', None),
24
+ ('logger', None),
25
+ ('rsi_period', 14),
26
+ ('rsi_upper', 70),
27
+ ('rsi_lower', 30),
28
+ ('sma_short', 50),
29
+ ('sma_long', 200),
30
+ ('max_loss_percent', 0.02),
31
+ ('take_profit_percent', 0.05),
32
+ ('position_size', 0.1),
33
+ ('atr_period', 14),
34
+ ('atr_multiplier', 3),
35
+ ('sentiment_threshold', 0.6), # Novo parâmetro
36
+ ('confidence_threshold', 0.7) # Novo parâmetro
37
+ )
38
+
39
+ def __init__(self):
40
+ # Parâmetros agora são acessados via self.params
41
+ self.logger = self.params.logger
42
+ self.recommendation = self.params.analysis['recommendation'] if self.params.analysis else 'HOLD'
43
+ self.technical_analysis = self.params.analysis['technical'] if self.params.analysis else None
44
+ self.sentiment_analysis = self.params.analysis['sentiment'] if self.params.analysis else None
45
+ self.confidence = self.params.analysis['confidence']['total_confidence'] if self.params.analysis else 0.5
46
+
47
+ # Indicadores usando parâmetros dinâmicos
48
+ self.rsi = bt.indicators.RSI(
49
+ self.data.close,
50
+ period=self.params.rsi_period
51
+ )
52
+
53
+ self.sma_short = bt.indicators.SMA(
54
+ self.data.close,
55
+ period=self.params.sma_short
56
+ )
57
+
58
+ self.sma_long = bt.indicators.SMA(
59
+ self.data.close,
60
+ period=self.params.sma_long
61
+ )
62
+
63
+
64
+ # Technical Indicators
65
+ self.rsi = bt.indicators.RSI(self.data.close, period=self.p.rsi_period)
66
+ self.sma_short = bt.indicators.SMA(self.data.close, period=self.p.sma_short)
67
+ self.sma_long = bt.indicators.SMA(self.data.close, period=self.p.sma_long)
68
+
69
+ # Volatility Indicator
70
+ self.atr = bt.indicators.ATR(self.data, period=self.p.atr_period)
71
+
72
+ # Trading management
73
+ self.order = None
74
+ self.stop_price = None
75
+ self.take_profit_price = None
76
+ self.buy_price = None
77
+ self.entry_date = None
78
+
79
+ def log(self, txt, dt=None):
80
+ dt = dt or self.datas[0].datetime.date(0)
81
+ log_entry = f'{dt.isoformat()}, {txt}'
82
+ self.logger.add_log(log_entry) # Store in shared logger
83
+ print(log_entry)
84
+
85
+ def notify_order(self, order):
86
+ if order.status in [order.Submitted, order.Accepted]:
87
+ return
88
+
89
+ if order.status in [order.Completed]:
90
+ if order.isbuy():
91
+ self.log(f'BUY EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm {order.executed.comm:.2f}')
92
+ self.buy_price = order.executed.price
93
+ self.entry_date = self.datas[0].datetime.date(0)
94
+ else:
95
+ self.log(f'SELL EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm {order.executed.comm:.2f}')
96
+
97
+ self.order = None
98
+
99
+ def notify_trade(self, trade):
100
+ if not trade.isclosed:
101
+ return
102
+
103
+ self.log(f'TRADE PROFIT, GROSS: {trade.pnl:.2f}, NET: {trade.pnlcomm:.2f}')
104
+
105
+ def calculate_position_size(self):
106
+ portfolio_value = self.broker.getvalue()
107
+ return int((portfolio_value * self.p.position_size) / self.data.close[0])
108
+
109
+ def next(self):
110
+ # Prevent multiple orders
111
+ if self.order:
112
+ return
113
+
114
+ current_price = self.data.close[0]
115
+
116
+ # Usar parâmetros dinâmicos nas regras
117
+ stop_loss = current_price * (1 - self.params.max_loss_percent)
118
+ take_profit = current_price * (1 + self.params.take_profit_percent)
119
+
120
+ # Analyze prior analysis for additional confirmation
121
+ analysis_confirmation = self._analyze_prior_research()
122
+
123
+ # No open position - look for entry
124
+ if not self.position:
125
+ # Enhanced entry conditions
126
+ # Condições com parâmetros ajustáveis
127
+ entry_conditions = (
128
+ current_price > self.sma_long[0] and
129
+ self.rsi[0] < self.params.rsi_lower and
130
+ bool(self.params.analysis['confidence']['total_confidence'] > self.p.confidence_threshold)
131
+ )
132
+
133
+ if entry_conditions:
134
+ # Calculate position size
135
+ size = self.calculate_position_size()
136
+
137
+ # Place buy order
138
+ self.order = self.buy(size=size)
139
+
140
+ # Calculate stop loss and take profit
141
+ stop_loss = current_price * (1 - self.p.max_loss_percent)
142
+ take_profit = current_price * (1 + self.p.take_profit_percent)
143
+
144
+ # Alternative stop loss using ATR for volatility
145
+ atr_stop = current_price - (self.atr[0] * self.p.atr_multiplier)
146
+ self.stop_price = max(stop_loss, atr_stop)
147
+ self.take_profit_price = take_profit
148
+
149
+ # Manage existing position
150
+ else:
151
+ # Exit conditions
152
+ exit_conditions = (
153
+ current_price < self.stop_price or # Stop loss triggered
154
+ current_price > self.take_profit_price or # Take profit reached
155
+ self.rsi[0] > self.p.rsi_upper or # Overbought condition
156
+ current_price < self.sma_short[0] or # Trend change
157
+ not analysis_confirmation # Loss of analysis confirmation
158
+ )
159
+
160
+ if exit_conditions:
161
+ self.close() # Close entire position
162
+ self.stop_price = None
163
+ self.take_profit_price = None
164
+
165
+ def _analyze_prior_research(self):
166
+ # Integrate multiple analysis aspects
167
+ if not self.p.analysis:
168
+ return True
169
+
170
+ # Sentiment analysis check
171
+ sentiment_positive = (
172
+ self.sentiment_analysis and
173
+ self.sentiment_analysis['positive'] > self.p.sentiment_threshold
174
+ )
175
+
176
+ # Technical analysis check
177
+ technical_bullish = (
178
+ self.technical_analysis and
179
+ self.technical_analysis['trend'] == 'bullish'
180
+ )
181
+
182
+ # Confidence check
183
+ high_confidence = bool(self.confidence > self.p.confidence_threshold)
184
+
185
+ # Combine conditions
186
+ return sentiment_positive and technical_bullish and high_confidence
187
+
188
+ def stop(self):
189
+ # Final report when backtest completes
190
+ self.log('Final Portfolio Value: %.2f' % self.broker.getvalue())
191
+
192
+ class BacktraderIntegration:
193
+ def __init__(self, analysis_result=None, strategy_params=None):
194
+ self.cerebro = bt.Cerebro()
195
+ self.analysis = analysis_result
196
+ self.strategy_params = strategy_params or {}
197
+ self.logger = Logger() # Create shared logger instance
198
+ self.setup_environment()
199
+
200
+ def setup_environment(self):
201
+ self.cerebro.broker.setcash(100000.0)
202
+ self.cerebro.broker.setcommission(commission=0.001)
203
+ self.cerebro.addstrategy(CustomStrategy, analysis=self.analysis, logger=self.logger, **self.strategy_params)
204
+
205
+ def add_data_feed(self, ticker, start_date, end_date):
206
+ # Convert datetime to string
207
+ start_str = start_date.strftime("%Y-%m-%d")
208
+ end_str = end_date.strftime("%Y-%m-%d")
209
+
210
+ # Download data from Yahoo Finance
211
+ df = YahooFinanceClient.download_data(ticker, start_str, end_str, period=None)
212
+
213
+
214
+ if not isinstance(df, pd.DataFrame):
215
+ raise TypeError(f"Esperado pandas.DataFrame, mas recebeu {type(df)}")
216
+
217
+ # adjust the columns
218
+ if isinstance(df.columns, pd.MultiIndex):
219
+ df.columns = df.columns.droplevel(1) # remove the multi-index
220
+
221
+ # minimum columns expected
222
+ expected_columns = ["Open", "High", "Low", "Close", "Volume"]
223
+
224
+ # Make sure that the columns are correct
225
+ if not all(col in df.columns for col in expected_columns):
226
+ raise ValueError(f"Colunas do DataFrame incorretas: {df.columns}")
227
+
228
+ data = bt.feeds.PandasData(dataname=df)
229
+ self.cerebro.adddata(data)
230
+
231
+ def run_simulation(self, initial_cash, commission):
232
+ self.cerebro.broker.setcash(initial_cash)
233
+ self.cerebro.broker.setcommission(commission=commission/100)
234
+ self.cerebro.run()
235
+
236
+ logs = self.logger.get_logs()
237
+
238
+ return self.cerebro.broker.getvalue(), logs
test.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ import random
3
+ import time
4
+
5
+ # Dummy lightweight LLM model function
6
+ def lightweight_llm(seed, difficulty_prefix):
7
+ """
8
+ Simulate the LLM finding a candidate hash starting with a given pattern.
9
+ The seed is used to generate a random number and the difficulty_prefix is what we aim for.
10
+ """
11
+ random.seed(seed + time.time())
12
+ candidate = f"{random.getrandbits(256):064x}"
13
+ if candidate.startswith(difficulty_prefix):
14
+ return candidate
15
+ return None
16
+
17
+ def mine_block(block_data, difficulty_prefix='000'):
18
+ """
19
+ Simplified mining function using a lightweight LLM model.
20
+ """
21
+ seed = random.randint(0, 1 << 32)
22
+ while True:
23
+ candidate_hash = lightweight_llm(seed, difficulty_prefix)
24
+ if candidate_hash:
25
+ # Incorporate block data and candidate hash
26
+ block_header = f"{block_data}{candidate_hash}"
27
+ final_hash = hashlib.sha256(block_header.encode()).hexdigest()
28
+ if final_hash.startswith(difficulty_prefix):
29
+ print(f"Block mined with candidate hash: {candidate_hash}")
30
+ return final_hash
31
+ # Optionally update the seed to vary the input
32
+ seed = random.randint(0, 1 << 32)
33
+
34
+ # Example block data and mining process
35
+ if __name__ == '__main__':
36
+ block_data = "previous_hash:0000000000000000000, transactions: [...]"
37
+ print("Starting mining process...")
38
+ new_block_hash = mine_block(block_data, difficulty_prefix='0000')
39
+ print(f"New block hash: {new_block_hash}")