Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add US IPOs #19

Merged
merged 1 commit into from
Feb 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion inc/data-sources.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <QTimer>

#include "data-sources/finnhub.hpp"
#include "data-sources/ipo-cal-appspot.hpp"
#include "ipo.hpp"
#include "mainwindow.hpp"
Expand All @@ -18,9 +19,12 @@ class DataSources : public QObject

public slots:
void queryJapaneseIpos();
void queryUsIpos();

private:
DataSourceIpoCalAppSpot *dataSourceJapan = new DataSourceIpoCalAppSpot();
DataSourceIpoCalAppSpot *dataSourceJapanIpos;
DataSourceFinnhub *dataSourceUsIpos;
MainWindow *parentObject;
QTimer *timer;
QTimer *timer2;
};
26 changes: 26 additions & 0 deletions inc/data-sources/finnhub.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrlQuery>

#include "ipo.hpp"

class DataSourceFinnhub : public QObject
{
Q_OBJECT

public:
explicit DataSourceFinnhub(QObject *parent = nullptr, QString apiKey = "");
~DataSourceFinnhub();

QList<Ipo> queryData();

private:
QString apiKey = "";
QString baseUrl = QString("https://finnhub.io/api/v1");
QNetworkAccessManager manager;
QUrlQuery query;
QNetworkReply *reply;
QUrl url = QUrl(baseUrl + "/calendar/ipo");
};
4 changes: 2 additions & 2 deletions inc/data-sources/ipo-cal-appspot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ class DataSourceIpoCalAppSpot : public QObject
Q_OBJECT

public:
explicit DataSourceIpoCalAppSpot(QObject *parent = 0);
explicit DataSourceIpoCalAppSpot(QObject *parent = nullptr);
~DataSourceIpoCalAppSpot();

QList<Ipo> query();
QList<Ipo> queryData();

private:
QString baseUrl = QString("https://ipo-cal.appspot.com/api");
Expand Down
1 change: 1 addition & 0 deletions inc/ipo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct Ipo {
QUrl company_website;
QDateTime expected_date;
QString region;
QString status; // "expected", "priced", "withdrawn", "filed"
QString stock_exchange;
QString ticker;
};
3 changes: 2 additions & 1 deletion inc/mainwindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class MainWindow : public QMainWindow
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
QList<Ipo> ipos;
QSettings *settings;

public slots:
void toggleHidden();
Expand All @@ -41,11 +42,11 @@ private slots:
private:
DataSources *dataSources;
QSystemTrayIcon *trayIcon;
QSettings *settings;
TrayMenu *trayMenu;
Ui::MainWindow *ui;

void bindShortcuts();
static bool compareDates(const Ipo &ipo1, const Ipo &ipo2);
QString formatDateCell(QString expectedDate);
QString formatWebsiteCell(QString websiteUrl);
void loadSettings();
Expand Down
2 changes: 2 additions & 0 deletions ipo-calendar.pro
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ SOURCES += src/main.cpp \
src/mainwindow.cpp \
src/traymenu.cpp \
src/runguard.cpp \
src/data-sources/finnhub.cpp \
src/data-sources/ipo-cal-appspot.cpp \
src/data-sources.cpp \

HEADERS += inc/mainwindow.hpp \
inc/traymenu.hpp \
inc/runguard.hpp \
inc/ipo.hpp \
inc/data-sources/finnhub.hpp \
inc/data-sources/ipo-cal-appspot.hpp \
inc/data-sources.hpp \

Expand Down
13 changes: 10 additions & 3 deletions res/styles/ipo-calendar.qss
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
/* List iew */
QTreeWidget {
background-color: #000;
color: white;
color: #fff;
}

/* Header */
QHeaderView::section {
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #616161, stop: 0.5 #505050,
stop: 0.6 #434343, stop: 1 #656565);
color: white;
padding-left: 4px;
border: 1px solid #6c6c6c;
color: #fff;
padding-left: 4px;
}

/* Cells */
QTreeWidget::item {
padding-right: 14px;
}
QTreeWidget::item::selected {
background-color: #013636;
}
QTreeView::item:selected:hover {
background-color: #024040;
}
QTreeView::item:hover {
background-color: #011919;
}


/* Scrollbar */
QScrollBar:vertical {
border: none;
Expand Down
38 changes: 36 additions & 2 deletions src/data-sources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,24 @@ DataSources::DataSources(QObject *parent) : QObject(parent)
{
parentObject = (MainWindow *)this->parent();

dataSourceJapanIpos = new DataSourceIpoCalAppSpot(this);
QString finnhubApiKey;
if (parentObject->settings->contains("Secrets/finnhubApiKey")) {
finnhubApiKey = parentObject->settings->value("Secrets/finnhubApiKey").toString();
}
dataSourceUsIpos = new DataSourceFinnhub(this, finnhubApiKey);

// Start-time requests
queryJapaneseIpos();
queryUsIpos();

// Consequitive requests
// Recurring requests
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(queryJapaneseIpos()));
timer->start(8 * HOUR_IN_MS);
timer2 = new QTimer(this);
connect(timer2, SIGNAL(timeout()), this, SLOT(queryUsIpos()));
timer->start(2 * HOUR_IN_MS);
}

DataSources::~DataSources()
Expand All @@ -24,7 +35,30 @@ DataSources::~DataSources()

void DataSources::queryJapaneseIpos()
{
QList<Ipo> retrievedIpos = dataSourceJapan->query();
QList<Ipo> retrievedIpos = dataSourceJapanIpos->queryData();

foreach (Ipo retrievedIpo, retrievedIpos) {
Ipo *existingIpo = nullptr;

foreach (Ipo ipo, parentObject->ipos) {
if (ipo.company_name == retrievedIpo.company_name) {
existingIpo = &ipo;
}
}

if (existingIpo) {
existingIpo = &retrievedIpo;
} else {
parentObject->ipos.append(retrievedIpo);
}
}

parentObject->updateList();
}

