feliponi commited on
Commit
4deea63
·
1 Parent(s): 95d9396

Primeiro commit

Browse files
Files changed (4) hide show
  1. __pycache__/stocks.cpython-311.pyc +0 -0
  2. app.py +244 -0
  3. requirements.txt +12 -0
  4. stocks.py +668 -0
__pycache__/stocks.cpython-311.pyc ADDED
Binary file (35.8 kB). View file
 
app.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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):
8
+ self.pipeline = pipeline
9
+ self.strategy_params = {
10
+ 'rsi_period': 14,
11
+ 'rsi_upper': 70,
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("### Parâmetros da Estratégia de Trading")
30
+ inputs = {}
31
+ inputs['rsi_period'] = gr.Number(value=14, label="Período RSI")
32
+ inputs['rsi_upper'] = gr.Number(value=70, label="Limite Superior RSI", minimum=0, maximum=100)
33
+ inputs['rsi_lower'] = gr.Number(value=30, label="Limite Inferior RSI", minimum=0, maximum=100)
34
+ inputs['sma_short'] = gr.Number(value=50, label="SMA Curta (período)")
35
+ inputs['sma_long'] = gr.Number(value=200, label="SMA Longa (período)")
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="Tamanho da Posição (%)")
39
+ inputs['atr_period'] = gr.Number(value=14, label="Período ATR")
40
+ inputs['atr_multiplier'] = gr.Number(value=3, label="Multiplicador ATR")
41
+ inputs['confidence_threshold'] = gr.Number(value=70, label="Nível de Confiança Mínima (%)", minimum=0, maximum=100)
42
+ inputs['sentiment_threshold'] = gr.Number(value=50, label="Nível Sentimento Mínimo (%)", minimum=0, maximum=100)
43
+ save_btn = gr.Button("Salvar Configurações")
44
+ # Adicionando a explicação em Markdown
45
+ gr.Markdown("""
46
+ ## 📊 Explicação dos Parâmetros da Estratégia de Trading
47
+
48
+ Esses parâmetros configuram indicadores técnicos para ajudar na decisão de compra e venda de ativos.
49
+
50
+ ### **📉 RSI (Relative Strength Index)**
51
+ O RSI é usado para medir a força do movimento de um ativo.
52
+
53
+ - **`rsi_period` (14)** → Número de períodos para calcular o RSI (padrão: 14).
54
+ - **`rsi_upper` (70)** → Se o RSI for maior que esse valor, pode indicar sobrecompra (sinal de venda).
55
+ - **`rsi_lower` (30)** → Se o RSI for menor que esse valor, pode indicar sobrevenda (sinal de compra).
56
+
57
+ ---
58
+
59
+ ### **📈 Médias Móveis Simples (SMA - Simple Moving Average)**
60
+ Indicadores que suavizam os preços ao longo do tempo.
61
+
62
+ - **`sma_short` (50)** → Média móvel curta, usada para capturar tendências de curto prazo.
63
+ - **`sma_long` (200)** → Média móvel longa, usada para capturar tendências de longo prazo.
64
+
65
+ ---
66
+
67
+ ### **📉 Gestão de Risco**
68
+
69
+ - **`max_loss_percent` (0.02)** → Stop Loss (limite de perda). Se o preço cair mais que 2%, a posição é fechada.
70
+ - **`take_profit_percent` (0.05)** → Take Profit (limite de lucro). Se o preço subir 5%, a posição é fechada.
71
+ - **`position_size` (0.1)** → Proporção do capital total que será usado em uma operação (10% do saldo).
72
+
73
+ ---
74
+
75
+ ### **📊 ATR (Average True Range) - Volatilidade**
76
+ O ATR é usado para medir a volatilidade do ativo.
77
+
78
+ - **`atr_period` (14)** → Número de períodos para calcular o ATR.
79
+ - **`atr_multiplier` (3)** → Multiplicador do ATR, geralmente usado para definir stop loss dinâmico.
80
+
81
+ ---
82
+
83
+ ## **🚀 Como esses parâmetros afetam a estratégia?**
84
+
85
+ - **Se `rsi_lower` for menor (ex: 20), a estratégia comprará em regiões mais sobrevendidas.**
86
+ - **Se `max_loss_percent` for muito pequeno, pode fechar trades prematuramente.**
87
+ - **Se `atr_multiplier` for maior, o stop loss será mais amplo e permitirá mais volatilidade.**
88
+ - **Se `sma_short` e `sma_long` estiverem distantes, as entradas serão mais conservadoras.**
89
+
90
+ Se precisar ajustar os valores para um backtest, posso sugerir otimizações! 🚀
91
+ """)
92
+
93
+ save_btn.click(
94
+ self.save_settings,
95
+ inputs=[v for v in inputs.values()],
96
+ outputs=None
97
+ )
98
+ return settings_interface
99
+
100
+ def save_settings(self, *args):
101
+ params = [
102
+ 'rsi_period', 'rsi_upper', 'rsi_lower',
103
+ 'sma_short', 'sma_long', 'max_loss_percent',
104
+ 'take_profit_percent', 'position_size',
105
+ 'atr_period', 'atr_multiplier', 'confidence_threshold', 'sentiment_threshold'
106
+ ]
107
+
108
+ self.strategy_params = dict(zip(params, args))
109
+ print("Parâmetros atualizados:", self.strategy_params)
110
+ return gr.Info("Configurações salvas com sucesso!")
111
+
112
+ def create_main_interface(self):
113
+ with gr.Blocks() as main_interface:
114
+ with gr.Row():
115
+ with gr.Column():
116
+ ticker_input = gr.Text(label="Ticker (ex: AAPL)")
117
+ api_key_input = gr.Textbox(label="API Key (opcional)", placeholder="Insira sua API Key https://newsapi.org/")
118
+ fetch_new = gr.Dropdown([True, False], label="Buscar noticias online?", value=False)
119
+ initial_investment = gr.Number(10000, label="Investimento Inicial (USD)")
120
+ years_back = gr.Number(5, label="Período Histórico (anos)")
121
+ commission = gr.Number(0.001, label="Comissão por Trade")
122
+ run_btn = gr.Button("Executar Análise e Simulação")
123
+ with gr.Column():
124
+ plot_output = gr.Plot()
125
+ with gr.Row():
126
+ # Adicionar uma saída para os resultados
127
+ output_md = gr.Markdown()
128
+
129
+ run_btn.click(
130
+ self.run_full_analysis,
131
+ inputs=[ticker_input, fetch_new, initial_investment, years_back, commission, api_key_input],
132
+ outputs=[output_md, plot_output]
133
+ )
134
+ return main_interface
135
+
136
+ def run_full_analysis(self, ticker, fetch_new, initial_investment, years_back, commission, api_key):
137
+ # Atualizar os parâmetros da pipeline
138
+ self.pipeline.set_sentiment_threshold(float(self.strategy_params['sentiment_threshold'])/100)
139
+ self.pipeline.set_confidence_threshold(float(self.strategy_params['confidence_threshold'])/100)
140
+
141
+ # Executar análise
142
+ result = self.pipeline.analyze_company(
143
+ ticker=ticker,
144
+ news_api_key=api_key,
145
+ fetch_new=fetch_new
146
+ )
147
+
148
+ if not result:
149
+ return "Erro na análise", None
150
+
151
+ # Configurar simulação
152
+ end_date = datetime.now()
153
+ start_date = end_date - timedelta(days=int(years_back*365))
154
+
155
+ # Criar estratégia personalizada com os parâmetros
156
+ custom_strategy_params = {
157
+ 'rsi_period': int(self.strategy_params['rsi_period']),
158
+ 'rsi_upper': int(self.strategy_params['rsi_upper']),
159
+ 'rsi_lower': int(self.strategy_params['rsi_lower']),
160
+ 'sma_short': int(self.strategy_params['sma_short']),
161
+ 'sma_long': int(self.strategy_params['sma_long']),
162
+ 'max_loss_percent': float(self.strategy_params['max_loss_percent']),
163
+ 'take_profit_percent': float(self.strategy_params['take_profit_percent']),
164
+ 'position_size': float(self.strategy_params['position_size']),
165
+ 'atr_period': int(self.strategy_params['atr_period']),
166
+ 'atr_multiplier': int(self.strategy_params['atr_multiplier']),
167
+ 'confidence_threshold' : float(self.strategy_params['confidence_threshold'])/100,
168
+ 'sentiment_threshold' : float(self.strategy_params['sentiment_threshold'])/100
169
+ }
170
+
171
+ # Criar uma instância de Progress
172
+ progress = gr.Progress()
173
+
174
+ # Atualizar progresso
175
+ progress(0.3, desc="Preparando simulação...")
176
+
177
+ # Executar simulação
178
+ bt_integration = st.BacktraderIntegration(analysis_result=result,strategy_params=custom_strategy_params)
179
+ bt_integration.add_data_feed(ticker, start_date, end_date)
180
+
181
+ progress(0.6, desc="Executando simulação...")
182
+
183
+ final_value = bt_integration.run_simulation(
184
+ initial_cash=initial_investment,
185
+ commission=commission
186
+ )
187
+
188
+ progress(0.9, desc="Gerando resultados...")
189
+
190
+ # Extrair os valores do JSON de sentimento
191
+ sentiment = result['sentiment']['sentiment']
192
+ negative_sentiment = sentiment.get('negativo', 0.0)
193
+ neutral_sentiment = sentiment.get('neutral', 0.0)
194
+ positive_sentiment = sentiment.get('positive', 0.0)
195
+
196
+ # Gerar saída formatada em Markdown
197
+ output = f"""
198
+ ## Recomendação: {result['recommendation']}
199
+
200
+ **Confiança**: {result['confidence']['total_confidence']:.2%}
201
+ **Retorno da Simulação**: {(final_value/initial_investment-1)*100:.2f}%
202
+
203
+ ### Detalhes:
204
+
205
+ - **Sentimento Negativo**: {negative_sentiment:.2%}
206
+ - **Sentimento Neutro**: {neutral_sentiment:.2%}
207
+ - **Sentimento Positivo**: {positive_sentiment:.2%}
208
+
209
+ - **RSI**: {result['technical']['rsi']:.1f}
210
+ - **Preço vs SMA50**: {result['technical']['price_vs_sma']:.2%}
211
+ - **P/E Ratio**: {result['fundamental'].get('trailingPE', 'N/A')}
212
+ """
213
+ # Gerar gráfico simples (exemplo)
214
+ plot = self.generate_simple_plot(bt_integration)
215
+
216
+ return output, plot
217
+
218
+
219
+ def generate_simple_plot(self, bt_integration):
220
+ # Implemente aqui a geração do gráfico usando matplotlib
221
+ import matplotlib.pyplot as plt
222
+
223
+ plt.figure(figsize=(10, 6))
224
+ # Exemplo: Plotar preço de fechamento
225
+ data = bt_integration.cerebro.datas[0].close.array
226
+ plt.plot(data, label='Preço')
227
+ plt.title("Desempenho Histórico")
228
+ plt.legend()
229
+ return plt.gcf()
230
+
231
+ # Configuração da interface completa
232
+ pipeline = st.AnalysisPipeline()
233
+
234
+ interface = GradioInterface(pipeline)
235
+
236
+ demo = gr.TabbedInterface(
237
+ [interface.create_main_interface(), interface.create_settings_interface()],
238
+ ["Análise Principal", "Configurações da Estratégia"],
239
+ title="Stock Analyst Pro"
240
+ )
241
+
242
+ if __name__ == "__main__":
243
+ #demo.launch(share=True)
244
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=3.0.0
2
+ matplotlib>=3.0.0
3
+ pandas>=1.0.0
4
+ numpy>=1.0.0
5
+ backtrader>=1.9.0
6
+ requests>=2.0.0
7
+ python-dateutil>=2.8.0
8
+ yfinance>=0.2.0
9
+ torch>=1.0.0
10
+ transformers>=4.0.0
11
+ sqlalchemy>=1.4.0
12
+ newsapi-python>=0.1.6
stocks.py ADDED
@@ -0,0 +1,668 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = {
159
+ 'sentiment': 0.4,
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': {
178
+ 'sentiment': sentiment_score,
179
+ 'technical': technical_score,
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,
352
+ 'technical': technical,
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
+ def run_simulation(self, initial_cash, commission):
436
+ self.cerebro.broker.setcash(initial_cash)
437
+ self.cerebro.broker.setcommission(commission=commission)
438
+ print(f'\nStarting Portfolio Value: {self.cerebro.broker.getvalue():.2f}')
439
+ self.cerebro.run()
440
+ print(f'Final Portfolio Value: {self.cerebro.broker.getvalue():.2f}')
441
+ return self.cerebro.broker.getvalue()
442
+
443
+ class CustomStrategy(bt.Strategy):
444
+ params = (
445
+ ('analysis', None),
446
+ ('rsi_period', 14),
447
+ ('rsi_upper', 70),
448
+ ('rsi_lower', 30),
449
+ ('sma_short', 50),
450
+ ('sma_long', 200),
451
+ ('max_loss_percent', 0.02),
452
+ ('take_profit_percent', 0.05),
453
+ ('position_size', 0.1),
454
+ ('atr_period', 14),
455
+ ('atr_multiplier', 3),
456
+ ('sentiment_threshold', 0.6), # Novo parâmetro
457
+ ('confidence_threshold', 0.7) # Novo parâmetro
458
+ )
459
+
460
+ def __init__(self):
461
+ # Parâmetros agora são acessados via self.params
462
+ self.recommendation = self.params.analysis['recommendation'] if self.params.analysis else 'HOLD'
463
+ self.technical_analysis = self.params.analysis['technical'] if self.params.analysis else None
464
+ self.sentiment_analysis = self.params.analysis['sentiment'] if self.params.analysis else None
465
+ self.confidence = self.params.analysis['confidence']['total_confidence'] if self.params.analysis else 0.5
466
+
467
+ # Indicadores usando parâmetros dinâmicos
468
+ self.rsi = bt.indicators.RSI(
469
+ self.data.close,
470
+ period=self.params.rsi_period
471
+ )
472
+
473
+ self.sma_short = bt.indicators.SMA(
474
+ self.data.close,
475
+ period=self.params.sma_short
476
+ )
477
+
478
+ self.sma_long = bt.indicators.SMA(
479
+ self.data.close,
480
+ period=self.params.sma_long
481
+ )
482
+
483
+
484
+ # Technical Indicators
485
+ self.rsi = bt.indicators.RSI(self.data.close, period=self.p.rsi_period)
486
+ self.sma_short = bt.indicators.SMA(self.data.close, period=self.p.sma_short)
487
+ self.sma_long = bt.indicators.SMA(self.data.close, period=self.p.sma_long)
488
+
489
+ # Volatility Indicator
490
+ self.atr = bt.indicators.ATR(self.data, period=self.p.atr_period)
491
+
492
+ # Trading management
493
+ self.order = None
494
+ self.stop_price = None
495
+ self.take_profit_price = None
496
+ self.buy_price = None
497
+ self.entry_date = None
498
+
499
+
500
+ def log(self, txt, dt=None):
501
+ dt = dt or self.datas[0].datetime.date(0)
502
+ print(f'{dt.isoformat()}, {txt}')
503
+
504
+ def notify_order(self, order):
505
+ if order.status in [order.Submitted, order.Accepted]:
506
+ return
507
+
508
+ if order.status in [order.Completed]:
509
+ if order.isbuy():
510
+ self.log(f'BUY EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm {order.executed.comm:.2f}')
511
+ self.buy_price = order.executed.price
512
+ self.entry_date = self.datas[0].datetime.date(0)
513
+ else:
514
+ self.log(f'SELL EXECUTED, Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Comm {order.executed.comm:.2f}')
515
+
516
+ self.order = None
517
+
518
+ def notify_trade(self, trade):
519
+ if not trade.isclosed:
520
+ return
521
+
522
+ self.log(f'TRADE PROFIT, GROSS: {trade.pnl:.2f}, NET: {trade.pnlcomm:.2f}')
523
+
524
+ def calculate_position_size(self):
525
+ portfolio_value = self.broker.getvalue()
526
+ return int((portfolio_value * self.p.position_size) / self.data.close[0])
527
+
528
+ def next(self):
529
+ # Prevent multiple orders
530
+ if self.order:
531
+ return
532
+
533
+ current_price = self.data.close[0]
534
+ portfolio_value = self.broker.getvalue()
535
+
536
+ # Usar parâmetros dinâmicos nas regras
537
+ stop_loss = current_price * (1 - self.params.max_loss_percent)
538
+ take_profit = current_price * (1 + self.params.take_profit_percent)
539
+
540
+ # Analyze prior analysis for additional confirmation
541
+ analysis_confirmation = self._analyze_prior_research()
542
+
543
+ # No open position - look for entry
544
+ if not self.position:
545
+ # Enhanced entry conditions
546
+ # Condições com parâmetros ajustáveis
547
+ entry_conditions = (
548
+ current_price > self.sma_long[0] and
549
+ self.rsi[0] < self.params.rsi_lower and
550
+ bool(self.params.analysis['confidence']['total_confidence'] > self.p.confidence_threshold)
551
+ )
552
+
553
+ if entry_conditions:
554
+ # Calculate position size
555
+ size = self.calculate_position_size()
556
+
557
+ # Place buy order
558
+ self.order = self.buy(size=size)
559
+
560
+ # Calculate stop loss and take profit
561
+ stop_loss = current_price * (1 - self.p.max_loss_percent)
562
+ take_profit = current_price * (1 + self.p.take_profit_percent)
563
+
564
+ # Alternative stop loss using ATR for volatility
565
+ atr_stop = current_price - (self.atr[0] * self.p.atr_multiplier)
566
+ self.stop_price = max(stop_loss, atr_stop)
567
+ self.take_profit_price = take_profit
568
+
569
+ # Manage existing position
570
+ else:
571
+ # Exit conditions
572
+ exit_conditions = (
573
+ current_price < self.stop_price or # Stop loss triggered
574
+ current_price > self.take_profit_price or # Take profit reached
575
+ self.rsi[0] > self.p.rsi_upper or # Overbought condition
576
+ current_price < self.sma_short[0] or # Trend change
577
+ not analysis_confirmation # Loss of analysis confirmation
578
+ )
579
+
580
+ if exit_conditions:
581
+ self.close() # Close entire position
582
+ self.stop_price = None
583
+ self.take_profit_price = None
584
+
585
+ def _analyze_prior_research(self):
586
+ # Integrate multiple analysis aspects
587
+ if not self.p.analysis:
588
+ return True
589
+
590
+ # Sentiment analysis check
591
+ sentiment_positive = (
592
+ self.sentiment_analysis and
593
+ self.sentiment_analysis['sentiment']['positive'] > self.p.sentiment_threshold
594
+ )
595
+
596
+ # Technical analysis check
597
+ technical_bullish = (
598
+ self.technical_analysis and
599
+ self.technical_analysis['trend'] == 'bullish'
600
+ )
601
+
602
+ # Confidence check
603
+ high_confidence = bool(self.confidence > self.p.confidence_threshold)
604
+
605
+ # Combine conditions
606
+ return sentiment_positive and technical_bullish and high_confidence
607
+
608
+ def stop(self):
609
+ # Final report when backtest completes
610
+ self.log('Final Portfolio Value: %.2f' % self.broker.getvalue())
611
+
612
+ # 8. Main
613
+ if __name__ == "__main__":
614
+ pipeline = AnalysisPipeline()
615
+
616
+ print("\n=== Analysis of Stock-Market ===")
617
+
618
+ # 1. Requesting the company ticker
619
+ ticker = input("Type the company ticker (ex: AAPL): ").strip().upper()
620
+
621
+ # 2. Requesting if the user wants to fetch new data
622
+ while True:
623
+ fetch_new = input("Would you like to have new data from internet? (y/n): ").lower()
624
+ if fetch_new in ['y', 'n', 'yes', 'no', 'no']:
625
+ fetch_new_bool = fetch_new in ['y', 'no']
626
+ break
627
+ print("Not a valid option! Type y or n")
628
+
629
+ initial_investment = float(input("Inicial Investment (USD): "))
630
+ years_back = int(input("Historical Period (years): "))
631
+ commission = float(input("Commission per trade: "))
632
+
633
+ # 3. API Key for NewsAPI
634
+ news_api_key = '85bfdbb4f83f4b148cd219196b4b6447'
635
+
636
+ # 4. Running the analysis
637
+ print(f"\nRunning the analysis with Machine Learning {ticker}...")
638
+ result = pipeline.analyze_company(
639
+ ticker=ticker,
640
+ news_api_key=news_api_key if news_api_key else None,
641
+ fetch_new=fetch_new_bool
642
+ )
643
+
644
+ # 5. Showing the results
645
+ if result:
646
+
647
+ # Running the simulation with Backtrader
648
+ end_date = datetime.now()
649
+ start_date = end_date - timedelta(days=years_back*365)
650
+
651
+ bt_integration = BacktraderIntegration(result)
652
+ bt_integration.add_data_feed(ticker, start_date, end_date)
653
+ final_value = bt_integration.run_simulation(initial_investment, commission)
654
+
655
+ print("\n=== Analysis Result ===")
656
+ print(f"Recommendation: {result['recommendation']}")
657
+ print(f"Confidence: {result['confidence']['total_confidence']:.2%}")
658
+ print(f"Return of the Simulation: {(final_value/initial_investment-1)*100:.2f}%")
659
+ print("\nDetails:")
660
+ print(f"1. Sentiment: {json.dumps(result['sentiment']['sentiment'], indent=2)}")
661
+ print(f"2. Technical Analysis: RSI {result['technical']['rsi']:.1f}, Price vs SMA50: {result['technical']['price_vs_sma']:.2%}")
662
+ print(f"3. Fundamental: P/E {result['fundamental'].get('trailingPE', 'N/A')} vs Sctor {result['fundamental'].get('sectorPE', 'N/A')}")
663
+ print(f"4. Confidence Components: {json.dumps(result['confidence']['components'], indent=2)}")
664
+ else:
665
+ print("\nIt was not possible to run the analysis, please check:")
666
+ print("- Internet connection")
667
+ print("- Ticker value")
668
+ print("- Historical data availability")