本文总结一下以前参加的一个股票分析软件的子项目(仅放了部分代码),项目用QT实现的,股票历史数据存储在excel文件中。
子项目的目标是根据提供的股票历史数据查找与近期股票数据相似的时间段,并根据历史数据对走势进行预测。主要功能包括:
- (1)股票任意时间段数据的显示
- (2)相似历史数据的查找
- (3)股票未来走势的预测
- (4)更新股票数据
- (5)导入/存储相应的数据
下面分别对上面的功能做一下思路性的概括总结:
- 股票数据的显示
使用蜡烛图对股票数据进行显示,包含当股价、交易量、走势等,如下图所示:
/***************价格图(蜡烛图)*****************/
//数据链
QCandlestickSeries *acmeSeries = new QCandlestickSeries();
acmeSeries->setName( name );
acmeSeries->setIncreasingColor(QColor(Qt::red));
acmeSeries->setDecreasingColor(QColor(Qt::green));
//acmeSeries->setBodyOutlineVisible(false);
//acmeSeries->setBodyWidth(0.01);
//横轴显示的信息
QStringList categories;
for( i=0;i<stock_chart->date.size();i++ )
categories.append( stock_chart->date.at(i) );
//添加最近交易天数的数据
add_chart_set( stock_chart,acmeSeries );
//创建Chart
QChart *chart = new QChart();
chart->addSeries(acmeSeries);
//chart->setTitle( name );
chart->setAnimationOptions(QChart::SeriesAnimations);
chart->createDefaultAxes();
QBarCategoryAxis *axisX = qobject_cast<QBarCategoryAxis *>(chart->axes(Qt::Horizontal).at(0));
axisX->setCategories(categories);
axisX->setLabelsAngle( 90 );
axisX->setLabelsFont( QFont("Times",7) );
QValueAxis *axisY = qobject_cast<QValueAxis *>(chart->axes(Qt::Vertical).at(0));
axisY->setMax(axisY->max() * 1.01);
axisY->setMin(axisY->min() * 0.99);
chart->legend()->setVisible(false);
//chart->legend()->setAlignment(Qt::AlignBottom);
stock_chart->chart_view->setChart( chart );
/***************价格图(蜡烛图)*****************/
/***************交易量图*****************/
QBarSet *set0 = new QBarSet("Jane");
for( i=0;i<stock_chart->date.size();i++ )
*set0 << stock_chart->volumes.at(i).toDouble()/100000;
QBarSeries *series = new QBarSeries();
series->append(set0);
QChart *chart2 = new QChart();
chart2->addSeries(series);
//chart2->setTitle("Simple barchart example");
chart2->setAnimationOptions(QChart::SeriesAnimations);
chart2->createDefaultAxes();
QBarCategoryAxis *axisX2 = qobject_cast<QBarCategoryAxis *>(chart2->axes(Qt::Horizontal).at(0));
axisX2->setCategories(categories);
axisX2->setLabelsAngle( 90 );
axisX2->setLabelsFont( QFont("Times",7) );
axisX2->setLabelsVisible(false);
QValueAxis *axisY2 = qobject_cast<QValueAxis *>(chart2->axes(Qt::Vertical).at(0));
axisY2->setMax(axisY2->max() * 1.01);
axisY2->setMin(axisY2->min() * 0.99);
//axisY2->setLabelsVisible(false);
chart2->legend()->setVisible(false);
//chart2->legend()->setAlignment(Qt::AlignBottom);
stock_chart->chart_volume->setChart( chart2 );
/***************交易量图*****************/
(二)相似历史数据的查找
因为查找历史数据是比较费时的操作,所以需要创建一个子线程专门用来进行查找,并连接好控制信号,例如开始、结束等,当查找到符合条件的数据时发生search_resul信号,并发送到主线程中处理:
/******************创建子线程*****************/
child_thread = new QThread();
child_process = new ProcessObject();
child_process->moveToThread( child_thread );
connect( child_thread,SIGNAL(finished()),child_process,SLOT(deleteLater()) );
connect( child_thread,SIGNAL(finished()),child_thread,SLOT(deleteLater()) );
connect( this,SIGNAL(start_search(QString,MultString,ListDouble,bool)),
child_process,SLOT(reply_start_search(QString,MultString,ListDouble,bool)) );
connect( child_process,SIGNAL(search_result(QString,ListInt,ListDouble)),
this,SLOT(reply_search_result(QString,ListInt,ListDouble)) );
connect( this,SIGNAL(stop_search()),child_process ,SLOT(reply_stop_search()) );
child_thread->start();
查找算法使用了相似性算法,算法本身不难,主要问题是在如何使用数据。
double similarity(QList<double> data1,QList<double> data2)
{
if( data1.isEmpty() || data2.isEmpty() )
return 0;
if( data1.size() != data2.size() )
return 0;
if( data1.size() < 1 )
return 0;
int i;
int _size = data1.size();
double r1,r2,r3,avg1,avg2,var1,var2;
r1 = r2 = var1 = var2 = 0;
//平均值
for( i=0;i<_size;i++ )
{
r1 += data1[i];
r2 += data2[i];
}
avg1 = r1/_size;
avg2 = r2/_size;
//方差
r1 = r2 = 0;
for( i=0;i<_size;i++ )
{
r1 += ( data1[i] - avg1 )*( data1[i] - avg1 );
r2 += ( data2[i] - avg2 )*( data2[i] - avg2 );
}
var1 = 1/sqrt( r1/(_size -1) );
var2 = 1/sqrt( r2/(_size -1) );
//数据标准化
QList<double> std_data1,std_data2;
std_data1.clear();
std_data2.clear();
for( i=0;i<_size;i++ )
{
std_data1.append ( ( data1[i] - avg1)*var1 );
std_data2.append ( ( data2[i] - avg2)*var2 );
}
//计算相关性,consine
r1 = r2 = r3 = 0;
for( i=0;i<_size;i++ )
{
r1 = r1 + std_data1[i]*std_data2[i];
r2 = r2 + std_data1[i]*std_data1[i];
r3 = r3 + std_data2[i]*std_data2[i];
}
return (r1/sqrt(r2*r3));
}
下图是正在查找股票数据的界面,查找的结果包含开始位置,结束时间(excel中的下标),相似度,相似度可以自己调节:
(三)股票未来走势的预测
未来走势预测是根据在历史数据中查找到的时间段来计算的,因为历史数据已经得到了走势数据,所以预测还是比较比较简单,下图就是预测的图,红色表示预测的数据,绿色表示实际的数据(因为没有更新数据,所以实际数据没显示出来):
(四)更新股票数据
点击界面上的更新按钮即可更新股票数据。在新浪网获取股票数据,15点后可自动更新一次,网络部分使用QNetworkManager,发送request请求。
//获取名字,例如,003300.xls,获取到的是003300
QString _name = stock_names.takeFirst();
_name = _name.split('.').at(0).trimmed();
if (_name.split('.').isEmpty())
return;
//构造网址,注:更改datalen可以获取到更多时间的数据
//100相当于获取到半年的数据,这里暂时取50
//完整的:QString url = "http://money.finance.sina.com.cn/quotes_service/api/json_v2.php/CN_MarketData.getKLineData?symbol=sh000001&scale=240&ma=no&datalen=100";
QString url = "http://money.finance.sina.com.cn/quotes_service/api/json_v2.php/CN_MarketData.getKLineData?symbol=";
if( _name.at(0)=='0' )//0开头,深证
url = url + "sz";
else //其他情况应该是6开头了,上证
url = url + "sh";
url = url + _name;
url = url + "&scale=240&ma=no&datalen=50";
//发送网络请求(更新第一个股票,其他股票的更新参考reply_network_finished()
QNetworkRequest request;
request.setUrl( QUrl(url) );
QNetworkReply *reply = network_manager->get(request);
connect( reply,SIGNAL( finished() ),this,SLOT( reply_network_finished() ) );
(五)数据的存储
数据存储在json文件中。存储内容包含查找到的数据,预测数据等。
//源数据对应的时间
QJsonArray _src_days;
for (i = 0;i < info.src_day.size();i ++)
_src_days.append(info.src_day.at(i));
jsobject.insert("src_day",QJsonValue(_src_days));
//源数据对应的股价
QJsonArray _src_prices;
for (i = 0;i < info.src_price.size();i ++)
_src_prices.append(info.src_price.at(i));
jsobject.insert("src_price",QJsonValue(_src_prices));
//目标数据对应的时间
QJsonArray _dst_days;
for (i = 0;i < info.dst_day.size();i ++)
_dst_days.append(info.dst_day.at(i));
jsobject.insert("dst_day",QJsonValue(_dst_days));
//目标数据对应的股价
QJsonArray _dst_prices;
for (i = 0;i < info.dst_price.size();i ++)
_dst_prices.append(info.dst_price.at(i));
jsobject.insert("dst_price",QJsonValue(_dst_prices));