void DataSources::queryUsIpos()
{
QList<Ipo> retrievedIpos = dataSourceUsIpos->queryData();

foreach (Ipo retrievedIpo, retrievedIpos) {
Ipo *existingIpo = nullptr;
Expand Down
85 changes: 85 additions & 0 deletions src/data-sources/finnhub.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* IPO Calendar data source: US IPOs
* API Spec: https://finnhub.io/docs/api
*
*/

#include <QCoreApplication>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QNetworkRequest>
#include <QTextCodec>

#include "data-sources/finnhub.hpp"

DataSourceFinnhub::DataSourceFinnhub(QObject *parent, QString apiKey) : QObject(parent)
{
query.addQueryItem("from", "2021-01-01");
query.addQueryItem("to", "2021-06-01");
url.setQuery(query.query());
this->apiKey = apiKey;
}

DataSourceFinnhub::~DataSourceFinnhub()
{
delete reply;
}

QList<Ipo> DataSourceFinnhub::queryData()
{
QNetworkRequest request(url);
QList<Ipo> retrievedIpos;

if (apiKey.isEmpty()) {
return retrievedIpos;
}

request.setRawHeader("X-Finnhub-Token", apiKey.toUtf8());

reply = manager.get(request);

while (!reply->isFinished()) {
QCoreApplication::processEvents();
}

QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
int status = statusCode.toInt();

if (status == 200) {
QJsonParseError jsonParseError;
QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll(), &jsonParseError);
if (jsonParseError.error != QJsonParseError::NoError) {
return retrievedIpos;
}
QJsonObject jsonRoot = jsonDocument.object();
if (jsonRoot["ipoCalendar"] == QJsonValue::Undefined) {
return retrievedIpos;
}
QJsonArray dataArray = jsonRoot["ipoCalendar"].toArray();

foreach (const QJsonValue &item, dataArray) {
Ipo ipo;
QJsonObject ipoObj = item.toObject();

if (ipoObj["name"] == QJsonValue::Undefined) {
continue;
}

ipo.company_name = ipoObj["name"].toString();
ipo.company_website = QUrl("https://ddg.gg/?q=\\" + ipo.company_name);
ipo.expected_date = QDateTime::fromString(ipoObj["date"].toString(), "yyyy-MM-dd");
ipo.region = QString("North America (US)");
ipo.status = ipoObj["status"].toString();
ipo.stock_exchange = ipoObj["exchange"].toString();
ipo.ticker = ipoObj["symbol"].toString();

retrievedIpos.append(ipo);
}
}

reply->deleteLater();

return retrievedIpos;
}
20 changes: 9 additions & 11 deletions src/data-sources/ipo-cal-appspot.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/*
* IPO Calendar data source: Japanese IPOs
* Spec: https://ipo-cal.appspot.com/apispec.html
* API Spec: https://ipo-cal.appspot.com/apispec.html
*
*/

#include <QCoreApplication>
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
Expand All @@ -15,21 +14,20 @@

#include "data-sources/ipo-cal-appspot.hpp"

DataSourceIpoCalAppSpot::DataSourceIpoCalAppSpot(QObject *parent): QObject(parent)
DataSourceIpoCalAppSpot::DataSourceIpoCalAppSpot(QObject *parent) : QObject(parent)
{
}

DataSourceIpoCalAppSpot::~DataSourceIpoCalAppSpot()
{
delete reply;
// TODO: overwrite apiKey with zeroes before freeing its memory (for security reasons)
}

QList<Ipo> DataSourceIpoCalAppSpot::query()
QList<Ipo> DataSourceIpoCalAppSpot::queryData()
{
QNetworkRequest request(url);
QList<Ipo> retrieved_ipos;

// request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QList<Ipo> retrievedIpos;

reply = manager.get(request);

Expand All @@ -44,11 +42,11 @@ QList<Ipo> DataSourceIpoCalAppSpot::query()
QJsonParseError jsonParseError;
QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll(), &jsonParseError);
if (jsonParseError.error != QJsonParseError::NoError) {
return retrieved_ipos;
return retrievedIpos;
}
QJsonObject jsonRoot = jsonDocument.object();
if (jsonRoot["result"] == QJsonValue::Undefined) {
return retrieved_ipos;
return retrievedIpos;
}
QJsonArray dataArray = jsonRoot["data"].toArray();

Expand All @@ -67,11 +65,11 @@ QList<Ipo> DataSourceIpoCalAppSpot::query()
ipo.stock_exchange = QString("TSE (%1)").arg(ipoObj["market_key"].toString());
ipo.ticker = ipoObj["code"].toString();

retrieved_ipos.append(ipo);
retrievedIpos.append(ipo);
}
}

reply->deleteLater();

return retrieved_ipos;
return retrievedIpos;
}
7 changes: 7 additions & 0 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ MainWindow::~MainWindow()
delete ui;
}

bool MainWindow::compareDates(const Ipo &ipo1, const Ipo &ipo2)
{
return ipo1.expected_date > ipo2.expected_date;
}

QString MainWindow::formatDateCell(QString timestamp)
{
QString color = "white";
Expand Down Expand Up @@ -94,6 +99,8 @@ void MainWindow::updateList()
delete ui->treeWidget->takeTopLevelItem(0);
}

qSort(ipos.begin(), ipos.end(), compareDates);

foreach(Ipo ipo, ipos) {
QTreeWidgetItem *ipoItem = new QTreeWidgetItem(static_cast<QTreeWidget *>(nullptr));
ipoItem->setText(0, ipo.company_name);
Expand Down