PDA

View Full Version : QT vs. Yahoo Finance - How to get a stock rate



cit
11th January 2015, 14:37
Hi,
for my little QT-project i want to write a function, which detect the current rate of stock.
Something like "double FinanceAPI::GetStockRate(QString stock_symbol)". The committed stock symbol represents the stock.

For these job i want to use the yahoo-finance-api. With a HTTP-request like...

http://finance.yahoo.com/d/quotes.csv?s=847402.DE&f=l1sn&ignore=.cvs

...in my webbrowser, i get a right answer. It is a CSV-file with the rate of stock "847402". My problem is how get these information with a qt-function.

This is what i try:





#include "financeapi.h"

FinanceAPI::FinanceAPI(QObject *parent) :
QObject(parent)
{

manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(replyFinished(QNetworkReply*)));

}


FinanceAPI::~FinanceAPI(){

delete manager;
}




void FinanceAPI::replyFinished(QNetworkReply *reply)
{
if(reply->error()!=QNetworkReply::NoError){
qDebug()<<"Fehlerhafte Anfrage\n";
}
else{


QByteArray bytes = reply->readAll();
QString answer = QString::fromUtf8(bytes);
QFile file("test.csv");
if(!file.open(QFile::WriteOnly))
{
qDebug()<<"test";
}

//file.write(*answer);
QTextStream out(&file);
out<<answer;
file.flush();
file.close();

}
}





double FinanceAPI::GetStockRate(QString stock_symbol){

QString tmpQuery="http://finance.yahoo.com/d/quotes.csv?s="+stock_symbol+"&f=l1sn&ignore=.cvs";//"http://ichart.finance.yahoo.com/table.csv?s="+stock_symbol+"&f=l1sn&ignore=.cvs";//
manager->get(QNetworkRequest(QUrl(tmpQuery)));

QNetworkRequest request;
request.setUrl(QUrl(tmpQuery));
manager->get(request);

return 0.0;
}





My call of the function looks like:




FinanceAPI *tmpFinance = new FinanceAPI();
double rate =tmpFinance->GetStockRate("847402.DE"));




Somethings goes wrong respectively i don't understand the working of the...


connect(manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(replyFinished(QNetworkReply*)));

Because I think the answer from the request (-> the rate) come not in the "
GetStockRate()", but rather "replyFinished()". But I needed at the end of the "GetStockRate()"-function to use it as return value.

Thanks for helping

anda_skoa
11th January 2015, 15:17
A web API request is asynchronous so you either deal with it asynchronously or you block until the asynchronous process has finished.

If you are in a GUI application, the last is usually not an option, as the program would stop responding to any user interaction.

Do you have a GUI program or is this a console application?

Cheers,
_

cit
11th January 2015, 19:21
yes, you are right. it is a "gui-program". I will use the function in two general case.

case one: to fill a textbox, asynchron is no problem

case two (most case): to use the value ( = the rate of the stock) for further calculation, asynchron will be a problem. I think in this case, i have to go synchron, maybe with little the help of threads to uncopple from the gui.

the problem is the same, how can i get the return value in the invoking function? Can you maybe optimize my code? ;-)

anda_skoa
12th January 2015, 08:37
the problem is the same, how can i get the return value in the invoking function?

You cannot do that without blocking.
This type of function does not work in a UI program.

You have two options:

1) asynchronous request/response, similar to how the QNetworkAccessManager requests work
2) local cache of values that gets updated asynchronously and reacting to those changes.

Cheers,
_

cit
12th January 2015, 17:57
Ok, then let us talk about option asynchon.How can i do that?

I think I have to give you more details over my architecture. I have different .h/.cpp for different main functionallies. We can focus on two of them.

first: gui.cpp
secound: financeAPI.cpp

Assuming that i want to calculate someting with this function in the gui


double GUI::CalcSomething(double stockRate)

So, i have to do something with GUI::CalcSomething() and the FinanceAPI::GetStockRate()
When i understand you right, then the following code is not good




...
CalcSomething(GetStockRate("StockNameABC"));
...


But can I say use the value of GetStockRate() to CalcSomething()? I normally work with VisualStudio, in these case I would use functionpointer/delegates.

Lesiok
12th January 2015, 18:26
I think that such a scenario would be a good :
1. Start GetStockRate with QTimer.
2. After receiving the data start CalcSomething with signal.

cit
12th January 2015, 19:20
And how i realise that? I think my functions from the first post are not working correct. Maybe you can look over it.




#include "financeapi.h"

FinanceAPI::FinanceAPI(QObject *parent) :
QObject(parent)
{

manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(replyFinished(QNetworkReply*)));

}


FinanceAPI::~FinanceAPI(){

delete manager;
}




