diff --git a/README.md b/README.md
index b93f063..2175ffe 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,8 @@
This aplication is a exercice working with Qt, PyQt arround the finance.
It is largely inspired by `TradingView` which is a wonderfull tool for finance analysis.
+
+

## Installation
@@ -44,4 +46,9 @@ It is largely inspired by `TradingView` which is a wonderfull tool for finance a
- [ ] Save users settings
- [ ] Embeded Python Console ?
- [ ] Draw over the graph
-- [ ] Use Machine learning to determine patterns
\ No newline at end of file
+- [ ] Use Machine learning to determine patterns
+- [ ] Draw Fibonnaci on Chart => 50% done
+- [ ] Make Virtual Portefolio
+- [ ] Avaible Trading
+- [ ] SplashScreen Loading
+- [ ] Find Compagnies with differents stratgies (Buffet, Peter Lynch, Carl Icahn)
\ No newline at end of file
diff --git a/app/add_ons/indicators/fibonnaci.py b/app/add_ons/indicators/fibonnaci.py
new file mode 100644
index 0000000..9b95950
--- /dev/null
+++ b/app/add_ons/indicators/fibonnaci.py
@@ -0,0 +1,177 @@
+import numpy as np
+import pyqtgraph as pg
+
+from utils.indicators_utils import Indicator, InputField, ChoiceField
+
+RATIO = [78.6, 61.8, 50.0, 38.2, 23.6]
+COLORS = {
+ 78.6: (255, 56, 56),
+ 61.8: (218, 255, 56),
+ 50.0: (56, 255, 82),
+ 38.2: (148, 56, 255),
+ 23.6: (56, 95, 255),
+ }
+
+class Fibonnaci(Indicator):
+ def __init__(self):
+ super(Fibonnaci, self).__init__()
+
+ self.name = "Fibonnaci"
+ self.description = "Fibonnaci"
+
+ # Define and register all customisable settings
+ # field_input = ChoiceField(
+ # "Input", choices=["Open", "Close", "High", "Low"], default="Close"
+ # )
+ # self.register_field(field_input)
+ # line1 = InputField(
+ # "Fibonnaci", color=(51, 153, 255), value=3, width=2
+ # )
+ # self.register_fields(line1)
+
+ def create_indicator(self, graph_view, *args, **kwargs):
+ super(Fibonnaci, self).create_indicator(self, graph_view)
+
+ # Get values
+ values = graph_view.values
+ self.quotation_plot = graph_view.g_quotation
+
+ self.roi = pg.RectROI([347, 113], [50, 60],
+ invertible=True,
+ pen=pg.mkPen(color=(255, 255, 255),
+ width=1,),
+ hoverPen=None,
+ )
+
+ self.roi.addTranslateHandle((0,0))
+ self.quotation_plot.addItem(self.roi)
+
+ self.set_fibonnaci_levels()
+ self.roi.sigRegionChanged.connect(self.move_items)
+
+ def move_items(self):
+ """This method is call everytime the ROI is move.
+ """
+ self.set_fibonnaci_levels()
+
+ def set_fibonnaci_levels(self):
+ try:
+ self.quotation_plot.removeItem(self.line_78)
+ self.quotation_plot.removeItem(self.label_78)
+ self.quotation_plot.removeItem(self.line_61)
+ self.quotation_plot.removeItem(self.label_61)
+ self.quotation_plot.removeItem(self.line_50)
+ self.quotation_plot.removeItem(self.label_50)
+ self.quotation_plot.removeItem(self.line_38)
+ self.quotation_plot.removeItem(self.label_38)
+ self.quotation_plot.removeItem(self.line_23)
+ self.quotation_plot.removeItem(self.label_23)
+ self.quotation_plot.removeItem(self.label_1)
+ self.quotation_plot.removeItem(self.label_100)
+ except:
+ pass
+
+ self.line_78 = self._set_fibonnaci_level(prc=78.6)
+ self.label_78 = self._set_label_level(prc=78.6)
+
+ self.line_61 = self._set_fibonnaci_level(prc=61.8)
+ self.label_61 = self._set_label_level(prc=61.8)
+
+ self.line_50 = self._set_fibonnaci_level(prc=50.0)
+ self.label_50 = self._set_label_level(prc=50.0)
+
+ self.line_38 = self._set_fibonnaci_level(prc=38.2)
+ self.label_38 = self._set_label_level(prc=38.2)
+
+ self.line_23 = self._set_fibonnaci_level(prc=23.6)
+ self.label_23 = self._set_label_level(prc=23.6)
+
+ self.label_100 = self._set_label_level(prc=100)
+ self.label_1 = self._set_label_level(prc=1)
+
+ self.quotation_plot.addItem(self.line_78)
+ self.quotation_plot.addItem(self.label_78)
+ self.quotation_plot.addItem(self.line_61)
+ self.quotation_plot.addItem(self.label_61)
+ self.quotation_plot.addItem(self.line_50)
+ self.quotation_plot.addItem(self.label_50)
+ self.quotation_plot.addItem(self.line_38)
+ self.quotation_plot.addItem(self.label_38)
+ self.quotation_plot.addItem(self.line_23)
+ self.quotation_plot.addItem(self.label_23)
+ self.quotation_plot.addItem(self.label_100)
+ self.quotation_plot.addItem(self.label_1)
+
+ def _set_fibonnaci_level(self, prc=50.0):
+ """This method plot line for each retracement
+ :param widget: ROIWidget
+ :type widget: PQQt.GraphWidget
+ """
+ color = COLORS[prc]
+ xpos, ypos = self.position_line(prc)
+
+ line = pg.LineSegmentROI(positions=(xpos, ypos),
+ pen=pg.mkPen(color, width=2),
+ movable=False,
+ rotatable=False,
+ resizable=False,
+ )
+ line.setSelected(False)
+ return line
+
+
+ def _set_label_level(self, prc=50.0):
+ """This method plot label for each retracement
+ :param widget: TextItem
+ :type widget: PQQt.GraphWidget
+ """
+ xpos, ypos = self.position_line(prc)
+
+ percentg_lb = "0.{}".format(int(prc))
+ label = pg.TextItem(text=' {} ({})'.format(percentg_lb, round(ypos[1], 2)),
+ anchor=(0, 0.5),
+ )
+
+ # Lock Label to the Right of ROI
+ if xpos[0] < ypos[0]:
+ position = ypos[0]
+ else:
+ position = xpos[0]
+
+ label.setPos(position, ypos[1])
+ return label
+
+ def position_line(self, prc=50.0):
+ """This method return the graph position for the levels
+ """
+ rtc = self._get_fibonnaci_level(prc)[0]
+ x_pos = [self.roi.pos()[0], rtc]
+ y_pos = [self.roi.pos()[0] + self.roi.size()[0], rtc]
+ return x_pos, y_pos
+
+
+ def _get_fibonnaci_level(self, prc):
+ """This method calcul the retracement level calculating the difference
+ between the min/max of the ROI.
+ :param prc: Percentage to calculate
+ :type float:
+ """
+ # position is lower-left ROI
+ # use x_min and y_min with width and height to find the position
+ # on the graph.
+ pos = self.roi.getState()['pos']
+ size = self.roi.getState()['size']
+
+ y_min = pos[1]
+ y_max = pos[1] + size[1]
+ variation = y_max - y_min
+
+ retracement = []
+ retrc = variation * (prc / 100)
+ value = y_min + retrc
+ retracement.append(value)
+
+ return retracement
+
+ def remove_indicator(self, graph_view, *args, **kwargs):
+ super(Fibonnaci, self).remove_indicator(graph_view)
diff --git a/app/libs/analysies/analyse_financials.py b/app/libs/analysies/analyse_financials.py
index 83553b4..04240cc 100644
--- a/app/libs/analysies/analyse_financials.py
+++ b/app/libs/analysies/analyse_financials.py
@@ -18,7 +18,7 @@
from pprint import pprint
from utils import utils as utl
from libs.yahoo_fin import stock_info as sf
-from .analyse import AnalyseData
+from libs.analysies.analyse import AnalyseData
class AnalyseFondamental(object):
@@ -34,12 +34,6 @@ def __init__(self, ticker):
self.year_atual = self.resultat_datas.keys()[0]
self.year_before = self.resultat_datas.keys()[1]
- # pprint(self.resultat_datas)
- # pprint(self.balance_datas)
- # pprint(self.cash_flow_datas)
- # pprint(self.per_datas)
- # pprint(self.statistic_datas)
-
self.datas = {}
self.data_analyse = {}
@@ -50,6 +44,7 @@ def __init__(self, ticker):
self.analyse = AnalyseData(self.data_analyse)
self.extend_dict_data()
+ self.tes()
def set_var(self):
self.actions = None
@@ -202,7 +197,7 @@ def datas_dict(self):
self.datas["Capitaux Propre"] = utl.format_data(
self.total_capitaux_propre.values.tolist()
)
- self.datas["Score"] = [self.total_score()]
+ self.datas["Score"] = ["", "", "", "", self.total_score()]
self.bna_years()
self.per_years()
@@ -213,6 +208,12 @@ def datas_dict(self):
self.roe_roa_ratio(roa=False)
self.roe_roa_ratio(roa=True)
+
+ def tes(self):
+ for key, value in self.datas.items():
+ if len(value) <= 4:
+ self.datas[key].append("")
+
def data_for_analyse(self):
self.data_analyse["Actifs Total"] = self.datas["Actifs Total"]
self.data_analyse["BNA"] = self.datas["BNA"]
@@ -222,7 +223,6 @@ def data_for_analyse(self):
"Capitaux Propre"
] = self.total_capitaux_propre.values.tolist()
self.data_analyse["Chiffre d'affaires"] = self.chiffre_affaire.tolist()
- self.data_analyse["Dividendes"] = self.datas["Dividendes"]
self.data_analyse["EBITDA"] = self.ebitda.values.tolist()
self.data_analyse["PER"] = self.datas["PER"]
self.data_analyse["ROA"] = self.datas["ROA"]
@@ -294,7 +294,10 @@ def dividendes_ratio(self):
calcul_rend = round(calcul_rend, 2)
rendement.append("{}€".format(calcul_rend))
- self.datas["Dividendes"] = dividendes
+ self.data_analyse["Dividendes"] = dividendes
+ self.datas["Dividendes"] = []
+ for i in dividendes:
+ self.datas["Dividendes"].append(" ".join(["{}€".format(str(x)) for x in i]))
self.datas["Dividendes Rendement"] = [
"{}%".format(i) for i in rendement
]
@@ -339,6 +342,6 @@ def extend_dict_data(self):
if __name__ == "__main__":
test = AnalyseFondamental("AAPL")
- pprint(test.data_analyse)
- pprint(test.analyse.__dict__)
+ pprint(test.datas)
+ # pprint(test.analyse.__dict__)
# testq = AnalyseFondamental("BN.PA")
diff --git a/app/libs/articles/get_articles.py b/app/libs/articles/boursorama_articles.py
similarity index 92%
rename from app/libs/articles/get_articles.py
rename to app/libs/articles/boursorama_articles.py
index 88b27b9..5e32c0b 100644
--- a/app/libs/articles/get_articles.py
+++ b/app/libs/articles/boursorama_articles.py
@@ -32,14 +32,15 @@ def __init__(self, ticker=None):
self.get_url(ticker=ticker)
def get_url(self, ticker):
- if "{}.PA".format(ticker) in sf.tickers_cac().keys():
+ if "{}".format(ticker) in sf.tickers_cac().keys():
url = "https://www.boursorama.com/cours/actualites/1rP{}/".format(
- ticker
+ ticker.split('.')[0]
)
else:
url = "https://www.boursorama.com/cours/actualites/{}/".format(
ticker
)
+ print(url)
self.response = requests.get(url, headers=self.headers)
self.soup()
@@ -61,8 +62,8 @@ def soup(self):
self.articles.append(
{
"title": title,
- "date": time,
- "descritpion": descritpion,
+ "published": time,
+ "summary": descritpion,
"link": "https://www.boursorama.com{}".format(link),
}
)
diff --git a/app/libs/articles/yahoo_articles.py b/app/libs/articles/yahoo_articles.py
new file mode 100644
index 0000000..b9e5bdd
--- /dev/null
+++ b/app/libs/articles/yahoo_articles.py
@@ -0,0 +1,86 @@
+#
+# This Class get all articles from selected ticker
+# from the Yahoo website.
+
+import requests
+from bs4 import BeautifulSoup
+from utils import utils
+from pprint import pprint
+from libs.yahoo_fin import news
+
+
+class ArticlesYahoo(object):
+ def __init__(self):
+ super(ArticlesYahoo, self).__init__()
+
+ self.articles = None
+
+ def get_articles_from_compagny(self, ticker):
+ self.articles = news.get_yf_rss(ticker)
+ compagny = utils.get_compagny_name_from_tick(ticker=ticker)
+
+ if not self.articles:
+ return
+
+ for article in self.articles:
+ article['compagny'] = compagny
+ self.get_thumbnail_link(article)
+ article['summary'] = self.cup_long_text(article['summary'])
+
+ return self.articles
+
+ def get_home_articles(self):
+ self.articles = news.get_yf_home_rss()
+
+ if not self.articles:
+ return
+
+ for article in self.articles:
+ article['published'] = article['published'].replace('T', ' ')
+ article['summary'] = ""
+ try:
+ article['img'] = article['media_content'][0]['url']
+ except:
+ article['img'] = ""
+
+
+ return self.articles
+
+ def get_thumbnail_link(self, article):
+ """
+ This method scrap the img url of the article from the website.
+ """
+ header = {
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36",
+ }
+ link = article['link']
+ request = requests.get(link, headers=header)
+ soup = BeautifulSoup(request.text, "html.parser")
+
+ try:
+ img = soup.findAll('img', {"class": "caas-img"})
+ img = img[-1]['src']
+ except:
+ img = None
+ article['img'] = img
+
+ return article
+
+
+ def cup_long_text(self, value):
+ """
+ This method cut long description.
+ return: list.
+ """
+ len_chart = 180
+ if len(value) > len_chart:
+ value = "{}...".format(value[0:len_chart])
+ return value
+
+
+if __name__ == '__main__':
+ tick = "MSFT"
+ x = ArticlesYahoo()
+ # articles = x.get_articles_from_compagny(tick)
+ articles = x.get_home_articles()
+ pprint(articles)
diff --git a/app/libs/articles_widget.py b/app/libs/articles_widget.py
index 5d047a0..2886a55 100644
--- a/app/libs/articles_widget.py
+++ b/app/libs/articles_widget.py
@@ -1,104 +1,38 @@
-import webbrowser
-from PySide2 import QtWidgets, QtGui
-from libs.thread_pool import ThreadPool
-from libs.articles.get_articles import Articles
+#
+# This class set all the articles from a selected compagny.
+#
+from pprint import pprint
+from PySide2 import QtWidgets, QtCore
+from libs.articles.yahoo_articles import ArticlesYahoo
+from libs.widgets.article_itemwidget import ArticlesWidgetItem
-class ArticlesWidget(QtWidgets.QWidget):
+
+class ArticlesWidget(QtWidgets.QListWidget):
def __init__(self, parent=None, ticker=None):
super(ArticlesWidget, self).__init__(parent)
+ # self._on_get_articles(ticker)
+
+ @QtCore.Slot(str)
+ def _on_get_articles(self, ticker):
+ articles = self._get_articles_dict(ticker=ticker)
+ self.clear()
+
+ for index, i in enumerate(articles):
+ article = ArticlesWidgetItem(parent=self)
+ article.set_title(i["title"], i["link"])
+ article.set_compagny(i["compagny"])
+ article.set_date(i["published"])
+ article.set_description(i["summary"])
+ article.set_thumbnail(i["img"])
+ item = QtWidgets.QListWidgetItem()
+ item.setSizeHint(article.sizeHint())
+ self.addItem(item)
+ self.setItemWidget(item, article)
- self.thread_pool = ThreadPool()
- self.get_articles(ticker)
-
- def get_articles(self, ticker):
- if ticker:
- articles = self._get_articles_dict(ticker=ticker).articles
-
- else:
- # Remplacer par 2 3 ticker Random
- articles = self._get_articles_dict(ticker="GLE").articles
-
- scroll = QtWidgets.QScrollArea(self)
- widget = QtWidgets.QWidget()
- container = QtWidgets.QVBoxLayout(widget)
-
- for i in articles:
- title = i["title"]
- date = i["date"]
- description = i["descritpion"]
- link = i["link"]
- article = ArticlesWidgetItem(
- parent=self,
- title=title,
- date=date,
- description=description,
- link=link,
- )
- container.addWidget(article)
-
- widget.setLayout(container)
- scroll.setWidget(widget)
- scroll.setWidgetResizable(True)
-
- layout = QtWidgets.QVBoxLayout(self)
- layout.addWidget(scroll)
def _get_articles_dict(self, ticker):
- return Articles(ticker=ticker)
-
-
-class ArticlesWidgetItem(QtWidgets.QWidget):
- def __init__(
- self, parent=None, title=None, date=None, description=None, link=None
- ):
- super(ArticlesWidgetItem, self).__init__(parent)
-
- self.layout = QtWidgets.QVBoxLayout(self)
- self.layout.setObjectName(u"layout")
- self.title = LabelTitle(title, link)
- self.title.setObjectName(u"title")
- self.layout.addWidget(self.title)
-
- self.date = QtWidgets.QLabel(self)
- self.date.setObjectName(u"date")
- self.date.setText(date)
- self.date.setFont(QtGui.QFont("Times", 10))
- self.layout.addWidget(self.date)
-
- self.description = Description(description)
- self.description.setObjectName(u"description")
- self.description.setEnabled(True)
- self.layout.addWidget(self.description)
-
- self.spliter = QHLine()
- self.layout.addWidget(self.spliter)
-
-
-class QHLine(QtWidgets.QFrame):
- def __init__(self):
- super(QHLine, self).__init__()
- self.setFrameShape(QtWidgets.QFrame.HLine)
- self.setFrameShadow(QtWidgets.QFrame.Sunken)
-
-
-class LabelTitle(QtWidgets.QLabel):
- def __init__(self, text, link=None):
- super(LabelTitle, self).__init__()
- self.link = link
-
- if text:
- self.setText(text)
- self.setFont(QtGui.QFont("Times", 20))
-
- def mousePressEvent(self, event) -> None:
- webbrowser.open(self.link)
+ article = ArticlesYahoo()
+ return article.get_articles_from_compagny(ticker=ticker)
-class Description(QtWidgets.QTextBrowser):
- def __init__(self, text):
- super(Description, self).__init__()
- self.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
- if text:
- self.setText(text)
- self.setFont(QtGui.QFont("Times", 10))
diff --git a/app/libs/events_handler.py b/app/libs/events_handler.py
index 2021684..167c240 100644
--- a/app/libs/events_handler.py
+++ b/app/libs/events_handler.py
@@ -22,7 +22,7 @@ class EventHandler(QtCore.QObject):
sig_indicator_settings_canceled = QtCore.Signal(object)
sig_indicator_settings_reseted = QtCore.Signal(object)
- sig_action_triggered = QtCore.Signal(str)
+ sig_action_triggered = QtCore.Signal(str, dict)
sig_favorite_created = QtCore.Signal(str)
sig_favorite_loaded = QtCore.Signal(list)
@@ -32,3 +32,8 @@ class EventHandler(QtCore.QObject):
sig_favorite_clicked = QtCore.Signal(str)
sig_articles = QtCore.Signal(dict)
+
+ sig_graph_clicked = QtCore.Signal(list, object)
+ sig_graph_mouse_moved = QtCore.Signal(object)
+ sig_graph_mouse_pressed = QtCore.Signal(object)
+ sig_graph_mouse_released = QtCore.Signal(object)
diff --git a/app/libs/financial_widget.py b/app/libs/financial_widget.py
new file mode 100644
index 0000000..2f43d7a
--- /dev/null
+++ b/app/libs/financial_widget.py
@@ -0,0 +1,82 @@
+from pprint import pprint
+from PySide2 import QtWidgets, QtCore, QtGui
+from libs.analysies.analyse_financials import AnalyseFondamental
+
+class QTableWidgetFinance(QtWidgets.QTableWidget):
+ def __init__(self, parent=None):
+ super(QTableWidgetFinance, self).__init__(parent=parent)
+
+
+class TableFinance(QTableWidgetFinance):
+ def __init__(self, parent=None):
+ super(TableFinance, self).__init__(parent=parent)
+
+ @QtCore.Slot(str)
+ def on_set_financials_table(self, ticker):
+ """
+ This method get the fondamental from the compagny
+ and fill the table.
+ """
+ analyses = AnalyseFondamental(ticker)
+ self.data = analyses.datas
+
+ # Remove all Cells and Items
+ self.clear()
+ self.setRowCount(0)
+ self.setColumnCount(0)
+
+ self.header = self.data['YEAR']
+ self.header[-1] = "Bilan"
+ self.header.insert(0, 'Valorisation')
+ score = self.data["Score"]
+
+ del self.data["YEAR"]
+ del self.data["Score"]
+
+ self.setColumnCount(len(self.header))
+ self.setHorizontalHeaderLabels(self.header)
+ self.horizontalHeader().resizeSection(0, 150)
+ self.setColumnWidth(len(self.header) - 1, 250)
+ self.setWordWrap(True)
+
+ # Rows
+ for row, (title, donnee) in enumerate(sorted(self.data.items())):
+ self.insertRow(row)
+ cell_val = QtWidgets.QTableWidgetItem()
+ cell_val.setData(QtCore.Qt.DisplayRole, title)
+ self.setItem(row, 0, cell_val)
+ for column, data in enumerate(donnee):
+ cell = QtWidgets.QTableWidgetItem()
+ cell.setData(QtCore.Qt.DisplayRole, str(data))
+ cell.setTextAlignment(QtCore.Qt.AlignCenter)
+ self.setItem(row, column + 1, cell)
+
+ # Add Score to the last Row
+ last_row = len(self.data)
+ self.insertRow(last_row)
+ cell_score_title = QtWidgets.QTableWidgetItem()
+ cell_score_title.setData(QtCore.Qt.DisplayRole, "Score")
+ self.setItem(last_row, 0, cell_score_title)
+ for column, data in enumerate(score):
+ cell_score = QtWidgets.QTableWidgetItem()
+ cell_score.setData(QtCore.Qt.DisplayRole, str(data))
+ cell_score.setTextAlignment(QtCore.Qt.AlignCenter)
+ self.setItem(last_row, column + 1, cell_score)
+
+ for i in range(self.rowCount()):
+ self.setRowHeight(i, 50)
+
+ self.verticalHeader().setVisible(False)
+ self.setShowGrid(False)
+ self.setMouseTracking(True)
+ self.cellEntered.connect(self.cellHover)
+
+ def cellHover(self, row, column):
+ """
+ This method get position (row,column) of cursor.
+ """
+ self.clearSelection()
+ self.current_hover = [0, 0]
+ for i in range(len(self.header)):
+ item = self.item(row, i)
+ item.setSelected(True)
diff --git a/app/libs/graph/graphwidget.py b/app/libs/graph/graphwidget.py
index 7e58082..b1692d6 100644
--- a/app/libs/graph/graphwidget.py
+++ b/app/libs/graph/graphwidget.py
@@ -2,6 +2,7 @@
from PySide2 import QtCore, QtGui, QtWidgets
from libs.graph.candlestick import CandlestickItem
+from libs.events_handler import EventHandler
from utils import utils
# TODO import from palette or Qss
@@ -19,11 +20,16 @@ def __init__(self, parent=None):
self.v_line = None
self.h_line = None
+ self.signals = EventHandler()
+
self.g_quotation = self.addPlot(row=0, col=0, name="Quotation")
self.g_quotation.showGrid(x=True, y=True, alpha=0.3)
self.g_vb = self.g_quotation.vb
self.set_cross_hair()
+ self.g_quotation.scene().sigMouseClicked.connect(
+ self._on_mouse_clicked
+ )
def plot_quotation(self, data, clear=True):
"""Plot the quotation
@@ -51,7 +57,25 @@ def plot_quotation(self, data, clear=True):
item = CandlestickItem(ls_data)
self.g_quotation.addItem(item)
self.g_quotation.enableAutoRange()
+
+ color_line = (38, 166, 154)
+ if data['Close'].iloc[-1] < data['Open'].iloc[-1]:
+ color_line = (239, 83, 80)
+
+ self.h_price = pg.InfiniteLine(
+ pos=data['Close'].iloc[-1],
+ angle=0,
+ movable=False,
+ pen=pg.mkPen(
+ color=color_line,
+ width=1,
+ style=QtCore.Qt.DotLine,
+ ),
+ )
+ self.g_quotation.addItem(self.h_price, ignoreBounds=True)
+
self.set_time_x_axis(widget=self.g_quotation)
+ self._set_y_axis_(widget=self.g_quotation, data_close=data['Close'])
self.set_cross_hair()
def set_cross_hair(self):
@@ -79,6 +103,7 @@ def set_cross_hair(self):
def set_time_x_axis(self, widget):
widget.setAxisItems({"bottom": pg.DateAxisItem(orientation="bottom")})
+ @QtCore.Slot(object)
def _on_mouse_moved(self, event):
"""Signal on mouse moved
@@ -91,6 +116,42 @@ def _on_mouse_moved(self, event):
self.v_line.setPos(mousePoint.x())
self.h_line.setPos(mousePoint.y())
+ def _set_y_axis_(self, widget, data_close):
+ """Set Y Axis in Left and add Price.
+
+ :param widget: GraphWidget
+ :type widget: PQQt.GraphWidget
+ :param data_close: Data Price 'Close'
+ :type data_close: DataFrame
+ """
+ widget.showAxis('right')
+ axis = widget.getAxis('right')
+ axis.setTicks([[(data_close[-1], str(round(data_close[-1], 2)))]])
+
+ @QtCore.Slot(object)
+ def _on_mouse_clicked(self, event):
+ """Called on mouse clicked
+
+ :param event: Mouse clicked
+ :type event: event
+ """
+ items = self.g_quotation.scene().items(event.scenePos())
+ plot_items = [x for x in items if isinstance(x, pg.PlotItem)]
+ self.signals.sig_graph_clicked.emit(plot_items, event)
+
+ def mousePressEvent(self, event):
+ self.signals.sig_graph_mouse_pressed.emit(event)
+ super(GraphView, self).mousePressEvent(event)
+ print(event.pos())
+
+ def mouseReleaseEvent(self, event):
+ self.signals.sig_graph_mouse_released.emit(event)
+ super(GraphView, self).mouseReleaseEvent(event)
+
+ def mouseMoveEvent(self, event):
+ self.signals.sig_graph_mouse_moved.emit(event)
+ super(GraphView, self).mouseMoveEvent(event)
+
class GraphWidget(QtWidgets.QWidget):
"""Widget wrapper for the graph"""
@@ -103,6 +164,22 @@ def __init__(self, parent=None):
layout.addWidget(self._graph)
self.setLayout(layout)
+ self.signals = EventHandler()
+
+ # Relay Signals
+ self._graph.signals.sig_graph_clicked.connect(
+ self.signals.sig_graph_clicked.emit
+ )
+ self._graph.signals.sig_graph_mouse_pressed.connect(
+ self.signals.sig_graph_mouse_pressed.emit
+ )
+ self._graph.signals.sig_graph_mouse_released.connect(
+ self.signals.sig_graph_mouse_released.emit
+ )
+ self._graph.signals.sig_graph_mouse_moved.connect(
+ self.signals.sig_graph_mouse_moved.emit
+ )
+
@property
def graph(self):
"""Return the graph
diff --git a/app/libs/markets_widget.py b/app/libs/markets_widget.py
new file mode 100644
index 0000000..04a5c54
--- /dev/null
+++ b/app/libs/markets_widget.py
@@ -0,0 +1,73 @@
+
+import numpy as np
+from ui import markets_widget
+from PySide2 import QtWidgets
+from libs.yahoo_fin import stock_info
+from libs.widgets.stackedwidget import StackedWidget
+
+TICKERS = {
+ "NASDAQ": "%5EIXIC",
+ "S&P 500": "%5EGSPC",
+ "Dow Jones": "%5EDJI",
+ "Oil": "CL%3DF",
+ "BTC": "BTC-USD",
+ "ETH": "ETH-USD",
+ "EUR/USD": "EURUSD%3DX",
+ "GBP/USD": "GBPUSD%3DX",
+ "Gold": "GC%3DF",
+}
+
+class MarketsWidget(StackedWidget):
+ """
+ Markets are the informations in the Welcome Page showing price and
+ variation from day before.
+ """
+ def __init__(self, parent=None):
+ super(MarketsWidget, self).__init__(parent)
+
+ page = QtWidgets.QWidget()
+ layout = QtWidgets.QHBoxLayout(page)
+
+ for count, (name, tick) in enumerate(TICKERS.items()):
+ if count % 5 == 0:
+ page = QtWidgets.QWidget()
+ layout = QtWidgets.QHBoxLayout(page)
+ item = MarketsWidgetItem(self, ticker=tick, compagny=name)
+ layout.addWidget(item)
+
+ self.addWidget(page)
+
+
+class MarketsWidgetItem(QtWidgets.QWidget, markets_widget.Ui_markets):
+ def __init__(self, parent=None, ticker=None, compagny=None):
+ super(MarketsWidgetItem, self).__init__(parent)
+
+ self.setupUi(self)
+ try:
+ x = stock_info.get_data(ticker)
+ except:
+ return
+
+ day = float(x['adjclose'][-1])
+ prev_day = float(x['adjclose'][-2])
+
+ if np.isnan(prev_day):
+ if not np.isnan(x['adjclose'][-3]):
+ prev_day = float(x['adjclose'][-3])
+ else:
+ prev_day = float(x['adjclose'][-4])
+
+ variation = ((day - prev_day) / prev_day) * 100
+ variation = round(variation, 2)
+
+ self.title.setText(compagny)
+ self.price.setText("{} €".format(str(round(day, 2))))
+ self.pourcentage.setText("{}%".format(str(variation)))
+
+ if variation <= 0:
+ self.pourcentage.setStyleSheet("color:rgb(239, 83, 80);")
+ else:
+ self.pourcentage.setStyleSheet("color:rgb(38, 166, 154);")
+
+
+
diff --git a/app/libs/predictions/predictions_1.py b/app/libs/predictions/predictions_1.py
new file mode 100644
index 0000000..99663cd
--- /dev/null
+++ b/app/libs/predictions/predictions_1.py
@@ -0,0 +1,84 @@
+import pandas as pd
+import numpy as np
+import yfinance as yf
+from pprint import pprint
+import matplotlib.pyplot as plt
+from sklearn.preprocessing import MinMaxScaler
+from tensorflow.keras.models import Sequential
+from tensorflow.keras.layers import Dense, Dropout, LSTM
+
+# https://www.youtube.com/watch?v=PuZY9q-aKLw&ab_channel=NeuralNine
+class MakePrediction(object):
+ def __init__(self, data):
+
+ # pprint(data) # 4105 days
+
+ prediction_days = 60
+ _end = 3009
+
+ # Prepare Data
+ test_data = data['Close'].iloc[:_end].values
+ scaler = MinMaxScaler(feature_range=(0, 1))
+ scaled_data = scaler.fit_transform(test_data.reshape(-1, 1))
+
+ x_train = []
+ y_train = []
+
+ for x in range(prediction_days, len(scaled_data)):
+ x_train.append(scaled_data[x - prediction_days:x, 0])
+ y_train.append(scaled_data[x, 0])
+
+ x_train, y_train = np.array(x_train), np.array(y_train)
+ x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))
+
+ # Build the Model
+ model = Sequential()
+
+ model.add(LSTM(units=50, return_sequences=True, input_shape=(x_train.shape[1], 1)))
+ model.add(Dropout(0.2))
+ model.add(LSTM(units=50, return_sequences=True))
+ model.add(Dropout(0.2))
+ model.add(LSTM(units=50))
+ model.add(Dropout(0.2))
+ model.add(Dense(units=1)) # Prediction of the next price Closing
+
+ model.compile(optimizer="adam", loss='mean_squared_error')
+ model.fit(x_train, y_train, epochs=25, batch_size=32)
+
+ """ Test Model Accurency on Existing Data """
+
+ # Load Test Data
+ test_data = data['Close'].iloc[_end:]
+
+ total_dataset = pd.concat((test_data, test_data), axis=0)
+
+ model_inputs = total_dataset[len(total_dataset) - len(test_data) - prediction_days:].values
+ model_inputs = model_inputs.reshape(-1, 1)
+ model_inputs = scaler.transform(model_inputs)
+
+ # Make Predictions on Test Data
+
+ x_test = []
+
+ for x in range(prediction_days, len(model_inputs)):
+ x_test.append(model_inputs[x-prediction_days:x, 0])
+
+ x_test = np.array(x_test)
+ x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1))
+
+ predicted_prices = model.predict(x_test)
+ predicted_prices = scaler.inverse_transform(predicted_prices)
+
+ # Plot Predictions
+ plt.plot(data['Close'].values, color='black', label='Price')
+ plt.plot(predicted_prices, color='green', label="Predict")
+ plt.xlabel('Time')
+ plt.ylabel('Price')
+ plt.legend()
+ plt.show()
+
+if __name__ == '__main__':
+ tick = "GLE.PA"
+ ticker = yf.Ticker(tick)
+ data = ticker.history(period="1y", interval="1d", start="2008-01-01")
+ pre = MakePrediction(data=data)
diff --git a/app/libs/predictions/predictions_2.py b/app/libs/predictions/predictions_2.py
new file mode 100644
index 0000000..605f233
--- /dev/null
+++ b/app/libs/predictions/predictions_2.py
@@ -0,0 +1,90 @@
+import pandas as pd
+import numpy as np
+import yfinance as yf
+from pprint import pprint
+import matplotlib.pyplot as plt
+from sklearn.preprocessing import MinMaxScaler
+from tensorflow.keras.models import Sequential
+from tensorflow.keras.layers import Dense, Dropout, LSTM
+from keras.callbacks import ModelCheckpoint
+
+# https://www.youtube.com/watch?v=PuZy9q-aKLw&ab_channel=NeuralNine
+class MakePrediction(object):
+ def __init__(self, data):
+
+ # pprint(data) # 4105 days
+ prices = data['Close'].values
+
+ print(data.shape)
+
+ prediction_days = 60
+
+ # Prepare Data
+
+ scaler = MinMaxScaler(feature_range=(0, 1))
+ scaled_data = scaler.fit_transform(prices.reshape(-1, 1))
+
+ # Split 80-20
+ train_size = int(len(scaled_data) * 0.80)
+ test_size = int(len(scaled_data) - train_size)
+ train = prices[0:train_size]
+ test = scaled_data[train_size:len(scaled_data)]
+
+ # Method for create features from the time series data
+ def create_features(data, window_size):
+ x, y = [], []
+ for i in range(len(data) - window_size - 1):
+ window = data[i:(i + window_size), 0]
+ x.append(window)
+ y.append(data[i + window_size, 0])
+ return np.array(x), np.array(y)
+
+ # Roughly one month of trading assuming 5 trading days per week
+ window_size = 20
+ x_train, y_train = create_features(train, window_size)
+
+ x_test, y_test = create_features(test, window_size)
+
+ # Reshape to the format of [samples, time steps, features]
+ x_train = np.reshape(x_train, (x_train.shape[0], 1, x_train.shape[1]))
+
+ x_test = np.reshape(x_test, (x_test.shape[0], 1, x_test.shape[1]))
+
+ scaled_data_shape = scaled_data.shape
+ train_shape = train.shape
+ test_shape = test.shape
+
+ # Make sure that the number of rows in the dataset = train rows + test rows
+ def is_leak(T_shape, train_shape, test_shape):
+ return not (T_shape[0] == (train_shape[0] + test_shape[0]))
+
+ print(is_leak(scaled_data_shape, train_shape, test_shape))
+
+ # Building model
+ model = Sequential()
+
+ model.add(LSTM(units=50, activation='relu', # return_sequences = True,
+ input_shape=(x_train.shape[1], window_size)))
+ model.add(Dropout(0.2))
+
+ # Output layer
+ model.add(Dense(1))
+ model.compile(loss='mean_squared_error', optimizer='adam')
+ model.fit(x_train, y_train, epochs=25, batch_size=32)
+
+ predicted_prices = model.predict(x_test)
+ predicted_prices = scaler.inverse_transform(predicted_prices)
+
+ # Plot Predictions
+ plt.plot(data['Close'].values, color='black', label='Price')
+ plt.plot(predicted_prices, color='green', label="Predict")
+ plt.xlabel('Time')
+ plt.ylabel('Price')
+ plt.legend()
+ plt.show()
+
+if __name__ == '__main__':
+ tick = "GLE.PA"
+ ticker = yf.Ticker(tick)
+ data = ticker.history(period="1y", interval="1d", start="2008-01-01")
+ pre = MakePrediction(data=data)
diff --git a/app/libs/roi_manager.py b/app/libs/roi_manager.py
new file mode 100644
index 0000000..f4456ad
--- /dev/null
+++ b/app/libs/roi_manager.py
@@ -0,0 +1,102 @@
+import time
+import pyqtgraph as pg
+from PySide2 import QtCore, QtWidgets
+
+
+class ROIManager(QtCore.QObject):
+ def __init__(self, parent=None):
+ super(ROIManager, self).__init__(parent)
+
+ # Constants
+ self.current_tool = None
+ self.current_handle = None
+ self.current_graph = None
+ self._graph = self.parent().wgt_graph.graph
+
+ # Signals
+ self.parent().wgt_graph.signals.sig_graph_clicked.connect(
+ self._on_roi_add_requested
+ )
+ self.parent().wgt_graph.signals.sig_graph_mouse_moved.connect(self._on_mouse_moved)
+ # self.parent().wgt_graph.signals.sig_graph_mouse_pressed.connect(self._on_mouse_pressed)
+ # self.parent().wgt_graph.signals.sig_graph_mouse_released.connect(self._on_mouse_released)
+
+ def drawer(self, graph, event):
+ """Drawer over the graph
+
+ :param graph: The graph on whch to draw
+ :type graph: pg.PlotItem
+ :param event: The event
+ :type event: object
+ """
+ self.current_graph = graph
+ vb = graph.vb
+ mouse_point = vb.mapSceneToView(event.pos())
+ if self.current_tool:
+ self.current_tool(initial_pos=mouse_point) # Exec the current tool
+
+ def bounded_line_drawer(self, initial_pos, **kwargs):
+ """Draw a bounded line
+ """
+ print('la')
+ roi = pg.LineSegmentROI((initial_pos, initial_pos), removable=True)
+ self.current_handle = roi.getHandles()[-1]
+ self.current_graph.addItem(roi)
+ roi.sigRemoveRequested.connect(self._on_roi_remove_requested)
+
+ def set_tool(self, **kwargs):
+ """Set the current tool. kwargs may have a tool which corresponds to
+ the method to call inside this module.
+
+ :return: The current tool
+ :rtype: method if found, None instead
+ """
+ self.current_tool = getattr(self, kwargs.get("tool", None), None)
+ return self.current_tool
+
+ def unset_tool(self):
+ """Unset the current tool"""
+ self.current_tool = None
+ self.current_handle = None
+ self.current_graph = None
+
+ def remove_roi(self, roi):
+ """Remove the given roi
+
+ :param roi: The roi to remove
+ :type roi: pg.ROI
+ """
+ print(roi)
+
+ @QtCore.Slot(object)
+ def _on_mouse_moved(self, event):
+ if self.current_handle:
+ vb = self.current_graph.vb
+ mousePoint = vb.mapSceneToView(event.pos())
+ self.current_handle.setPos(mousePoint)
+
+ @QtCore.Slot(object)
+ def _on_mouse_released(self, event):
+ print("Released", event)
+
+ @QtCore.Slot(object)
+ def _on_mouse_pressed(self, event):
+ print("Pressed", event)
+
+ @QtCore.Slot(list, object)
+ def _on_roi_add_requested(self, objects, event):
+ """Called on a draw requested"""
+ if not self.current_handle:
+ if objects:
+ self.drawer(graph=objects[0], event=event)
+ else:
+ self.unset_tool()
+
+ @QtCore.Slot(object)
+ def _on_roi_remove_requested(self, roi):
+ """Called on a request for ROI deletion
+
+ :param roi: The roi to remove
+ :type roi: pg.ROI
+ """
+ self.remove_roi(roi=roi)
diff --git a/app/libs/splashcreen.py b/app/libs/splashcreen.py
new file mode 100644
index 0000000..2e01c15
--- /dev/null
+++ b/app/libs/splashcreen.py
@@ -0,0 +1,50 @@
+import os
+
+from PySide2 import QtCore, QtGui, QtWidgets
+from ui.splashscreen import Ui_splashscreen
+
+
+class SplashScreen(QtWidgets.QSplashScreen, Ui_splashscreen):
+ """This class is a subclass of a QtWidgets.QSplashScreen
+
+ :param QtWidgets: QtWidgets.QSplashScreen object
+ :type QtWidgets: obj
+ """
+ def __init__(self, path, title):
+
+ pixmap = QtGui.QPixmap(path).scaled(900, 600,
+ QtCore.Qt.KeepAspectRatio)
+
+ super(SplashScreen, self).__init__(pixmap, QtCore.Qt.WindowStaysOnTopHint)
+
+ self.setupUi(self)
+
+ self.lab_app_name.setText(title)
+ self.splash_color = QtGui.QColor(100, 100, 100)
+ self.setWindowFlags(
+ QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint
+ )
+ self.center()
+
+ blur = QtWidgets.QGraphicsDropShadowEffect(self)
+ blur.setBlurRadius(10)
+ blur.setColor(QtGui.QColor(0, 0, 0))
+ self.setGraphicsEffect(blur)
+
+ def show_message(self, message=None):
+ """Thi function shows the message in the splashscreen
+
+ :param message: The message to show, defaults to None
+ :type message: str, optional
+ """
+ self.showMessage(
+ message,
+ QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter,
+ self.splash_color,
+ )
+
+ def center(self):
+ qRect = self.frameGeometry()
+ centerPoint = QtWidgets.QDesktopWidget().availableGeometry().center()
+ qRect.moveCenter(centerPoint)
+ self.move(qRect.topLeft())
\ No newline at end of file
diff --git a/app/libs/tradingview_ta/__init__.py b/app/libs/tradingview_ta/__init__.py
new file mode 100644
index 0000000..f90cfeb
--- /dev/null
+++ b/app/libs/tradingview_ta/__init__.py
@@ -0,0 +1,2 @@
+from .main import TA_Handler, TradingView, Analysis, Interval, Exchange, __version__
+from .technicals import Recommendation, Compute
diff --git a/app/libs/tradingview_ta/main.py b/app/libs/tradingview_ta/main.py
new file mode 100644
index 0000000..fec22dd
--- /dev/null
+++ b/app/libs/tradingview_ta/main.py
@@ -0,0 +1,338 @@
+# Tradingview Technical Analysis (tradingview-ta)
+# Author: deathlyface (https://github.com/deathlyface)
+# License: MIT
+
+import re
+import requests, json, datetime, warnings
+from pprint import pprint
+from .technicals import Compute
+
+__version__ = "3.2.3"
+
+
+class Analysis(object):
+ exchange = ""
+ symbol = ""
+ screener = ""
+ time = ""
+ interval = ""
+ summary = {}
+ oscillators = {}
+ moving_averages = {}
+ indicators = {}
+
+
+class Interval:
+ INTERVAL_1_MINUTE = "1m"
+ INTERVAL_5_MINUTES = "5m"
+ INTERVAL_15_MINUTES = "15m"
+ INTERVAL_1_HOUR = "1h"
+ INTERVAL_4_HOURS = "4h"
+ INTERVAL_1_DAY = "1d"
+ INTERVAL_1_WEEK = "1W"
+ INTERVAL_1_MONTH = "1M"
+
+
+class Exchange:
+ FOREX = "FX_IDC"
+ CFD = "TVC"
+
+
+class TradingView:
+ scan_url = "https://scanner.tradingview.com/"
+ indicators = ["Recommend.Other{}", "Recommend.All{}", "Recommend.MA{}", "RSI{}", "RSI[1]{}", "Stoch.K{}",
+ "Stoch.D{}", "Stoch.K[1]{}", "Stoch.D[1]{}", "CCI20{}", "CCI20[1]{}", "ADX{}", "ADX+DI{}", "ADX-DI{}",
+ "ADX+DI[1]{}", "ADX-DI[1]{}", "AO{}", "AO[1]{}", "Mom{}", "Mom[1]{}", "MACD.macd{}", "MACD.signal{}",
+ "Rec.Stoch.RSI{}", "Stoch.RSI.K{}", "Rec.WR{}", "W.R{}", "Rec.BBPower{}", "BBPower{}", "Rec.UO{}",
+ "UO{}", "close{}", "EMA5{}", "SMA5{}", "EMA10{}", "SMA10{}", "EMA20{}", "SMA20{}", "EMA30{}",
+ "SMA30{}", "EMA50{}", "SMA50{}", "EMA100{}", "SMA100{}", "EMA200{}", "SMA200{}", "Rec.Ichimoku{}",
+ "Ichimoku.BLine{}", "Rec.VWMA{}", "VWMA{}", "Rec.HullMA9{}", "HullMA9{}", "Pivot.M.Classic.S3{}",
+ "Pivot.M.Classic.S2{}", "Pivot.M.Classic.S1{}", "Pivot.M.Classic.Middle{}", "Pivot.M.Classic.R1{}",
+ "Pivot.M.Classic.R2{}", "Pivot.M.Classic.R3{}", "Pivot.M.Fibonacci.S3{}", "Pivot.M.Fibonacci.S2{}",
+ "Pivot.M.Fibonacci.S1{}", "Pivot.M.Fibonacci.Middle{}", "Pivot.M.Fibonacci.R1{}",
+ "Pivot.M.Fibonacci.R2{}", "Pivot.M.Fibonacci.R3{}", "Pivot.M.Camarilla.S3{}",
+ "Pivot.M.Camarilla.S2{}", "Pivot.M.Camarilla.S1{}", "Pivot.M.Camarilla.Middle{}",
+ "Pivot.M.Camarilla.R1{}", "Pivot.M.Camarilla.R2{}", "Pivot.M.Camarilla.R3{}", "Pivot.M.Woodie.S3{}",
+ "Pivot.M.Woodie.S2{}", "Pivot.M.Woodie.S1{}", "Pivot.M.Woodie.Middle{}", "Pivot.M.Woodie.R1{}",
+ "Pivot.M.Woodie.R2{}", "Pivot.M.Woodie.R3{}", "Pivot.M.Demark.S1{}", "Pivot.M.Demark.Middle{}",
+ "Pivot.M.Demark.R1{}", "open{}", "P.SAR{}", "BB.lower{}", "BB.upper{}"]
+
+ def data(symbol, interval):
+ """Format TradingView's Scanner Post Data
+
+ Args:
+ symbol (string): EXCHANGE:SYMBOL (ex: NASDAQ:AAPL or BINANCE:BTCUSDT)
+ interval (string): Time Interval (ex: 1m, 5m, 15m, 1h, 4h, 1d, 1W, 1M)
+
+ Returns:
+ string: JSON object as a string.
+ """
+ if interval == "1m":
+ # 1 Minute
+ data_interval = "|1"
+ elif interval == "5m":
+ # 5 Minutes
+ data_interval = "|5"
+ elif interval == "15m":
+ # 15 Minutes
+ data_interval = "|15"
+ elif interval == "1h":
+ # 1 Hour
+ data_interval = "|60"
+ elif interval == "4h":
+ # 4 Hour
+ data_interval = "|240"
+ elif interval == "1W":
+ # 1 Week
+ data_interval = "|1W"
+ elif interval == "1M":
+ # 1 Month
+ data_interval = "|1M"
+ else:
+ if interval != '1d':
+ warnings.warn("Interval is empty or not valid, defaulting to 1 day.")
+ # Default, 1 Day
+ data_interval = ""
+
+ data_json = {"symbols": {"tickers": [symbol.upper()], "query": {"types": []}},
+ "columns": [x.format(data_interval) for x in TradingView.indicators]}
+
+ return data_json
+
+
+class TA_Handler(object):
+ screener = ""
+ exchange = ""
+ symbol = ""
+ interval = ""
+ timeout = None
+
+ def __init__(self, screener="", exchange="", symbol="", interval="", timeout=None):
+ """Create an instance of TA_Handler class
+
+ Args:
+ screener (str, required): Screener (see documentation and tradingview's site).
+ exchange (str, required): Exchange (see documentation and tradingview's site).
+ symbol (str, required): Abbreviation of a stock or currency (see documentation and tradingview's site).
+ interval (str, optional): See the interval class and the documentation. Defaults to 1 day.
+ timeout (float, optional): Timeout for requests (in seconds). Defaults to None.
+ """
+ self.screener = screener
+ self.exchange = exchange
+ self.symbol = symbol
+ self.interval = interval
+ self.timeout = timeout
+
+ def get_screener_from_symabol(self, symbol):
+ countries = ['france', 'america']
+ for country in countries:
+ try:
+ scan_url = TradingView.scan_url + country.lower() + "/scan"
+ headers = {"User-Agent": "tradingview_ta/{}".format(__version__)}
+ response = requests.post(scan_url, headers=headers)
+ result = json.loads(response.text)["data"]
+ for ticks in result:
+ ticker = ticks['s']
+
+ if ticker.split(':')[1].startswith(symbol):
+ if ticker.split(':')[1].endswith(symbol):
+ return country, ticker.split(':')[0]
+ except:
+ raise Exception("Exchange not found for this symbol.")
+
+ # Set functions
+ def set_screener_as_stock(self, country):
+ """Set the screener as a country (for stocks).
+
+ Args:
+ country (string): Stock's country (ex: If NFLX or AAPL, then "america" is the screener)
+ """
+ self.screener = country
+
+ def set_screener_as_crypto(self):
+ """Set the screener as crypto (for cryptocurrencies).
+ """
+ self.screener = "crypto"
+
+ def set_screener_as_cfd(self):
+ """Set the screener as cfd (contract for differences).
+ """
+ self.screener = "cfd"
+
+ def set_screener_as_forex(self):
+ """Set the screener as forex.
+ """
+ self.screener = "forex"
+
+ def set_exchange_as_crypto_or_stock(self, exchange):
+ """Set the exchange
+
+ Args:
+ exchange (string): Stock/Crypto's exchange (NASDAQ, NYSE, BINANCE, BITTREX, etc).
+ """
+ self.exchange = exchange
+
+ def set_exchange_as_forex(self):
+ """Set the exchange as FX_IDC for forex.
+ """
+ self.exchange = "FX_IDC"
+
+ def set_exchange_as_cfd(self):
+ """Set the exchange as TVC for cfd.
+ """
+ self.exchange = "TVC"
+
+ def set_interval_as(self, intvl):
+ """Set the interval.
+
+ Refer to: https://python-tradingview-ta.readthedocs.io/en/latest/usage.html#setting-the-interval
+
+ Args:
+ intvl (string): interval. You can use values from the Interval class.
+
+ """
+ self.interval = intvl
+
+ def set_symbol_as(self, symbol):
+ """Set the symbol.
+
+ Refer to: https://python-tradingview-ta.readthedocs.io/en/latest/usage.html#setting-the-symbol
+
+ Args:
+ symbol (string): abbreviation of a stock or currency (ex: NFLX, AAPL, BTCUSD).
+ """
+ self.symbol = symbol
+
+ # Get analysis
+ def get_analysis(self):
+ """Get analysis from TradingView and compute it.
+
+ Returns:
+ Analysis: Contains information about the analysis.
+ """
+ if self.screener == "" or type(self.screener) != str:
+ raise Exception("Screener is empty or not valid.")
+ elif self.exchange == "" or type(self.exchange) != str:
+ raise Exception("Exchange is empty or not valid.")
+ elif self.symbol == "" or type(self.symbol) != str:
+ raise Exception("Symbol is empty or not valid.")
+
+ exch_smbl = self.exchange.upper() + ":" + self.symbol.upper()
+ data = TradingView.data(exch_smbl, self.interval)
+ scan_url = TradingView.scan_url + self.screener.lower() + "/scan"
+ headers = {"User-Agent": "tradingview_ta/{}".format(__version__)}
+ # https://scanner.tradingview.com/france/scan
+ response = requests.post(scan_url, json=data, headers=headers, timeout=self.timeout)
+
+ # Return False if can't get data
+ if response.status_code != 200:
+ raise Exception("Can't access TradingView's API. HTTP status code: {}.".format(response.status_code))
+
+ result = json.loads(response.text)["data"]
+ if result != []:
+ indicator_values = result[0]["d"]
+ else:
+ raise Exception("Exchange or symbol not found.")
+
+ oscillators_counter, ma_counter = {"BUY": 0, "SELL": 0, "NEUTRAL": 0}, {"BUY": 0, "SELL": 0, "NEUTRAL": 0}
+ computed_oscillators, computed_ma = {}, {}
+
+ # RECOMMENDATIONS
+ recommend_oscillators = Compute.Recommend(indicator_values[0])
+ recommend_summary = Compute.Recommend(indicator_values[1])
+ recommend_moving_averages = Compute.Recommend(indicator_values[2])
+
+ # OSCILLATORS
+ # RSI (14)
+ if None not in indicator_values[3:5]:
+ computed_oscillators["RSI"] = Compute.RSI(indicator_values[3], indicator_values[4])
+ oscillators_counter[computed_oscillators["RSI"]] += 1
+ # Stoch %K
+ if None not in indicator_values[5:9]:
+ computed_oscillators["STOCH.K"] = Compute.Stoch(indicator_values[5], indicator_values[6],
+ indicator_values[7], indicator_values[8])
+ oscillators_counter[computed_oscillators["STOCH.K"]] += 1
+ # CCI (20)
+ if None not in indicator_values[9:11]:
+ computed_oscillators["CCI"] = Compute.CCI20(indicator_values[9], indicator_values[10])
+ oscillators_counter[computed_oscillators["CCI"]] += 1
+ # ADX (14)
+ if None not in indicator_values[11:16]:
+ computed_oscillators["ADX"] = Compute.ADX(indicator_values[11], indicator_values[12], indicator_values[13],
+ indicator_values[14], indicator_values[15])
+ oscillators_counter[computed_oscillators["ADX"]] += 1
+ # AO
+ if None not in indicator_values[16:18]:
+ computed_oscillators["AO"] = Compute.AO(indicator_values[16], indicator_values[17])
+ oscillators_counter[computed_oscillators["AO"]] += 1
+ # Mom (10)
+ if None not in indicator_values[18:20]:
+ computed_oscillators["Mom"] = Compute.Mom(indicator_values[18], indicator_values[19])
+ oscillators_counter[computed_oscillators["Mom"]] += 1
+ # MACD
+ if None not in indicator_values[20:22]:
+ computed_oscillators["MACD"] = Compute.MACD(indicator_values[20], indicator_values[21])
+ oscillators_counter[computed_oscillators["MACD"]] += 1
+ # Stoch RSI
+ if indicator_values[22] != None:
+ computed_oscillators["Stoch.RSI"] = Compute.Simple(indicator_values[22])
+ oscillators_counter[computed_oscillators["Stoch.RSI"]] += 1
+ # W%R
+ if indicator_values[24] != None:
+ computed_oscillators["W%R"] = Compute.Simple(indicator_values[24])
+ oscillators_counter[computed_oscillators["W%R"]] += 1
+ # BBP
+ if indicator_values[26] != None:
+ computed_oscillators["BBP"] = Compute.Simple(indicator_values[26])
+ oscillators_counter[computed_oscillators["BBP"]] += 1
+ # UO
+ if indicator_values[28] != None:
+ computed_oscillators["UO"] = Compute.Simple(indicator_values[28])
+ oscillators_counter[computed_oscillators["UO"]] += 1
+
+ # MOVING AVERAGES
+ ma_list = ["EMA10", "SMA10", "EMA20", "SMA20", "EMA30", "SMA30", "EMA50", "SMA50", "EMA100", "SMA100", "EMA200",
+ "SMA200"]
+ close = indicator_values[30]
+ ma_list_counter = 0
+ for index in range(33, 45):
+ if indicator_values[index] != None:
+ computed_ma[ma_list[ma_list_counter]] = Compute.MA(indicator_values[index], close)
+ ma_counter[computed_ma[ma_list[ma_list_counter]]] += 1
+ ma_list_counter += 1
+
+ # MOVING AVERAGES, pt 2
+ # ICHIMOKU
+ if indicator_values[45] != None:
+ computed_ma["Ichimoku"] = Compute.Simple(indicator_values[45])
+ ma_counter[computed_ma["Ichimoku"]] += 1
+ # VWMA
+ if indicator_values[47] != None:
+ computed_ma["VWMA"] = Compute.Simple(indicator_values[47])
+ ma_counter[computed_ma["VWMA"]] += 1
+ # HullMA (9)
+ if indicator_values[49] != None:
+ computed_ma["HullMA"] = Compute.Simple(indicator_values[49])
+ ma_counter[computed_ma["HullMA"]] += 1
+
+ analysis = Analysis()
+ analysis.screener = self.screener
+ analysis.exchange = self.exchange
+ analysis.symbol = self.symbol
+ analysis.interval = self.interval
+ analysis.time = datetime.datetime.now()
+
+ for x in range(len(indicator_values)):
+ analysis.indicators[TradingView.indicators[x].format("")] = indicator_values[x]
+
+ analysis.oscillators = {"RECOMMENDATION": recommend_oscillators, "BUY": oscillators_counter["BUY"],
+ "SELL": oscillators_counter["SELL"], "NEUTRAL": oscillators_counter["NEUTRAL"],
+ "COMPUTE": computed_oscillators}
+ analysis.moving_averages = {"RECOMMENDATION": recommend_moving_averages, "BUY": ma_counter["BUY"],
+ "SELL": ma_counter["SELL"], "NEUTRAL": ma_counter["NEUTRAL"],
+ "COMPUTE": computed_ma}
+ analysis.summary = {"RECOMMENDATION": recommend_summary, "BUY": oscillators_counter["BUY"] + ma_counter["BUY"],
+ "SELL": oscillators_counter["SELL"] + ma_counter["SELL"],
+ "NEUTRAL": oscillators_counter["NEUTRAL"] + ma_counter["NEUTRAL"]}
+
+ return analysis
diff --git a/app/libs/tradingview_ta/technicals.py b/app/libs/tradingview_ta/technicals.py
new file mode 100644
index 0000000..e43bb42
--- /dev/null
+++ b/app/libs/tradingview_ta/technicals.py
@@ -0,0 +1,239 @@
+# Tradingview Technical Analysis (tradingview-ta)
+# Author: deathlyface (https://github.com/deathlyface)
+# Rewritten from https://www.tradingview.com/static/bundles/technicals.f2e6e6a51aebb6cd46f8.js
+# License: MIT
+
+class Recommendation:
+ buy = "BUY"
+ strong_buy = "STRONG_BUY"
+ sell = "SELL"
+ strong_sell = "STRONG_SELL"
+ neutral = "NEUTRAL"
+ error = "ERROR"
+
+class Compute:
+ def MA(ma, close):
+ """Compute Moving Average
+
+ Args:
+ ma (float): MA value
+ close (float): Close value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (ma < close):
+ return Recommendation.buy
+ elif (ma > close):
+ return Recommendation.sell
+ else:
+ return Recommendation.neutral
+
+ def RSI(rsi, rsi1):
+ """Compute Relative Strength Index
+
+ Args:
+ rsi (float): RSI value
+ rsi1 (float): RSI[1] value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (rsi < 30 and rsi1 > rsi):
+ return Recommendation.buy
+ elif (rsi > 70 and rsi1 < rsi):
+ return Recommendation.sell
+ else:
+ return Recommendation.neutral
+
+ def Stoch(k, d, k1, d1):
+ """Compute Stochastic
+
+ Args:
+ k (float): Stoch.K value
+ d (float): Stoch.D value
+ k1 (float): Stoch.K[1] value
+ d1 (float): Stoch.D[1] value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (k < 20 and d < 20 and k > d and k1 < d1):
+ return Recommendation.buy
+ elif (k > 80 and d > 80 and k < d and k1 > d1):
+ return Recommendation.sell
+ else:
+ return Recommendation.neutral
+
+ def CCI20(cci20, cci201):
+ """Compute Commodity Channel Index 20
+
+ Args:
+ cci20 (float): CCI20 value
+ cci201 ([type]): CCI20[1] value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (cci20 < -100 and cci20 > cci201):
+ return Recommendation.buy
+ elif (cci20 > 100 and cci20 < cci201):
+ return Recommendation.sell
+ else:
+ return Recommendation.neutral
+
+ def ADX(adx, adxpdi, adxndi, adxpdi1, adxndi1):
+ """Compute Average Directional Index
+
+ Args:
+ adx (float): ADX value
+ adxpdi (float): ADX+DI value
+ adxndi (float): ADX-DI value
+ adxpdi1 (float): ADX+DI[1] value
+ adxndi1 (float): ADX-DI[1] value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (adx > 20 and adxpdi1 < adxndi1 and adxpdi > adxndi):
+ return Recommendation.buy
+ elif (adx > 20 and adxpdi1 > adxndi1 and adxpdi < adxndi):
+ return Recommendation.sell
+ else:
+ return Recommendation.neutral
+
+ def AO(ao, ao1):
+ """Compute Awesome Oscillator
+
+ Args:
+ ao (float): AO value
+ ao1 (float): AO[1] value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (ao > 0 and ao1 < 0 or ao > 0 and ao1 > 0 and ao > ao1):
+ return Recommendation.buy
+ elif (ao < 0 and ao1 > 0 or ao < 0 and ao1 < 0 and ao < ao1):
+ return Recommendation.sell
+ else:
+ return Recommendation.neutral
+
+ def Mom(mom, mom1):
+ """Compute Momentum
+
+ Args:
+ mom (float): Mom value
+ mom1 (float): Mom[1] value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (mom < mom1):
+ return Recommendation.sell
+ elif (mom > mom1):
+ return Recommendation.buy
+ else:
+ return Recommendation.neutral
+
+ def MACD(macd, signal):
+ """Compute Moving Average Convergence/Divergence
+
+ Args:
+ macd (float): MACD.macd value
+ signal (float): MACD.signal value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (macd > signal):
+ return Recommendation.buy
+ elif (macd < signal):
+ return Recommendation.sell
+ else:
+ return Recommendation.neutral
+
+ def BBBuy(close, bblower):
+ """Compute Bull Bear Buy
+
+ Args:
+ close (float): close value
+ bblower (float): BB.lower value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (close < bblower):
+ return Recommendation.buy
+ else:
+ return Recommendation.neutral
+
+ def BBSell(close, bbupper):
+ """Compute Bull Bear Sell
+
+ Args:
+ close (float): close value
+ bbupper (float): BB.upper value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (close > bbupper):
+ return Recommendation.sell
+ else:
+ return Recommendation.neutral
+
+ def PSAR(psar, open):
+ """Compute Parabolic Stop-And-Reverse
+
+ Args:
+ psar (float): P.SAR value
+ open (float): open value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (psar < open):
+ return Recommendation.buy
+ elif (psar > open):
+ return Recommendation.sell
+ else:
+ return Recommendation.neutral
+
+ def Recommend(value):
+ """Compute Recommend
+
+ Args:
+ value (float): recommend value
+
+ Returns:
+ string: "STRONG_BUY", "BUY", "NEUTRAL", "SELL", "STRONG_SELL", or "ERROR"
+ """
+ if (value >= -1 and value < -.5):
+ return Recommendation.strong_sell
+ elif (value >= -.5 and value < 0):
+ return Recommendation.sell
+ elif (value == 0):
+ return Recommendation.neutral
+ elif (value > 0 and value <= .5):
+ return Recommendation.buy
+ elif (value > .5 and value <= 1):
+ return Recommendation.strong_buy
+ else:
+ return Recommendation.error
+
+ def Simple(value):
+ """Compute Simple
+
+ Args:
+ value (float): Rec.X value
+
+ Returns:
+ string: "BUY", "SELL", or "NEUTRAL"
+ """
+ if (value == -1):
+ return Recommendation.sell
+ elif (value == 1):
+ return Recommendation.buy
+ else:
+ return Recommendation.neutral
diff --git a/app/libs/welcome_widget.py b/app/libs/welcome_widget.py
new file mode 100644
index 0000000..18bfb9f
--- /dev/null
+++ b/app/libs/welcome_widget.py
@@ -0,0 +1,36 @@
+#
+# This class set the default Welcome Page.
+#
+from pprint import pprint
+from PySide2 import QtWidgets
+from libs.articles.yahoo_articles import ArticlesYahoo
+from libs.widgets.article_itemwidget import ArticlesWidgetItem
+
+
+class WelcomeWidget(QtWidgets.QListWidget):
+ """
+ This is loading all articles from the main Yahoo Page.
+ https://finance.yahoo.com/
+ """
+ def __init__(self, parent=None):
+ super(WelcomeWidget, self).__init__(parent)
+
+ # articles = self._get_articles_dict()[:5]
+ #
+ # for index, i in enumerate(articles):
+ # article = ArticlesWidgetItem(parent=self)
+ # article.set_title(i["title"], i["link"])
+ # article.set_date(i["published"])
+ # article.set_description(i["summary"])
+ # article.set_thumbnail(i["img"])
+ # item = QtWidgets.QListWidgetItem()
+ # item.setSizeHint(article.sizeHint())
+ # self.addItem(item)
+ # self.setItemWidget(item, article)
+
+ def _get_articles_dict(self):
+ article = ArticlesYahoo()
+ return article.get_home_articles()
+
+
+
diff --git a/app/libs/widgets/article_itemwidget.py b/app/libs/widgets/article_itemwidget.py
new file mode 100644
index 0000000..f706f70
--- /dev/null
+++ b/app/libs/widgets/article_itemwidget.py
@@ -0,0 +1,71 @@
+
+import os
+from utils import utils
+from ui.article import Ui_Article
+from libs.thread_pool import ThreadPool
+from PySide2 import QtWidgets, QtCore, QtGui
+
+class ArticlesWidgetItem(QtWidgets.QWidget, Ui_Article):
+ def __init__(self, parent=None):
+ super(ArticlesWidgetItem, self).__init__(parent)
+ self.setupUi(self)
+ self.thread_pool = ThreadPool()
+ self.thread_pool.signals.sig_thread_result.connect(
+ self._on_thumbnail_available
+ )
+
+ def set_compagny(self, text):
+ """
+ Set Compagny name on the article.
+ """
+ self.lb_compagny.setText(text)
+ self.lb_compagny.setFont(QtGui.QFont("Times", 12))
+
+ def set_title(self, text, link):
+ """
+ Set Title of the article and link clickable.
+ """
+ self.lb_title.setText(text)
+ self.lb_title.set_link(link)
+ self.lb_title.set_font_size(size=14)
+
+ def set_date(self, text):
+ """
+ Set date published article.
+ """
+ self.lb_date.setText(text)
+
+ def set_description(self, text):
+ """
+ Set description of the article.
+ """
+ self.desc.setText(text)
+ self.desc.setFont(QtGui.QFont("Times", 10))
+
+ def set_thumbnail(self, link):
+ if link:
+ self.thread_pool.execution(
+ function=utils.get_image_from_url, url=link
+ )
+ else:
+ path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
+ image = os.path.join(path, 'resources', 'img', "no_file.png")
+ image = QtGui.QPixmap(image)
+ # image = QtGui.QPixmap(":/img/no_file.png")
+ self._on_thumbnail_available(image)
+
+
+ @QtCore.Slot(object)
+ def _on_thumbnail_available(self, image):
+ """Called when a thumbnail is available for the company
+
+ :param image: The thumbnail
+ :type image: QPixmap
+ """
+ image = image.scaled(self.size(),
+ QtCore.Qt.KeepAspectRatio,
+ QtCore.Qt.SmoothTransformation
+ )
+ self.thumbnail.setPixmap(image)
+
+
diff --git a/app/libs/widgets/label.py b/app/libs/widgets/label.py
new file mode 100644
index 0000000..5f7e97d
--- /dev/null
+++ b/app/libs/widgets/label.py
@@ -0,0 +1,18 @@
+import webbrowser
+from PySide2 import QtWidgets, QtGui
+
+class LabelTitle(QtWidgets.QLabel):
+ def __init__(self, link=None, size=20):
+ super(LabelTitle, self).__init__()
+
+ self.font = "Times"
+ self.set_font_size(size)
+
+ def set_font_size(self, size):
+ self.setFont(QtGui.QFont(self.font, size))
+
+ def set_link(self, link):
+ self.link = link
+
+ def mousePressEvent(self, event) -> None:
+ webbrowser.open(self.link)
diff --git a/app/libs/widgets/sentimentals_widget.py b/app/libs/widgets/sentimentals_widget.py
new file mode 100644
index 0000000..0c8c6d7
--- /dev/null
+++ b/app/libs/widgets/sentimentals_widget.py
@@ -0,0 +1,84 @@
+from utils import utils
+from ui.sentimentals import Ui_Sentiment_Form
+from PySide2 import QtCore, QtGui, QtWidgets
+from libs.tradingview_ta import TA_Handler, Interval, Exchange
+
+TICKERS = ['TSLA', 'AAPL', 'MSFT', 'GLE.PA', 'FP.PA']
+
+LVLS = {
+ "STRONG_SELL": 10,
+ "SELL": 25,
+ "NEUTRAL": 50,
+ "BUY": 75,
+ "STRONG_BUY": 90,
+}
+
+
+class Sentimental_Widget(QtWidgets.QWidget):
+ def __init__(self, parent=None, ticker=None):
+ super(Sentimental_Widget, self).__init__(parent=parent)
+
+ layout = QtWidgets.QVBoxLayout(self)
+
+ for ticker in TICKERS:
+ item = self.get_widget_items(ticker)
+ layout.addWidget(item)
+
+ def get_widget_items(self, tick):
+ widget = Sentimental_Widget_Item()
+ widget.set_sentimental_ui(ticker=tick)
+ return widget
+
+
+class Sentimental_Widget_Item(QtWidgets.QWidget, Ui_Sentiment_Form):
+ def __init__(self, parent=None):
+ super(Sentimental_Widget_Item, self).__init__(parent=parent)
+ self.setupUi(self)
+
+ def set_sentimental_ui(self, ticker):
+ sentiment = self.get_sentiments(ticker)
+ prc = LVLS[sentiment['RECOMMENDATION']]
+ compagny_name = utils.get_compagny_name_from_tick(ticker)
+
+ self.horizontalSlider.setValue(prc)
+ self.horizontalSlider.setEnabled(False)
+ self.label.setText(compagny_name)
+ self.label.setFont(QtGui.QFont("Times", 12))
+
+ def get_sentiments(self, tick):
+ """This method get the Sentiment from the Sentimentals Class
+ """
+ sentiment = Sentimentals(tick)
+ sentiment = sentiment.get_senti()
+ return sentiment
+
+
+class Sentimentals(object):
+ """
+ This is returning the sentiment for a compagny.
+ Going on TradingView, look for indicator if it s a buying, selling or neutral.
+
+ :return: {'RECOMMENDATION': 'BUY', 'BUY: 15, 'SELL': 6, 'NEUTRAL': 4}
+ """
+
+ def __init__(self, ticker):
+ self.handler = TA_Handler()
+
+ # IF
+ ticker = utils.check_french_ticker(ticker)
+
+ country, screener = self.handler.get_screener_from_symabol(ticker)
+
+ self.handler.set_symbol_as(ticker)
+ self.handler.set_screener_as_stock(country)
+ self.handler.set_exchange_as_crypto_or_stock(screener)
+ self.handler.set_interval_as(Interval.INTERVAL_1_DAY)
+
+ def get_senti(self):
+ return self.handler.get_analysis().summary
+
+
+if __name__ == '__main__':
+ ticker = "FP.PA"
+ x = Sentimentals(ticker=ticker)
+ print(x.get_senti())
diff --git a/app/libs/widgets/textbrowser.py b/app/libs/widgets/textbrowser.py
new file mode 100644
index 0000000..009ef23
--- /dev/null
+++ b/app/libs/widgets/textbrowser.py
@@ -0,0 +1,24 @@
+from PySide2 import QtWidgets, QtGui, QtCore
+
+
+class Description(QtWidgets.QTextEdit):
+ def __init__(self, text):
+ super(Description, self).__init__()
+ self.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
+
+ size_policy = self.sizePolicy()
+ document = self.document()
+ margins = self.contentsMargins()
+ # document_width = size_policy - margins.left() - margins.right()
+ # print(QtCore.QSize(size_policy))
+ # print(document.setTextWidth(document_width))
+
+ self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ # self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
+
+ self.setFixedHeight(self.document().size().height() + self.contentsMargins().top() * 2)
+
+ if text:
+ self.setText("text")
+ self.setFont(QtGui.QFont("Times", 10))
diff --git a/app/libs/widgets/toolbar.py b/app/libs/widgets/toolbar.py
index bdb1b34..97b4638 100644
--- a/app/libs/widgets/toolbar.py
+++ b/app/libs/widgets/toolbar.py
@@ -28,7 +28,7 @@ def __init__(self, parent=None):
def _trigger_action(self):
"""Called on action triggered"""
sender = self.sender()
- self.signals.sig_action_triggered.emit(sender.action)
+ self.signals.sig_action_triggered.emit(sender.action, sender.args)
def init_toolbar(self):
"""This method inits the toolbar of the app"""
@@ -136,6 +136,7 @@ def __init__(self, parent=None, text=None, action=None, **kwargs):
# Properties
self.action = action
+ self.args = kwargs.get("args", {})
icon = kwargs.get("icon", None)
if icon:
diff --git a/app/libs/yahoo_fin/news.py b/app/libs/yahoo_fin/news.py
index 3a791cc..0abe32c 100644
--- a/app/libs/yahoo_fin/news.py
+++ b/app/libs/yahoo_fin/news.py
@@ -1,10 +1,18 @@
import feedparser
-yf_rss_url = "https://feeds.finance.yahoo.com/rss/2.0/headline?s=%s®ion=US&lang=en-US"
+yf_rss_url = "https://feeds.finance.yahoo.com/rss/2.0/headline?s=%s®ion=FR&lang=fr-FR"
+home_page = "https://finance.yahoo.com/news/rssr/2.0/"
def get_yf_rss(ticker):
- feed = feedparser.parse(yf_rss_url % ticker)
+ feeds = feedparser.parse(yf_rss_url % ticker)
- return feed.entries
+ return feeds.entries
+
+
+def get_yf_home_rss():
+
+ feeds = feedparser.parse(home_page)
+
+ return feeds.entries
\ No newline at end of file
diff --git a/app/libs/yahoo_fin/stock_info.py b/app/libs/yahoo_fin/stock_info.py
index 506c973..d532964 100644
--- a/app/libs/yahoo_fin/stock_info.py
+++ b/app/libs/yahoo_fin/stock_info.py
@@ -5,6 +5,7 @@
import re
import json
import datetime
+from bs4 import BeautifulSoup
try:
from requests_html import HTMLSession
@@ -227,14 +228,14 @@ def tickers_other(include_company_data=False):
return tickers
-def tickers_dow(include_company_data=False):
+def _tickers_dow_(include_company_data=False):
"""Downloads list of currently traded tickers on the Dow"""
site = "https://finance.yahoo.com/quote/%5EDJI/components?p=%5EDJI"
table = pd.read_html(site)[0]
-
+ print(table)
if include_company_data:
return table
@@ -245,6 +246,23 @@ def tickers_dow(include_company_data=False):
return dow_tickers
+def tickers_dow():
+
+ site = "https://en.wikipedia.org/wiki/Dow_Jones_Industrial_Average"
+ head = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"
+
+ request = requests.get(site, headers={"user-agent": head})
+ soup = BeautifulSoup(request.text, 'html.parser')
+
+ ul = soup.find("table", {"class": "wikitable sortable"})
+ title = ul.findAll("th", {"scope": "row"})
+ tickers = ul.findAll("a", {"class": "external text"})
+
+ dow_tickers = {
+ key: value
+ for key, value in zip([i.text for i in tickers], [i.text.strip() for i in title])
+ }
+ return dow_tickers
def tickers_ibovespa(include_company_data=False):
diff --git a/app/resources/img/no_file.png b/app/resources/img/no_file.png
new file mode 100644
index 0000000..bd0ad99
Binary files /dev/null and b/app/resources/img/no_file.png differ
diff --git a/app/resources/img/splashscreen.jpg b/app/resources/img/splashscreen.jpg
new file mode 100644
index 0000000..ed53ac8
Binary files /dev/null and b/app/resources/img/splashscreen.jpg differ
diff --git a/app/resources/img/splashscreen_2.jpg b/app/resources/img/splashscreen_2.jpg
new file mode 100644
index 0000000..be99274
Binary files /dev/null and b/app/resources/img/splashscreen_2.jpg differ
diff --git a/app/resources/resources.qrc b/app/resources/resources.qrc
index d05714d..f5c1a54 100644
--- a/app/resources/resources.qrc
+++ b/app/resources/resources.qrc
@@ -27,8 +27,9 @@
svg/line.svg
svg/dot-line.svg
svg/dash-line.svg
-
style/style.qss
toolbar/toolbar.json
+ img/no_file.png
+ img/splashscreen.jpg
diff --git a/app/resources/resources_rc.py b/app/resources/resources_rc.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/resources/style/style.qss b/app/resources/style/style.qss
index b637ede..34535a0 100644
--- a/app/resources/style/style.qss
+++ b/app/resources/style/style.qss
@@ -44,6 +44,20 @@ QTabBar::tab:selected:disabled {
border-bottom: 2px solid palette(light);
}
+
+/*
+ * QTabWidget
+ */
+
+QTabWidget#tab_settings {
+ background: transparent;
+ border: none;
+}
+
+QWidget#wgt_settings_input,#wgt_settings_styles {
+ background: palette(base);
+}
+
/*
* QScrollBar
*/
@@ -132,7 +146,7 @@ QSlider::handle:horizontal {
}
QSlider::add-page:horizontal {
- background: palette(base);
+ background: palette(base);
}
QSlider::sub-page:horizontal {
@@ -143,6 +157,25 @@ QSlider::sub-page:horizontal:disabled {
background-color: palette(light);
}
+QSlider::sub-page#horizontalSlider:horizontal:disabled{
+ border-radius: 5px;
+ background:transparent;
+}
+
+QSlider::sub-page#horizontalSlider:horizontal:disabled{
+ background-color: rgb(42, 218, 130);
+ background-color: qlineargradient(spread:pad,
+ x1:0, y1:0, x2:1, y2:0,
+ stop:0 rgb(61, 217, 245),
+ stop:1 rgb(42, 130, 218));
+}
+
+QSlider::handle#horizontalSlider:horizontal:disabled{
+
+}
+
+/* QTableView */
+
QTableView {
background-color: palette(link-visited);
alternate-background-color: palette(midlight);
@@ -175,3 +208,52 @@ QDockWidget {
titlebar-close-icon: url(:/svg/times.svg);
titlebar-normal-icon: url(:/svg/window.svg);
}
+
+/* TableWidget */
+QTableWidget#wid_table_financ,#tab_settings {
+ background:transparent;
+ border-style: none;
+}
+
+/* QListWidget */
+
+QListWidget#wdg_article_welcome,#wgt_articles,#lst_inputs {
+ background:transparent;
+ border-style: none;
+}
+
+/* QPushButton */
+QPushButton#pub_cancel,#pub_ok,#pub_reset{
+ background: palette(midlight);
+ height:20px;
+ border-radius:3px;
+}
+
+/* QComboBox */
+QComboBox#cob_value_list,#cob_line_style{
+ background: palette(midlight);
+ border-radius:3px;
+ width: 50px;
+ height:20px;
+}
+
+/* QDoubleSpinBox */
+QDoubleSpinBox{
+ background: palette(midlight);
+ border-radius:3px;
+ width: 40px;
+ height:20px;
+}
+
+
+/* QSpinBox */
+QSpinBox{
+ background: palette(midlight);
+ border-radius:3px;
+ width: 40px;
+ height:20px;
+}
+
+SetSysColors{
+ border: 1px solid black;
+}
\ No newline at end of file
diff --git a/app/resources/toolbar/toolbar.json b/app/resources/toolbar/toolbar.json
index f3f4d7b..dd5c0bd 100644
--- a/app/resources/toolbar/toolbar.json
+++ b/app/resources/toolbar/toolbar.json
@@ -16,7 +16,10 @@
"type": "resources",
"value": ":/svg/trend-line.svg"
},
- "action": "method_to_call",
+ "action": "roi_manager.set_tool",
+ "args": {
+ "tool": "bounded_line_drawer"
+ },
"tooltip": "Trend Line",
"text": "Trend Line",
"shortcut": "Alt+T",
diff --git a/app/ui/__convert.py b/app/ui/__convert.py
index 9e6c83e..78e4af8 100644
--- a/app/ui/__convert.py
+++ b/app/ui/__convert.py
@@ -1,6 +1,6 @@
import subprocess
-cmd = "C:\\Users\\vince\\AppData\\Roaming\\Python\\Python39\\Scripts\\pyside2-uic.exe main_window.ui > main_window.py"
+cmd = "C:\\Users\\vince\\AppData\\Roaming\\Python\\Python39\\Scripts\\pyside2-uic.exe splashscreen.ui > splashscreen.py"
# cmd = "C:\\Users\\vince\\AppData\\Roaming\\Python\\Python39\\Scripts\\pyside2-uic.exe search.ui > search.py"
-
+# main_window markets_widget article indicator_settings_dialog sentimentals splashscreen
subprocess.run(cmd, shell=True)
diff --git a/app/ui/article.py b/app/ui/article.py
new file mode 100644
index 0000000..61573c3
--- /dev/null
+++ b/app/ui/article.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'article.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+from libs.widgets.label import LabelTitle
+
+
+class Ui_Article(object):
+ def setupUi(self, Article):
+ if not Article.objectName():
+ Article.setObjectName(u"Article")
+ Article.resize(638, 219)
+ self.verticalLayout_2 = QVBoxLayout(Article)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.thumbnail = QLabel(Article)
+ self.thumbnail.setObjectName(u"thumbnail")
+ self.thumbnail.setMinimumSize(QSize(250, 150))
+ self.thumbnail.setMaximumSize(QSize(250, 150))
+
+ self.horizontalLayout.addWidget(self.thumbnail)
+
+ self.horizontalSpacer = QSpacerItem(5, 20, QSizePolicy.Fixed, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(-1, -1, 0, -1)
+ self.lb_title = LabelTitle(Article)
+ self.lb_title.setObjectName(u"lb_title")
+
+ self.verticalLayout.addWidget(self.lb_title)
+
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setSpacing(0)
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.lb_date = QLabel(Article)
+ self.lb_date.setObjectName(u"lb_date")
+ self.lb_date.setMinimumSize(QSize(100, 25))
+ self.lb_date.setMaximumSize(QSize(100, 16777215))
+
+ self.horizontalLayout_2.addWidget(self.lb_date)
+
+ self.lb_compagny = QLabel(Article)
+ self.lb_compagny.setObjectName(u"lb_compagny")
+ self.lb_compagny.setMinimumSize(QSize(0, 25))
+ self.lb_compagny.setMaximumSize(QSize(16777215, 16777215))
+
+ self.horizontalLayout_2.addWidget(self.lb_compagny)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+
+ self.desc = QLabel(Article)
+ self.desc.setObjectName(u"desc")
+ self.desc.setMinimumSize(QSize(175, 0))
+
+ self.verticalLayout.addWidget(self.desc)
+
+
+ self.horizontalLayout.addLayout(self.verticalLayout)
+
+
+ self.verticalLayout_2.addLayout(self.horizontalLayout)
+
+ self.line = QFrame(Article)
+ self.line.setObjectName(u"line")
+ self.line.setFrameShape(QFrame.HLine)
+ self.line.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout_2.addWidget(self.line)
+
+
+ self.retranslateUi(Article)
+
+ QMetaObject.connectSlotsByName(Article)
+ # setupUi
+
+ def retranslateUi(self, Article):
+ Article.setWindowTitle(QCoreApplication.translate("Article", u"Form", None))
+ self.thumbnail.setText("")
+ self.lb_title.setText(QCoreApplication.translate("Article", u"Title", None))
+ self.lb_date.setText(QCoreApplication.translate("Article", u"date", None))
+ self.lb_compagny.setText("")
+ self.desc.setText("")
+ # retranslateUi
+
diff --git a/app/ui/article.ui b/app/ui/article.ui
new file mode 100644
index 0000000..2dc2890
--- /dev/null
+++ b/app/ui/article.ui
@@ -0,0 +1,146 @@
+
+
+ Article
+
+
+
+ 0
+ 0
+ 638
+ 219
+
+
+
+ Form
+
+
+ -
+
+
-
+
+
+
+ 250
+ 150
+
+
+
+
+ 250
+ 150
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Title
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 100
+ 25
+
+
+
+
+ 100
+ 16777215
+
+
+
+ date
+
+
+
+ -
+
+
+
+ 0
+ 25
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 175
+ 0
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+
+
+
+ LabelTitle
+ QLabel
+
+
+
+
+
+
diff --git a/app/ui/indicator_settings_dialog.py b/app/ui/indicator_settings_dialog.py
index 167e7cc..0a003c9 100644
--- a/app/ui/indicator_settings_dialog.py
+++ b/app/ui/indicator_settings_dialog.py
@@ -16,10 +16,8 @@
class Ui_IndicatorSettingsDialogWindow(object):
def setupUi(self, IndicatorSettingsDialogWindow):
if not IndicatorSettingsDialogWindow.objectName():
- IndicatorSettingsDialogWindow.setObjectName(
- u"IndicatorSettingsDialogWindow"
- )
- IndicatorSettingsDialogWindow.resize(400, 300)
+ IndicatorSettingsDialogWindow.setObjectName(u"IndicatorSettingsDialogWindow")
+ IndicatorSettingsDialogWindow.resize(400, 301)
self.verticalLayout = QVBoxLayout(IndicatorSettingsDialogWindow)
self.verticalLayout.setObjectName(u"verticalLayout")
self.tab_settings = QTabWidget(IndicatorSettingsDialogWindow)
@@ -71,51 +69,25 @@ def setupUi(self, IndicatorSettingsDialogWindow):
self.horizontalLayout.addWidget(self.pub_ok)
+
self.verticalLayout.addLayout(self.horizontalLayout)
+
self.retranslateUi(IndicatorSettingsDialogWindow)
self.tab_settings.setCurrentIndex(0)
self.pub_ok.setDefault(True)
- QMetaObject.connectSlotsByName(IndicatorSettingsDialogWindow)
+ QMetaObject.connectSlotsByName(IndicatorSettingsDialogWindow)
# setupUi
def retranslateUi(self, IndicatorSettingsDialogWindow):
- IndicatorSettingsDialogWindow.setWindowTitle(
- QCoreApplication.translate(
- "IndicatorSettingsDialogWindow",
- u"IndicatorSettingsDialogWindow",
- None,
- )
- )
- self.tab_settings.setTabText(
- self.tab_settings.indexOf(self.wgt_settings_input),
- QCoreApplication.translate(
- "IndicatorSettingsDialogWindow", u"Input", None
- ),
- )
- self.tab_settings.setTabText(
- self.tab_settings.indexOf(self.wgt_settings_styles),
- QCoreApplication.translate(
- "IndicatorSettingsDialogWindow", u"Style", None
- ),
- )
- self.pub_reset.setText(
- QCoreApplication.translate(
- "IndicatorSettingsDialogWindow", u"Reset values", None
- )
- )
- self.pub_cancel.setText(
- QCoreApplication.translate(
- "IndicatorSettingsDialogWindow", u"Cancel", None
- )
- )
- self.pub_ok.setText(
- QCoreApplication.translate(
- "IndicatorSettingsDialogWindow", u"Ok", None
- )
- )
-
+ IndicatorSettingsDialogWindow.setWindowTitle(QCoreApplication.translate("IndicatorSettingsDialogWindow", u"Settings Indicators", None))
+ self.tab_settings.setTabText(self.tab_settings.indexOf(self.wgt_settings_input), QCoreApplication.translate("IndicatorSettingsDialogWindow", u"Input", None))
+ self.tab_settings.setTabText(self.tab_settings.indexOf(self.wgt_settings_styles), QCoreApplication.translate("IndicatorSettingsDialogWindow", u"Style", None))
+ self.pub_reset.setText(QCoreApplication.translate("IndicatorSettingsDialogWindow", u"Reset values", None))
+ self.pub_cancel.setText(QCoreApplication.translate("IndicatorSettingsDialogWindow", u"Cancel", None))
+ self.pub_ok.setText(QCoreApplication.translate("IndicatorSettingsDialogWindow", u"Ok", None))
# retranslateUi
+
diff --git a/app/ui/indicator_settings_dialog.ui b/app/ui/indicator_settings_dialog.ui
index 4aca884..aae48b8 100644
--- a/app/ui/indicator_settings_dialog.ui
+++ b/app/ui/indicator_settings_dialog.ui
@@ -7,11 +7,11 @@
0
0
400
- 300
+ 301
- IndicatorSettingsDialogWindow
+ Settings Indicators
-
diff --git a/app/ui/main_window.py b/app/ui/main_window.py
index 1d8a1a4..2ab8841 100644
--- a/app/ui/main_window.py
+++ b/app/ui/main_window.py
@@ -12,6 +12,7 @@
from PySide2.QtGui import *
from PySide2.QtWidgets import *
+from libs.widgets.label import LabelTitle
from libs.graph.graphwidget import GraphWidget
from libs.indicators_widget import IndicatorsWidget
from libs.widgets.toolbar import ToolBar
@@ -19,19 +20,20 @@
from libs.company_widget import CompanyWidget
from libs.favorites_widget import FavoritesWidget
from libs.articles_widget import ArticlesWidget
+from libs.financial_widget import TableFinance
+from libs.welcome_widget import WelcomeWidget
+from libs.markets_widget import MarketsWidget
+from libs.widgets.sentimentals_widget import Sentimental_Widget
import resources_rc
-
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
- MainWindow.resize(900, 700)
+ MainWindow.resize(998, 754)
self.action_reload_indicators = QAction(MainWindow)
- self.action_reload_indicators.setObjectName(
- u"action_reload_indicators"
- )
+ self.action_reload_indicators.setObjectName(u"action_reload_indicators")
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.verticalLayout = QVBoxLayout(self.centralwidget)
@@ -44,9 +46,7 @@ def setupUi(self, MainWindow):
self.pub_go_welcome.setMaximumSize(QSize(25, 25))
self.pub_go_welcome.setCursor(QCursor(Qt.PointingHandCursor))
icon = QIcon()
- icon.addFile(
- u":/svg/keyboard-arrow-left.svg", QSize(), QIcon.Normal, QIcon.Off
- )
+ icon.addFile(u":/svg/keyboard-arrow-left.svg", QSize(), QIcon.Normal, QIcon.Off)
self.pub_go_welcome.setIcon(icon)
self.pub_go_welcome.setIconSize(QSize(28, 28))
self.pub_go_welcome.setFlat(True)
@@ -66,36 +66,164 @@ def setupUi(self, MainWindow):
self.pub_go_graph.setMaximumSize(QSize(25, 25))
self.pub_go_graph.setCursor(QCursor(Qt.PointingHandCursor))
icon1 = QIcon()
- icon1.addFile(
- u":/svg/keyboard-arrow-right.svg", QSize(), QIcon.Normal, QIcon.Off
- )
+ icon1.addFile(u":/svg/keyboard-arrow-right.svg", QSize(), QIcon.Normal, QIcon.Off)
self.pub_go_graph.setIcon(icon1)
self.pub_go_graph.setIconSize(QSize(28, 28))
self.pub_go_graph.setFlat(True)
self.horizontalLayout.addWidget(self.pub_go_graph)
+
self.verticalLayout.addLayout(self.horizontalLayout)
self.stw_main = StackedWidget(self.centralwidget)
self.stw_main.setObjectName(u"stw_main")
self.wgt_welcome = QWidget()
self.wgt_welcome.setObjectName(u"wgt_welcome")
+ self.verticalLayout_3 = QVBoxLayout(self.wgt_welcome)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.label = LabelTitle(self.wgt_welcome)
+ self.label.setObjectName(u"label")
+ self.label.setAlignment(Qt.AlignCenter)
+
+ self.verticalLayout_3.addWidget(self.label)
+
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Minimum)
+
+ self.verticalLayout_3.addItem(self.verticalSpacer)
+
+ self.horizontalLayout_3 = QHBoxLayout()
+ self.horizontalLayout_3.setSpacing(0)
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.horizontalLayout_3.setSizeConstraint(QLayout.SetMinimumSize)
+ self.pub_go_market_prev = QPushButton(self.wgt_welcome)
+ self.pub_go_market_prev.setObjectName(u"pub_go_market_prev")
+ self.pub_go_market_prev.setMinimumSize(QSize(25, 25))
+ self.pub_go_market_prev.setMaximumSize(QSize(25, 25))
+ self.pub_go_market_prev.setCursor(QCursor(Qt.PointingHandCursor))
+ self.pub_go_market_prev.setIcon(icon)
+ self.pub_go_market_prev.setIconSize(QSize(28, 28))
+ self.pub_go_market_prev.setFlat(True)
+
+ self.horizontalLayout_3.addWidget(self.pub_go_market_prev)
+
+ self.wgt_markets_2 = MarketsWidget(self.wgt_welcome)
+ self.wgt_markets_2.setObjectName(u"wgt_markets_2")
+ self.wgt_markets_2.setMaximumSize(QSize(16777215, 80))
+
+ self.horizontalLayout_3.addWidget(self.wgt_markets_2)
+
+ self.pub_go_market_next = QPushButton(self.wgt_welcome)
+ self.pub_go_market_next.setObjectName(u"pub_go_market_next")
+ self.pub_go_market_next.setMinimumSize(QSize(25, 25))
+ self.pub_go_market_next.setMaximumSize(QSize(25, 25))
+ self.pub_go_market_next.setCursor(QCursor(Qt.PointingHandCursor))
+ self.pub_go_market_next.setIcon(icon1)
+ self.pub_go_market_next.setIconSize(QSize(28, 28))
+ self.pub_go_market_next.setFlat(True)
+
+ self.horizontalLayout_3.addWidget(self.pub_go_market_next)
+
+
+ self.verticalLayout_3.addLayout(self.horizontalLayout_3)
+
+ self.line = QFrame(self.wgt_welcome)
+ self.line.setObjectName(u"line")
+ self.line.setFrameShape(QFrame.HLine)
+ self.line.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout_3.addWidget(self.line)
+
+ self.horizontalLayout_5 = QHBoxLayout()
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+ self.wdg_article_welcome = WelcomeWidget(self.wgt_welcome)
+ self.wdg_article_welcome.setObjectName(u"wdg_article_welcome")
+
+ self.horizontalLayout_5.addWidget(self.wdg_article_welcome)
+
+ self.verticalWidget = Sentimental_Widget(self.wgt_welcome)
+ self.verticalWidget.setObjectName(u"verticalWidget")
+ self.verticalWidget.setMinimumSize(QSize(0, 0))
+ self.verticalWidget.setMaximumSize(QSize(300, 16777215))
+ self.verticalLayout_6 = QVBoxLayout(self.verticalWidget)
+ self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+
+ self.horizontalLayout_5.addWidget(self.verticalWidget)
+
+
+ self.verticalLayout_3.addLayout(self.horizontalLayout_5)
+
self.stw_main.addWidget(self.wgt_welcome)
- self.wgt_articles = ArticlesWidget()
+ self.wgt_article = QWidget()
+ self.wgt_article.setObjectName(u"wgt_article")
+ self.verticalLayout_2 = QVBoxLayout(self.wgt_article)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.label_2 = LabelTitle(self.wgt_article)
+ self.label_2.setObjectName(u"label_2")
+ self.label_2.setAlignment(Qt.AlignCenter)
+
+ self.verticalLayout_2.addWidget(self.label_2)
+
+ self.line_2 = QFrame(self.wgt_article)
+ self.line_2.setObjectName(u"line_2")
+ self.line_2.setFrameShape(QFrame.HLine)
+ self.line_2.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout_2.addWidget(self.line_2)
+
+ self.wgt_articles = ArticlesWidget(self.wgt_article)
self.wgt_articles.setObjectName(u"wgt_articles")
- self.stw_main.addWidget(self.wgt_articles)
+
+ self.verticalLayout_2.addWidget(self.wgt_articles)
+
+ self.stw_main.addWidget(self.wgt_article)
self.wgt_graph = GraphWidget()
self.wgt_graph.setObjectName(u"wgt_graph")
self.wgt_graph.setCursor(QCursor(Qt.CrossCursor))
self.stw_main.addWidget(self.wgt_graph)
+ self.wgt_financ = QWidget()
+ self.wgt_financ.setObjectName(u"wgt_financ")
+ self.verticalLayout_4 = QVBoxLayout(self.wgt_financ)
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.financial_label = LabelTitle(self.wgt_financ)
+ self.financial_label.setObjectName(u"financial_label")
+ self.financial_label.setAlignment(Qt.AlignCenter)
+
+ self.verticalLayout_4.addWidget(self.financial_label)
+
+ self.financials_layout = QHBoxLayout()
+ self.financials_layout.setObjectName(u"financials_layout")
+
+ self.verticalLayout_4.addLayout(self.financials_layout)
+
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalSpacer = QSpacerItem(240, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer)
+
+ self.wid_table_financ = TableFinance(self.wgt_financ)
+ self.wid_table_financ.setObjectName(u"wid_table_financ")
+ self.wid_table_financ.setMinimumSize(QSize(850, 0))
+ self.wid_table_financ.setMaximumSize(QSize(850, 16777215))
+
+ self.horizontalLayout_2.addWidget(self.wid_table_financ)
+
+ self.horizontalSpacer_2 = QSpacerItem(240, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer_2)
+
+
+ self.verticalLayout_4.addLayout(self.horizontalLayout_2)
+
+ self.stw_main.addWidget(self.wgt_financ)
self.verticalLayout.addWidget(self.stw_main)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(MainWindow)
self.menubar.setObjectName(u"menubar")
- self.menubar.setGeometry(QRect(0, 0, 900, 21))
+ self.menubar.setGeometry(QRect(0, 0, 998, 21))
self.menuOptions = QMenu(self.menubar)
self.menuOptions.setObjectName(u"menuOptions")
MainWindow.setMenuBar(self.menubar)
@@ -107,9 +235,7 @@ def setupUi(self, MainWindow):
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(
- self.dock_wgt_company.sizePolicy().hasHeightForWidth()
- )
+ sizePolicy.setHeightForWidth(self.dock_wgt_company.sizePolicy().hasHeightForWidth())
self.dock_wgt_company.setSizePolicy(sizePolicy)
font = QFont()
font.setBold(True)
@@ -125,9 +251,7 @@ def setupUi(self, MainWindow):
self.wgt_indicators = IndicatorsWidget()
self.wgt_indicators.setObjectName(u"wgt_indicators")
self.dock_wgt_indicators.setWidget(self.wgt_indicators)
- MainWindow.addDockWidget(
- Qt.RightDockWidgetArea, self.dock_wgt_indicators
- )
+ MainWindow.addDockWidget(Qt.RightDockWidgetArea, self.dock_wgt_indicators)
self.tool_bar = ToolBar(MainWindow)
self.tool_bar.setObjectName(u"tool_bar")
MainWindow.addToolBar(Qt.LeftToolBarArea, self.tool_bar)
@@ -137,46 +261,33 @@ def setupUi(self, MainWindow):
self.wgt_favorites = FavoritesWidget()
self.wgt_favorites.setObjectName(u"wgt_favorites")
self.dock_wgt_favorites.setWidget(self.wgt_favorites)
- MainWindow.addDockWidget(
- Qt.RightDockWidgetArea, self.dock_wgt_favorites
- )
+ MainWindow.addDockWidget(Qt.RightDockWidgetArea, self.dock_wgt_favorites)
self.menubar.addAction(self.menuOptions.menuAction())
self.menuOptions.addAction(self.action_reload_indicators)
self.retranslateUi(MainWindow)
- self.stw_main.setCurrentIndex(1)
+ self.stw_main.setCurrentIndex(0)
- QMetaObject.connectSlotsByName(MainWindow)
+ QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
- MainWindow.setWindowTitle(
- QCoreApplication.translate("MainWindow", u"Trade Helper", None)
- )
- self.action_reload_indicators.setText(
- QCoreApplication.translate(
- "MainWindow", u"Reload Indicators", None
- )
- )
+ MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Trade Helper", None))
+ self.action_reload_indicators.setText(QCoreApplication.translate("MainWindow", u"Reload Indicators", None))
self.pub_go_welcome.setText("")
self.pub_go_graph.setText("")
- self.menuOptions.setTitle(
- QCoreApplication.translate("MainWindow", u"Options", None)
- )
- self.dock_wgt_company.setWindowTitle(
- QCoreApplication.translate("MainWindow", u"Company", None)
- )
- self.dock_wgt_indicators.setWindowTitle(
- QCoreApplication.translate("MainWindow", u"Indicators", None)
- )
- self.tool_bar.setWindowTitle(
- QCoreApplication.translate("MainWindow", u"toolBar", None)
- )
- self.dock_wgt_favorites.setWindowTitle(
- QCoreApplication.translate("MainWindow", u"Favorites", None)
- )
-
+ self.label.setText(QCoreApplication.translate("MainWindow", u"Trading Visualisation", None))
+ self.pub_go_market_prev.setText("")
+ self.pub_go_market_next.setText("")
+ self.label_2.setText(QCoreApplication.translate("MainWindow", u"Related News", None))
+ self.financial_label.setText(QCoreApplication.translate("MainWindow", u"Financials", None))
+ self.menuOptions.setTitle(QCoreApplication.translate("MainWindow", u"Options", None))
+ self.dock_wgt_company.setWindowTitle(QCoreApplication.translate("MainWindow", u"Company", None))
+ self.dock_wgt_indicators.setWindowTitle(QCoreApplication.translate("MainWindow", u"Indicators", None))
+ self.tool_bar.setWindowTitle(QCoreApplication.translate("MainWindow", u"toolBar", None))
+ self.dock_wgt_favorites.setWindowTitle(QCoreApplication.translate("MainWindow", u"Favorites", None))
# retranslateUi
+
diff --git a/app/ui/main_window.ui b/app/ui/main_window.ui
index 85ba138..5df0186 100644
--- a/app/ui/main_window.ui
+++ b/app/ui/main_window.ui
@@ -6,8 +6,8 @@
0
0
- 900
- 700
+ 998
+ 754
@@ -102,15 +102,257 @@
-
- 1
+ 0
-
-
+
+
+
-
+
+
+ Trading Visualisation
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Minimum
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ 0
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+
+ 25
+ 25
+
+
+
+
+ 25
+ 25
+
+
+
+ PointingHandCursor
+
+
+
+
+
+
+ :/svg/keyboard-arrow-left.svg:/svg/keyboard-arrow-left.svg
+
+
+
+ 28
+ 28
+
+
+
+ true
+
+
+
+ -
+
+
+
+ 16777215
+ 80
+
+
+
+
+ -
+
+
+
+ 25
+ 25
+
+
+
+
+ 25
+ 25
+
+
+
+ PointingHandCursor
+
+
+
+
+
+
+ :/svg/keyboard-arrow-right.svg:/svg/keyboard-arrow-right.svg
+
+
+
+ 28
+ 28
+
+
+
+ true
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 16777215
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Related News
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+
CrossCursor
+
+
+ -
+
+
+ Financials
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Expanding
+
+
+
+ 240
+ 20
+
+
+
+
+ -
+
+
+
+ 850
+ 0
+
+
+
+
+ 850
+ 16777215
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Expanding
+
+
+
+ 240
+ 20
+
+
+
+
+
+
+
+
@@ -120,7 +362,7 @@
0
0
- 900
+ 998
21
@@ -202,6 +444,11 @@
+
+ LabelTitle
+ QLabel
+
+
GraphWidget
QWidget
@@ -243,6 +490,28 @@
1
+
+ TableFinance
+ QTableWidget
+
+
+
+ WelcomeWidget
+ QListWidget
+
+
+
+ MarketsWidget
+ QStackedWidget
+
+ 1
+
+
+ Sentimental_Widget
+ QWidget
+ libs.widgets.sentimentals_widget.h
+ 1
+
diff --git a/app/ui/markets_widget.py b/app/ui/markets_widget.py
new file mode 100644
index 0000000..4071473
--- /dev/null
+++ b/app/ui/markets_widget.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'markets_widget.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_markets(object):
+ def setupUi(self, markets):
+ if not markets.objectName():
+ markets.setObjectName(u"markets")
+ markets.resize(125, 80)
+ markets.setMinimumSize(QSize(125, 80))
+ markets.setMaximumSize(QSize(125, 80))
+ self.horizontalLayout = QHBoxLayout(markets)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.title = QLabel(markets)
+ self.title.setObjectName(u"title")
+ font = QFont()
+ font.setPointSize(12)
+ self.title.setFont(font)
+
+ self.verticalLayout.addWidget(self.title)
+
+ self.price = QLabel(markets)
+ self.price.setObjectName(u"price")
+ font1 = QFont()
+ font1.setPointSize(10)
+ self.price.setFont(font1)
+
+ self.verticalLayout.addWidget(self.price)
+
+ self.pourcentage = QLabel(markets)
+ self.pourcentage.setObjectName(u"pourcentage")
+ self.pourcentage.setFont(font1)
+
+ self.verticalLayout.addWidget(self.pourcentage)
+
+
+ self.horizontalLayout.addLayout(self.verticalLayout)
+
+ self.line = QFrame(markets)
+ self.line.setObjectName(u"line")
+ self.line.setFrameShape(QFrame.VLine)
+ self.line.setFrameShadow(QFrame.Sunken)
+
+ self.horizontalLayout.addWidget(self.line)
+
+
+ self.retranslateUi(markets)
+
+ QMetaObject.connectSlotsByName(markets)
+ # setupUi
+
+ def retranslateUi(self, markets):
+ markets.setWindowTitle(QCoreApplication.translate("markets", u"Form", None))
+ self.title.setText(QCoreApplication.translate("markets", u"Markets", None))
+ self.price.setText(QCoreApplication.translate("markets", u"Price", None))
+ self.pourcentage.setText(QCoreApplication.translate("markets", u"%", None))
+ # retranslateUi
+
diff --git a/app/ui/markets_widget.ui b/app/ui/markets_widget.ui
new file mode 100644
index 0000000..601b552
--- /dev/null
+++ b/app/ui/markets_widget.ui
@@ -0,0 +1,80 @@
+
+
+ markets
+
+
+
+ 0
+ 0
+ 125
+ 80
+
+
+
+
+ 125
+ 80
+
+
+
+
+ 125
+ 80
+
+
+
+ Form
+
+
+ -
+
+
-
+
+
+
+ 12
+
+
+
+ Markets
+
+
+
+ -
+
+
+
+ 10
+
+
+
+ Price
+
+
+
+ -
+
+
+
+ 10
+
+
+
+ %
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+
+
+
+
+
diff --git a/app/ui/sentimentals.py b/app/ui/sentimentals.py
new file mode 100644
index 0000000..9f46817
--- /dev/null
+++ b/app/ui/sentimentals.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'sentimentals.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+import resources_rc
+
+class Ui_Sentiment_Form(object):
+ def setupUi(self, Sentiment_Form):
+ if not Sentiment_Form.objectName():
+ Sentiment_Form.setObjectName(u"Sentiment_Form")
+ Sentiment_Form.resize(300, 90)
+ Sentiment_Form.setMaximumSize(QSize(300, 90))
+ self.verticalLayout = QVBoxLayout(Sentiment_Form)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.label = QLabel(Sentiment_Form)
+ self.label.setObjectName(u"label")
+ self.label.setMinimumSize(QSize(0, 15))
+ self.label.setMaximumSize(QSize(16777215, 80))
+ self.label.setAlignment(Qt.AlignCenter)
+
+ self.verticalLayout.addWidget(self.label)
+
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.label_6 = QLabel(Sentiment_Form)
+ self.label_6.setObjectName(u"label_6")
+ self.label_6.setAlignment(Qt.AlignCenter)
+
+ self.horizontalLayout_2.addWidget(self.label_6)
+
+ self.label_7 = QLabel(Sentiment_Form)
+ self.label_7.setObjectName(u"label_7")
+ self.label_7.setAlignment(Qt.AlignCenter)
+
+ self.horizontalLayout_2.addWidget(self.label_7)
+
+ self.label_8 = QLabel(Sentiment_Form)
+ self.label_8.setObjectName(u"label_8")
+ self.label_8.setAlignment(Qt.AlignCenter)
+
+ self.horizontalLayout_2.addWidget(self.label_8)
+
+ self.label_9 = QLabel(Sentiment_Form)
+ self.label_9.setObjectName(u"label_9")
+ self.label_9.setAlignment(Qt.AlignCenter)
+
+ self.horizontalLayout_2.addWidget(self.label_9)
+
+ self.label_10 = QLabel(Sentiment_Form)
+ self.label_10.setObjectName(u"label_10")
+ self.label_10.setAlignment(Qt.AlignCenter)
+
+ self.horizontalLayout_2.addWidget(self.label_10)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+
+ self.horizontalSlider = QSlider(Sentiment_Form)
+ self.horizontalSlider.setObjectName(u"horizontalSlider")
+ self.horizontalSlider.setTracking(True)
+ self.horizontalSlider.setOrientation(Qt.Horizontal)
+
+ self.verticalLayout.addWidget(self.horizontalSlider)
+
+
+ self.retranslateUi(Sentiment_Form)
+
+ QMetaObject.connectSlotsByName(Sentiment_Form)
+ # setupUi
+
+ def retranslateUi(self, Sentiment_Form):
+ Sentiment_Form.setWindowTitle(QCoreApplication.translate("Sentiment_Form", u"Form", None))
+ self.label.setText(QCoreApplication.translate("Sentiment_Form", u"Compagny", None))
+ self.label_6.setText(QCoreApplication.translate("Sentiment_Form", u"Strong Sell", None))
+ self.label_7.setText(QCoreApplication.translate("Sentiment_Form", u"Sell", None))
+ self.label_8.setText(QCoreApplication.translate("Sentiment_Form", u"Neutral", None))
+ self.label_9.setText(QCoreApplication.translate("Sentiment_Form", u"Buy", None))
+ self.label_10.setText(QCoreApplication.translate("Sentiment_Form", u"Strong Buy", None))
+ # retranslateUi
+
diff --git a/app/ui/sentimentals.ui b/app/ui/sentimentals.ui
new file mode 100644
index 0000000..e48bbb4
--- /dev/null
+++ b/app/ui/sentimentals.ui
@@ -0,0 +1,115 @@
+
+
+ Sentiment_Form
+
+
+
+ 0
+ 0
+ 300
+ 90
+
+
+
+
+ 300
+ 90
+
+
+
+ Form
+
+
+ -
+
+
+
+ 0
+ 15
+
+
+
+
+ 16777215
+ 80
+
+
+
+ Compagny
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
-
+
+
+ Strong Sell
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Sell
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Neutral
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Buy
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Strong Buy
+
+
+ Qt::AlignCenter
+
+
+
+
+
+ -
+
+
+ true
+
+
+ Qt::Horizontal
+
+
+
+
+
+
+
+
+
+
diff --git a/app/ui/splashscreen.py b/app/ui/splashscreen.py
new file mode 100644
index 0000000..df6e56f
--- /dev/null
+++ b/app/ui/splashscreen.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'splashscreen.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_splashscreen(object):
+ def setupUi(self, splashscreen):
+ if not splashscreen.objectName():
+ splashscreen.setObjectName(u"splashscreen")
+ splashscreen.resize(900, 500)
+ self.verticalLayout = QVBoxLayout(splashscreen)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.lab_app_name = QLabel(splashscreen)
+ self.lab_app_name.setObjectName(u"lab_app_name")
+ self.lab_app_name.setMinimumSize(QSize(0, 230))
+ font = QFont()
+ font.setPointSize(18)
+ self.lab_app_name.setFont(font)
+ self.lab_app_name.setStyleSheet(u"color:#FFFFFF; ")
+ self.lab_app_name.setAlignment(Qt.AlignCenter)
+
+ self.verticalLayout.addWidget(self.lab_app_name)
+
+
+ self.retranslateUi(splashscreen)
+
+ QMetaObject.connectSlotsByName(splashscreen)
+ # setupUi
+
+ def retranslateUi(self, splashscreen):
+ splashscreen.setWindowTitle(QCoreApplication.translate("splashscreen", u"Form", None))
+ self.lab_app_name.setText(QCoreApplication.translate("splashscreen", u"app_name", None))
+ # retranslateUi
+
diff --git a/app/ui/splashscreen.ui b/app/ui/splashscreen.ui
new file mode 100644
index 0000000..2a390aa
--- /dev/null
+++ b/app/ui/splashscreen.ui
@@ -0,0 +1,45 @@
+
+
+ splashscreen
+
+
+
+ 0
+ 0
+ 900
+ 500
+
+
+
+ Form
+
+
+ -
+
+
+
+ 0
+ 230
+
+
+
+
+ 18
+
+
+
+ color:#FFFFFF;
+
+
+ app_name
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
+
diff --git a/app/utils/utils.py b/app/utils/utils.py
index 1708220..3d91841 100644
--- a/app/utils/utils.py
+++ b/app/utils/utils.py
@@ -1,6 +1,10 @@
+import os
+import json
+import random
import datetime
import urllib.request
from statistics import mean
+from libs.yahoo_fin import stock_info as sf
from sklearn import preprocessing
import numpy as np
@@ -36,16 +40,20 @@ def savgol_filter(values, window_length, polyorder=3):
mode="interp",
)
-
-def remove_nan(values):
- """Remove NaN from array
-
- :param values: array of data
- :type values: np.array
- :return: The array without NaN
- :rtype: np.array
+def remove_nan(data):
+ """
+ This method replace Nan value by 0.
+ Sinon return float.
+ :param data:
+ :return: List
"""
- return values[~np.isnan(values)]
+ data_format = []
+ for i in data:
+ if str(i) == "nan":
+ i = 0
+ data_format.append(float(i))
+ return data_format
+
def _peaks_detection(values, rounded=3, direction="up"):
@@ -236,3 +244,89 @@ def get_main_window_instance(name: str = "MainWindow"):
continue
return widget
return None
+
+
+
+def get_all_tickers():
+ """
+ This method return a dict of all the compagny for each markets.
+ """
+ try:
+ dow = sf.tickers_dow()
+ cac = sf.tickers_cac()
+ sp500 = sf.tickers_sp500()
+ nasdaq = sf.tickers_nasdaq()
+
+ data = {}
+ for i in [cac, dow, nasdaq, sp500]:
+ data.update(i)
+
+ except:
+ SCRIPT_PATH = os.path.dirname(os.path.dirname(__file__))
+ with open(os.path.join(SCRIPT_PATH, "data", "dataset.json"), "r") as f:
+ data = json.load(f)
+
+ return data
+
+
+def get_compagny_name_from_tick(ticker):
+ """
+ This method return the Compagny name for his ticker.
+ """
+
+ data = get_all_tickers()
+
+ for tick, company in data.items():
+ if ticker.startswith(tick):
+ return company
+
+def get_last_value(data):
+ if data[0] != 0:
+ index = 0
+ value = data[index]
+ else:
+ index = 1
+ value = data[index]
+ return value, index
+
+def format_data(data):
+ """
+ This method format number with ','.
+ exemple: 2,120,350
+ :param data:
+ :return: List of string
+ """
+ data_format = []
+ for i in remove_nan(data):
+ i = f"{int(i):,}"
+ data_format.append(i)
+ return data_format
+
+
+def get_rdm_tickers(qty=5):
+ """
+ This method get a quantity of randoms tickers.
+ return: list
+ """
+ all_tickers = get_all_tickers().keys()
+ tickers = random.sample(all_tickers, qty)
+ return tickers
+
+def check_french_ticker(ticker):
+ """This method split the ticker if endswith '.PA'
+ :return: str
+ """
+ try:
+ ticker = ticker.split('.')[0]
+ except:
+ pass
+
+ return ticker
+
+def clear_layout(layout):
+ """
+ This method remove all widgets inside a layout.
+ :param: QtLayout
+ """
+ for i in reversed(range(layout.count())):
+ layout.itemAt(i).widget().setParent(None)
\ No newline at end of file
diff --git a/app/view.py b/app/view.py
index ee274fe..04b7724 100644
--- a/app/view.py
+++ b/app/view.py
@@ -13,6 +13,9 @@
from libs.thread_pool import ThreadPool
from libs.graph.candlestick import CandlestickItem
from libs.io.favorite_settings import FavoritesManager
+from libs.widgets.sentimentals_widget import Sentimental_Widget_Item
+from libs.splashcreen import SplashScreen
+from libs.roi_manager import ROIManager
from ui import main_window
@@ -20,12 +23,20 @@
from utils import decorators
SCRIPT_PATH = os.path.dirname(__file__)
-
+TITLE = "TRADING VISUALISATION"
class MainWindow(QtWidgets.QMainWindow, main_window.Ui_MainWindow):
def __init__(self, parent=None, data=None):
super(MainWindow, self).__init__(parent=parent)
+ img = "resources\img\splashscreen.jpg"
+ path = os.path.join(SCRIPT_PATH, img)
+
+ self.splash = SplashScreen(path, TITLE)
+ # self.splash.show()
+ self.splash.show_message("Loading UI...\n\n")
+ QtWidgets.QApplication.processEvents()
+
self.setupUi(self)
self.setWindowState(QtCore.Qt.WindowMaximized)
@@ -67,9 +78,23 @@ def __init__(self, parent=None, data=None):
self.signals.sig_ticker_infos_fetched.connect(
self.wgt_company._on_ticker_infos
)
- ##
- self.signals.sig_ticker_articles_fetched.connect(
- self.wgt_articles.get_articles
+ self.tickers_dialog.signal.sig_ticker_choosen.connect(
+ self.wgt_articles._on_get_articles
+ )
+ self.wgt_favorites.signals.sig_favorite_clicked.connect(
+ self.wgt_articles._on_get_articles
+ )
+ self.tickers_dialog.signal.sig_ticker_choosen.connect(
+ self.wid_table_financ.on_set_financials_table
+ )
+ self.tickers_dialog.signal.sig_ticker_choosen.connect(
+ self.set_sentiment_compagny
+ )
+ self.wgt_favorites.signals.sig_favorite_clicked.connect(
+ self.wid_table_financ.on_set_financials_table
+ )
+ self.wgt_favorites.signals.sig_favorite_clicked.connect(
+ self.set_sentiment_compagny
)
self.thread_pool.signals.sig_thread_pre.connect(
self.busy_indicator.show
@@ -95,10 +120,15 @@ def __init__(self, parent=None, data=None):
self.pub_go_welcome.clicked.connect(self.stw_main.slide_in_prev)
self.pub_go_graph.clicked.connect(self.stw_main.slide_in_next)
+ self.pub_go_market_prev.clicked.connect(self.wgt_markets_2.slide_in_prev)
+ self.pub_go_market_next.clicked.connect(self.wgt_markets_2.slide_in_next)
# Action which needs to be loaded after all signals
+ self.splash.show_message("Loading Favorites...\n\n")
self.favorites_manager.load_favorite()
+ self.splash.hide()
+
def _init_app_home(self):
"""Init the APP_HOME of the application"""
base_path = os.path.expanduser("~")
@@ -157,16 +187,28 @@ def _on_indicator_switched(self, indicator: object, state: bool):
else:
indicator.remove_indicator(graph_view=self.wgt_graph.graph)
- @QtCore.Slot(str)
- def _on_action_triggered(self, action: str):
+ @QtCore.Slot(str, dict)
+ def _on_action_triggered(self, action: str, args: dict):
"""Callback on action triggered from the toolbar
:param action: The action to find and call
:type action: str
+ :param args: Args for the action
+ :type args: dict
"""
action_obj = utils.find_method(module=action, obj=self)
if action_obj:
- action_obj()
+ action_obj(**args)
+
+ @QtCore.Slot(str)
+ def set_sentiment_compagny(self, ticker):
+ """This method set the sentimental widget for
+ the select tick.
+ """
+ utils.clear_layout(self.financials_layout)
+ widget = Sentimental_Widget_Item()
+ widget.set_sentimental_ui(ticker=ticker)
+ self.financials_layout.addWidget(widget)
def resizeEvent(self, event):
if self.tickers_dialog:
diff --git a/preview_home.png b/preview_home.png
new file mode 100644
index 0000000..248c638
Binary files /dev/null and b/preview_home.png differ