void FinanceAPI::replyFinished(QNetworkReply *reply)
{
if(reply->error()!=QNetworkReply::NoError){
qDebug()<<"Fehlerhafte Anfrage\n";
}
else{


QByteArray bytes = reply->readAll();
QString answer = QString::fromUtf8(bytes);
QFile file("test.csv");
if(!file.open(QFile::WriteOnly))
{
qDebug()<<"test";
}

//file.write(*answer);
QTextStream out(&file);
out<<answer;
file.flush();
file.close();

}
}





double FinanceAPI::GetStockRate(QString stock_symbol){

QString tmpQuery="http://finance.yahoo.com/d/quotes.csv?s="+stock_symbol+"&f=l1sn&ignore=.cvs";//"http://ichart.finance.yahoo.com/table.csv?s="+stock_symbol+"&f=l1sn&ignore=.cvs";//
manager->get(QNetworkRequest(QUrl(tmpQuery)));

QNetworkRequest request;
request.setUrl(QUrl(tmpQuery));
manager->get(request);

return 0.0;
}

jefftee
13th January 2015, 01:33
And how i realise that?
One way you can wait for the asynchronous response is to use something similar to the code below. Google around for "QNetworkAccessManager synchronous" to learn the drawbacks of this approach.


QEventLoop loop;
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(tmpQuery)));
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
QByteArray bytes = reply->readAll();

anda_skoa
13th January 2015, 08:49
Ok, then let us talk about option asynchon.How can i do that?

Look at the API of QNetworkAccessManager, it uses such a call/response pattern.


Assuming that i want to calculate someting with this function in the gui


double GUI::CalcSomething(double stockRate)


You would call that from the reponse handler that delivers the result of your asynchronous call.



But can I say use the value of GetStockRate() to CalcSomething()? I normally work with VisualStudio, in these case I would use functionpointer/delegates.
I am not sure what the IDE has to do with what you can do in C++, but you can of course use VS to develop Qt applications.

Cheers,
_

jesse_mark
14th January 2015, 16:57
first in you code here you have send the request twice, was that intentially ??


]
double FinanceAPI::GetStockRate(QString stock_symbol){

QString tmpQuery="http://finance.yahoo.com/d/quotes.csv?s="+stock_symbol+"&f=l1sn&ignore=.cvs";//"http://ichart.finance.yahoo.com/table.csv?s="+stock_symbol+"&f=l1sn&ignore=.cvs";//

// first time
manager->get(QNetworkRequest(QUrl(tmpQuery)));


// 2nd time
QNetworkRequest request;
request.setUrl(QUrl(tmpQuery));
manager->get(request);



return 0.0;
}



since you must wait until the request finish and return you the reply, as well as you need to do some caluclation after getting your responce,
I would just call the calculatoin function from replyFinished(QNetworkReply*) slot.

cit
14th January 2015, 19:40
Okay yes, this could be from my experiment.

Can somebody look over the other function;





FinanceAPI::FinanceAPI(QObject *parent) :
QObject(parent)
{

manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(replyFinished(QNetworkReply*)));

}

I don't know how to get the information out of the CSV-file. Some idea's?

FinanceAPI::~FinanceAPI(){

delete manager;
}






void FinanceAPI::replyFinished(QNetworkReply *reply)
{
if(reply->error()!=QNetworkReply::NoError){
qDebug()<<"Fehlerhafte Anfrage\n";
}
else{


QByteArray bytes = reply->readAll();
QString answer = QString::fromUtf8(bytes);
QFile file("test.csv");
if(!file.open(QFile::WriteOnly))
{
qDebug()<<"test";
}

//file.write(*answer);
QTextStream out(&file);
out<<answer;
file.flush();
file.close();

}





TO jthomps: In Line 4 (loop.exec();) the programm will be wait till the answer is recived ?


TO anda_skoa: Can I develop QT-application with VisualStudio?

jefftee
14th January 2015, 22:53
TO jthomps: In Line 4 (loop.exec();) the programm will be wait till the answer is recived ?

Yes, in the example I gave, when the QNetworkReply::finished signal is fired, it executes the QEventLoop::quit slot causing the event loop to end. That's the purpose of the connect statement.

anda_skoa
15th January 2015, 09:08
I don't know how to get the information out of the CSV-file. Some idea's?

If you don't need the data inside a file you can directly process it.
QString::indexOf(), QString::mid(), QString::split() can be useful when dealing with CSV data.



TO jthomps: In Line 4 (loop.exec();) the programm will be wait till the answer is recived ?

Yes, but be aware that it is a crude hack that will have all kinds of consequences.
Use it if you don't want a clean solution.



TO anda_skoa: Can I develop QT-application with VisualStudio?

Yes.

Cheers,
_

cit
15th January 2015, 18:43
Thank you for your answers!!!! ;-)

Today I test something.





FinanceAPI::FinanceAPI(QObject *parent) :
QObject(parent)
{

manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(replyUpdateFinished(QNetworkReply*)));

}


FinanceAPI::~FinanceAPI(){

delete manager;
}




void FinanceAPI::replyUpdateFinished(QNetworkReply *reply)
{
if(reply->error()!=QNetworkReply::NoError){
qDebug()<<"Fehlerhafte Anfrage\n";
}
else{


QByteArray bytes = reply->readAll();
QString answer = QString::fromUtf8(bytes); //HERE I GET THE ERROR
QFile file("test.csv");
if(!file.open(QFile::WriteOnly))
{
qDebug()<<"test";
}
qDebug()<<"hat geklappt";

//file.write(*answer);
QTextStream out(&file);
out<<answer;
file.flush();
file.close();

}
}




void FinanceAPI::Update(QString stock_symbol){

QString tmpQuery="http://finance.yahoo.com/d/quotes.csv?s=847402.DE&f=l1sn&ignore=.cvs";
manager->get(QNetworkRequest(QUrl(tmpQuery)));

}








But I get as reply only these statement (found it in the variable "answer"):

<HTML>\n<HEAD>\n<TITLE>Error</TITLE>\n</HEAD>\n\n<BODY BGCOLOR="white" FGCOLOR="black">\n<!-- status code : 301 -->\n<!-- Error: GET -->\n<!-- host machine: yts274.global.media.ir2.yahoo.com -->\n<!-- timestamp: 1421343924.000 -->\n<!-- url: http://finance.yahoo.com/d/quotes.csv?s=847402.DE&f=l1sn&ignore=.cvs-->\n<H1>Error</H1>\n<HR>\n\n<FONT FACE="Helvetica,Arial"><B>\nDescription: Could not process this "GET" request.\n</B></FONT>\n<HR>\n</BODY>\n

Can somebody help? Maybe i have to make some settings before i send the query.

Added after 17 minutes:

When i try something like this:

http://http://stackoverflow.com/questions/22120117/qt-4-7-getting-csv-file-from-yahoo-finances

It works in my browser and i get a CSV-File. But in QT I get an error-reply like this:

"<!doctype html public "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head><title>Yahoo! - 404 Not Found</title><style>
/* nn4 hide */
/*/*/
body {font:small/1.2em arial,helvetica,clean,sans-serif;font:x-small;text-align:center;}table {font-size:inherit;font:x-small;}
html>body {font:83%/1.2em arial,helvetica,clean,sans-serif;}input {font-size:100%;vertical-align:middle;}p, form {margin:0;padding:0;}
p {padding-bottom:6px;margin-bottom:10px;}#doc {width:48.5em;margin:0 auto;border:1px solid #fff;text-align:center;}#ygma {text-align:right;margin-bottom:53px}
#ygma img {float:left;}#ygma div {border-bottom:1px solid #ccc;padding-bottom:8px;margin-left:152px;}#bd {clear:both;text-align:left;width:75%;margin:0 auto 20px;}
h1 {font-size:135%;text-align:center;margin:0 0 15px;}legend {display:none;}fieldset {border:0 solid #fff;padding:.8em 0 .8em 4.5em;}
form {position:relative;background:#eee;margin-bottom:15px;border:1px solid #ccc;border-width:1px 0;}
#s1p {width:15em;margin-right:.1em;}
form span {position:absolute;left:70%;top:.8em;}form a {font:78%/1.2em arial;display:block;padding-left:.8em;white-space:nowrap;background: url(http://l.yimg.com/a/i/s/bullet.gif) no-repeat left center;}
form .sep {display:none;}.more {text-align:center;}#ft {padding-top:10px;border-top:1px solid #999;}#ft p {text-align:center;font:78% arial;}
/* end nn4 hide */
</style></head>
<body><div id="doc">
<div id="ygma"><a href="http://us.rd.yahoo.com/404/*http://www.yahoo.com"><img
src=http://l.yimg.com/a/i/yahoo.gif
width=147 height=31 border=0 alt="Yahoo!"></a><div><a
href="http://us.rd.yahoo.com/404/*http://www.yahoo.com">Yahoo!</a>
- <a href="http://us.rd.yahoo.com/404/*http://help.yahoo.com">Help</a></div></div>
<div id="bd"><h1>Sorry, the page you requested was not found.</h1>
<p>Please check the URL for proper spelling and capitalization. If
you're having trouble locating a destination on Yahoo!, try visiting the
<strong><a
href="http://us.rd.yahoo.com/404/*http://www.yahoo.com">Yahoo! home
page</a></strong> or look through a list of <strong><a
href="http://us.rd.yahoo.com/404/*http://docs.yahoo.com/docs/family/more/">Yahoo!'s
online services</a></strong>. Also, you may find what you're looking for
if you try searching below.</p>
<form name="s1" action="http://us.rd.yahoo.com/404/*-http://search.yahoo.com/search"><fieldset>
<legend><label for="s1p">Search the Web</label></legend>
<input type="text" size=30 name="p" id="s1p" title="enter search terms here">
<input type="submit" value="Search">
<span><a href="http://us.rd.yahoo.com/404/*http://search.yahoo.com/search/options?p=">advanced search</a> <span class=sep>|</span> <a href="http://us.rd.yahoo.com/404/*http://buzz.yahoo.com">most popular</a></span>
</fieldset></form>
<p class="more">Please try <strong><a
href="http://us.rd.yahoo.com/404/*http://help.yahoo.com">Yahoo!
Help Central</a></strong> if you need more assistance.</p>
</div><div id="ft"><p>Copyright &copy; 2015 Yahoo! Inc.
All rights reserved. <a
href="http://us.rd.yahoo.com/404/*http://privacy.yahoo.com">Privacy
Policy</a> - <a
href="http://us.rd.yahoo.com/404/*http://docs.yahoo.com/info/terms/">Terms
of Service</a></p></div>
</div></body></html>
"

Added after 21 minutes:

The second error message is maybe my fault.

Added after 4 minutes:

Now i tried a totally different csv-download from an other webpage. And it's worked fine. :) :( Whats wrong with yahoo????

jefftee
16th January 2015, 03:06
When using firefox web console and entering the URL:

http://finance.yahoo.com/d/quotes.csv?s=847402.DE&f=l1sn&ignore=.cvs

I get an HTTP/1.1 301 Redirect response, which is a permanent redirect. The Location header specifies the relocated URL is:

http://download.finance.yahoo.com/d/quotes.csv?s=847402.DE&f=l1sn&ignore=.cvs

Unless I am mistaken, you must handle redirects yourself. Look at QNetworkReply::attribute to check for the following attributes pertinent for a redirected URL:



QNetworkRequest::HttpStatusCodeAttribute - for the HTTP status code i.e. 301
QNetworkRequest::RedirectionTargetAttribute - for the redirected URL


Hope that helps.

cit
16th January 2015, 09:01
uffff...redirect...manually? That sounds not easy.Or?

Or can I use the second link direct? Or is the second link a "dynamic" link and always another link?

jefftee
16th January 2015, 16:22
uffff...redirect...manually? That sounds not easy.Or?

Or can I use the second link direct? Or is the second link a "dynamic" link and always another link?
The redirected URL is the permanent location for the redirect, since the HTTP return code is 301, meaning it has moved permanently. You should be able to use it instead of your original URL.

As with other programming best practices, you should check return codes and not just assume everything works. The HTTP status code is the return code for the HTTP request that you made and you are currently ignoring it and assuming you will receive the data you expect, etc. If you were checking your HTTP status codes in your finished slot, you would have seen right away that the HTTP status of your request was HTTP 301.

To your point about having to handle the redirect manually, I don't believe handling this is a difficult task. I already gave you the two attributes you need to use to detect an HTTP 301 status code and get the redirected URL target. All you would need to do it issue another get request for that redirected URL target.

cit
16th January 2015, 19:10
I hope you are little bit pride of me ;-)




void FinanceAPI::replyUpdateFinished(QNetworkReply *reply)
{

if(reply->error())
{
qDebug() << "ERROR!";
qDebug() << reply->errorString();
}
else
{
qDebug() << reply->header(QNetworkRequest::ContentTypeHeader).toStrin g();
qDebug() << reply->header(QNetworkRequest::LastModifiedHeader).toDate Time().toString();
qDebug() << reply->header(QNetworkRequest::ContentLengthHeader).toULo ngLong();
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute ).toInt();
qDebug() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribu te).toString();
qDebug() << reply->attribute(QNetworkRequest::RedirectionTargetAttrib ute).toString();


//Redirect? -> Cancel this reply and send new request with new URL
if(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute ).toInt()==301)
{
QNetworkRequest request;
request.setUrl(QUrl(reply->attribute(QNetworkRequest::RedirectionTargetAttrib ute).toString()));
manager->get(request);
return;
}


QFile *file = new QFile("downloaded.txt");
if(file->open(QFile::Append))
{
file->write(reply->readAll());
file->flush();
file->close();
}
delete file;
}

reply->deleteLater();
}



For the problem with the synchron/asynchron. I think i will use a asynchron update-function for full update on application start and store all information in a table of the database. And later, when i need some data, i pick the data synchron from the database. In my opinion, it make more sense und is easier for programming ;-)

Thanks to all. For the help!

jefftee
16th January 2015, 20:30
Better, but could be improved... :)

You are blindly writing out the data you receive without validating the data is in the correct format and something that you can consume. You have have other code that validates the data before you use it, but why write out potentially bogus data without validating it first?

Good luck.

Edit: I edited reply after realizing you are checking for an error, but data validation comment is still valid IMHO.