- 浏览: 3002 次
- 性别:
- 来自: 成都
最近访客 更多访客>>
最新评论
-
norry2004:
用了一下,还是可以的,至少是免费的云服务器。建了一个站:xzy ...
免费好用的web应用托管平台 -
norry2004:
很好的服务哦!
免费好用的web应用托管平台
VC炒股软件开发
文档说明:
此文档适合VC++的初学者,高手也可参考(希望能提出宝贵意见)。
开发前准备:
这是一个根据股票信息的数据绘的几个制界面,数据来源通信达软件的数据文件,通常在安装了通信达以后并下载数据到本地以后就有了。
1.数据文件的准备:
假如你安装通信达的目录是:D:\jcb_gx。那么对应的数据文件就在D:\jcb_gx\vipdoc\目录下,里面每一个目录下就是一类股票的数据,我们开发这个界面需要用到的是每个目录下的lday目录下的.lday后缀名的文件。每一个文件里面存放的是一支滚票的数据信息。我当时开发用到了两类股票的信息,分别对应的目录是:D:\jcb_gx\vipdoc\sh\lday和D:\jcb_gx\vipdoc\sz\lday。其实每类开发的方法完全一样,唯一不同就是读取不同的目录而已。
2.文件数据结构:
准备好数据以后,还有一点是必须知道的,不然也没有办法进行下去,就是文件里面的数据格式是怎样的。因为我用的是通信达的数据文件,所以只需要在www.g.cn查询通信达的数据格式就可以了,如下:
view plaincopy to clipboard
01.typedef struct
02.{ //共32字节
03. int date; //4字节 如20091229
04. int open; //开盘价
05. int high; //最高价
06. int low; //最低价
07. int close; //收盘价
08. float amount; //成交额
09. int vol; //成交量
10. int reservation; //保留值
11.} StockData;
详细开发过程(包括我的思路和具体实现):
1.实现如上图的界面,需要做如下事情
(1)读一个目录下的所有文件,并从文件名中提取出相应股票的代码
可行性分析:
首先我们打开的是一个目录,然后从这个目录中读出里面所有的文件名,目录存放的内容其实就是此目录下的文件名或目录名。用到两个函数,一个FindFirstFile查找到一个目录下的第一个文件名,另一个FindNextFile查找下一个文件名。这样就可以遍历一个目录下的所有文件名了。
具体实现:
view plaincopy to clipboard
01.BOOLCTongXinDaView::ReadFileData(LPCSTR path)
02.{
03. //path是这种形式的参数:D:\\jcb_gx\\vipdoc\\sh\\lday\\*
04. m_iCount = 0;
05. WIN32_FIND_DATA tFind = {0};
06. int i = 0;
07. CString strTemp;
08.
09. HANDLE hSearch = ::FindFirstFile(path, &tFind);
10. if (hSearch == INVALID_HANDLE_VALUE)
11. {
12. return FALSE;
13. }
14.
15. //过滤掉.和..文件
16. ::FindNextFile(hSearch, &tFind);
17. while (::FindNextFile(hSearch, &tFind))
18. {
19. strTemp.Format("%s",tFind.cFileName);
20. m_File[i].Format("%s",path);
21. //去掉查询用到的*通配符
22. m_File[i] =m_File[i].Left(m_File[i].GetLength()-1);
23. m_File[i] +=strTemp;
24. //从文件名中提取股票代码
25. m_FileName[i] =strTemp.Mid(2, 6);
26. i++;
27. }
28.
29. m_iPageCount = i / 31 + 1; //求出需要显示的总页面数
30. m_iLeave = i % 31; //最后一页显示的数据
31. m_CurrFile = m_File[m_iCount]; //保存选中的文件名
32. ::FindClose(hSearch);
33. return TRUE;
34.}
注意事项:每一个目录下都有这两个目录文件:“.”和“..”。它们分别代表本目录和父目录(就是上层目录),必须过滤掉这两个目录文件。还好每次这两个目录文件总是最先被读出,所以前两次读出来的信息直接不管就可以了。
上面的函数被相应的每一个菜单项事件调用,就是针对不同的股票用一个菜单项打开。
(2)页面的显示:
可行性分析:
先说说我当时需要完成的现实任务,每页显示31行(具体可以变动,但是31 行效果比较好),显示3列,第一列索引号,也就是起个计数的作用,第二列就是刚才我们提取到的股票代码号,第三列随便填充4个汉字。还要求画一条线表示当前选中的股票,鼠标上下滚动和PageDown,PageUp按键实现上下翻页功能,鼠标点击选中点击最近的一支股票,按键上下键也可以移动股票选择。明白了需要实现的功能,我现在就一步一步来完成。这里需要用到文字输出函数DrawText。
具体实现:
view plaincopy to clipboard
01.void CTongXinDaView::DrawText(CDC *pDC, int page)
02.{
03. CRect rt;
04. GetClientRect(&rt);
05. int high = rt.Width() / 55;
06. int y = high;//控制每一行显示数据的增量
07.
08. CString strLine;
09. int number = 1;
10. pDC->SetBkMode(TRANSPARENT);
11. pDC->SetTextColor(RGB(200, 200, 200));
12. strLine.Format(" 代码 名称 日期 开牌价 最高价 最低价 收盘价 多多 成交量 ");
13. pDC->TextOut(0, 0, strLine);
14. //控制最后一页只显示剩余的
15. if (page == m_iPageCount-1)
16. {
17. for (int i=page * SCREENHEIGHT; i<(page*SCREENHEIGHT + m_iLeave); i++)
18. {
19. if (i == m_iCount)
20. {
21. CPen pen(PS_SOLID, 1, RGB(255, 255, 0));
22. CPen *pOldPen = pDC->SelectObject(&pen);
23. pDC->MoveTo(50, y+high-5);
24. pDC->LineTo(rt.right, y+high-5);
25. pDC->SelectObject(pOldPen);
26. }
27. strLine.Format("%d", i+1);
28. pDC->DrawText(strLine, CRect(0, y, 40, y+high), DT_RIGHT);
29. pDC->DrawText(m_FileName[i], CRect(50, y, 100, y+high), DT_LEFT);
30. pDC->DrawText("长城开发", CRect(110, y, 180, y+high), DT_LEFT);
31. y = y + high;
32. }
33. }
34. else
35. {
36. for (int i=page * SCREENHEIGHT; i<(page+1)*SCREENHEIGHT; i++)
37. {
38. if (i == m_iCount)
39. {
40. CPen pen(PS_SOLID, 1, RGB(255, 255, 0));
41. CPen *pOldPen = pDC->SelectObject(&pen);
42. pDC->MoveTo(50, y+high-7);
43. pDC->LineTo(rt.right, y+high-7);
44. pDC->SelectObject(pOldPen);
45. }
46. strLine.Format("%d", i+1);
47. pDC->DrawText(strLine, CRect(0, y, 40, y+high), DT_RIGHT);
48. pDC->DrawText(m_FileName[i], CRect(50, y, 100, y+high), DT_LEFT);
49. pDC->DrawText("长城开发", CRect(110, y, 180, y+high), DT_LEFT);
50. y = y + high;
51. }
52. }
53.}
注意事项:
1.最后一页数据条目不够,需要特殊处理。
2.输出函数用的是DawText而不是TextOut,是为了使输出对齐。
3.鼠标和按键的响应只是简单逻辑处理和显示不同的数据。
(3)分页显示和选取当前一支股票的实现思想说明:
可行性分析:
上面已经实现了页面的显示,现在说说怎样控制上下翻页和鼠标键盘实现选中一支股票(我是用一条黄色的线标示)。上下翻页时通过键盘上的PageDown和PageUp,还有鼠标滚轮控制的。其实原理很简单,只需要我们在读目录下每个股票文件时记录一下这个目录下的股票数量,也就是我们需要显示的所有行数。我们一个常量记录每一页显示的数目,用总数除以这个数就是总共需要的页数。然后用一个变量记录当前显示的是第几页,上下翻页就是对这个变量的加减操作了。选中一支股票则是根据我们点击的鼠标的位置来决定,因为每一行所占的页面宽度是一样的,只需要判断点击在哪一行所处的位置就可以了。当然也需要用变量记录选中的是那一只股票,总数刚才我们也记录了,所以很容易记录当前的哪一只股票,只是需要注意翻页后选择的股票相应的加减一页的显示的股票数。最后一点就是注意一些边界条件的处理。
2.实现如下图的界面:
(1)读取选中的股票文件,并保存以为绘图使用这些数据
可行性分析:
按照固定的数据格式把文件中的数据读入到一个结构体中保存,用fread每次读入固定长度的数据格式接可以了。
具体实现:
view plaincopy to clipboard
01./*
02.* 函数名称: ReadData
03.* 输 入:
04.* 输 出:
05.* 功能描述: 从当前文件中读取数据
06.* 全局变量:
07.* 作 者: 吴友强
08.* 日 期: 2009年11月29日
09.* 修 改:
10.* 日 期:
11.*/
12.void CTongXinDaView::ReadData()
13.{
14. FILE *fp;
15. m_iDataItemCount = -1;
16. //打开当前文件
17. if ((fp = fopen(m_CurrFile, "rb")) == NULL)
18. {
19. return ;
20. }
21.
22. while (!feof(fp))
23. {
24. m_iDataItemCount++;
25. fread(&m_StockData[m_iDataItemCount],sizeof(StockData),1,fp);
26. //求最大的日期
27. if (m_StockData[m_iDataItemCount].date > m_iMaxDate)
28. {
29. m_iMaxDate = m_StockData[m_iDataItemCount].date;
30. }
31. //求最小的日期
32. if (m_iDataItemCount == 0)
33. {
34. m_iMinDate = m_StockData[m_iDataItemCount].date;
35. }
36. else if (m_StockData[m_iDataItemCount].date > 0
37.&& m_StockData[m_iDataItemCount].date < m_iMinDate)
38. {
39. m_iMinDate = m_StockData[m_iDataItemCount].date;
40. }
41. }
42.
43. m_iDataItemCount--;//去掉最后一条无用的记录
44. m_iStartDay = m_iDataItemCount;
45. if (m_iDays > m_iDataItemCount)
46. {
47. m_iDays = m_iDataItemCount+1;
48. }
49.//以前在这里没有关闭文件,所以当打开一定数量的时候(windows限制的)在打开文件就会失败
50. fclose(fp);
51.}
(2)提取当前需要显示的数据:
可行性分析:
根据当前需要显示多少天的数据来提取,从上面我们从文件里面读取的数据中提取,以后的所谓放大缩小,左右移动就是提取不同天的数据就是了。
具体实现:
view plaincopy to clipboard
01./*
02.* 函数名称: GetStockData
03.* 输 入: days
04.* days: 屏幕需要显示的天数
05.* 输 出:
06.* 功能描述: 得到显示的数据和求取各个最值
07.* 全局变量:
08.* 作 者: 吴友强
09.* 日 期: 2009年11月30日
10.* 修 改:
11.* 日 期:
12.*/
13.void CTongXinDaView::GetStockData(int days)
14.{
15. m_iLowMax = 0;
16. m_iHighMax = 0;
17. m_iVolMax = 0;
18. if (m_sdCurrData != NULL)
19. {
20. delete m_sdCurrData;
21. }
22.
23. m_sdCurrData = new StockData[days];
24. for (int i=0; i<days; i++)
25. {
26. m_sdCurrData[days-i-1] = m_StockData[m_iStartDay-i-1];
27.
28. //求本次显示成交量的最大值
29. if (m_sdCurrData[days-i-1].vol > m_iVolMax)
30. {
31. m_iVolMax = m_sdCurrData[days-i-1].vol;
32. }
33. //求本次显示最高值的最大值
34. if (m_sdCurrData[days-i-1].high > m_iHighMax)
35. {
36. m_iHighMax = m_sdCurrData[days-i-1].high;
37. }
38. //求本次显示最低值的最小值
39. if (i == 0)
40. {
41. m_iLowMax = m_sdCurrData[days-i-1].low;
42. }
43. else if (m_sdCurrData[days-i-1].low < m_iLowMax)
44. {
45. m_iLowMax = m_sdCurrData[days-i-1].low;
46. }
47. }
48.}
(3)得到需要显示的数据以后,我们就可以开始绘图了
可行性分析:
我们得到需要显示的数据以后,就需要根据当前显示的宽度和高度来划分屏幕了,根据客户的需求大致需要把屏幕分为上中下三部分,第一部分画成交量的平均值线和一天中的最高值到最低值的一条竖线,还有开盘价和收盘价的矩形图;第二部分成交总量;第三部分成交价格除以2,3,4,5刻度控制在-4到4资料的线形图。首先我们必须确定三部分的高度,然后把3个坐标固定下来并绘画出来,至于坐标的刻度我们可以动态的根据每次需要显示的数据的最大值和最小值来计算确定,然后根据刻度的比例来画所有的图形。其中很多需要计算,具体的情看代码。下面是整个系统的黑心部分,具体请看代码注释。
具体实现:
view plaincopy to clipboard
01./*
02.* 函数名称: DrawGraphic
03.* 输 入: pDC, days
04.* pDC: 画图的CDC指针
05.* days: 显示数据的天数
06.* 输 出:
07.* 功能描述: 画各种坐标以及图形
08.* 全局变量:
09.* 作 者: 吴友强
10.* 日 期: 2009年11月29日
11.* 修 改: 吴友强
12.* 日 期: 2009年12月4日
13.*/
14.void CTongXinDaView::DrawGraphic(CDC *pDC, int days)
15.{
16.//设置透明绘图模式
17. pDC->SetBkMode(TRANSPARENT);
18. pDC->SetTextColor(RGB(200, 0 ,0));
19.
20. CRect rt;
21. GetClientRect(&rt);
22. float average;
23. float averPri;
24. float ftemp, ftext;
25. CString str;
26. int i = 0;
27. float xAver;
28.
29. pDC->DrawText("当前代码: " + m_FileName[m_iCount], CRect(0, 0, 200, 20), DT_LEFT);
30. //初始化各个坐标原点
31. m_ptOrigin[0].x = rt.Width()-100;
32. m_ptOrigin[0].y = rt.Height()/8 * 3;
33. m_ptOrigin[1].x = rt.Width()-100;
34. m_ptOrigin[1].y = rt.Height()/4 * 2;
35. m_ptOrigin[2].x = rt.Width()-100;
36. m_ptOrigin[2].y = rt.Height()/4 * 3;
37.
38.//划分屏幕为三部分
39. CPen penRedSolid(PS_SOLID, 1, RGB(200, 0, 0));
40. CPen *pOldPen = pDC->SelectObject(&penRedSolid);
41. pDC->MoveTo(0, m_ptOrigin[0].y);
42. pDC->LineTo(rt.Width(), m_ptOrigin[0].y);
43. pDC->MoveTo(0, m_ptOrigin[1].y);
44. pDC->LineTo(rt.Width(), m_ptOrigin[1].y);
45. pDC->MoveTo(0, m_ptOrigin[2].y);
46. pDC->LineTo(rt.Width()-100, m_ptOrigin[2].y);
47. pDC->MoveTo(rt.Width()-100, 0);
48. pDC->LineTo(rt.Width()-100, rt.Height());
49.
50. //每一天显示的宽度xAver
51. xAver = (rt.Width()-100) / (float)days;
52. //平均刻度代表的价格
53. averPri = (m_iHighMax-m_iLowMax) / 5.0 / 100;
54. //第一条价格起始线
55. ftemp = m_iLowMax/ 100.0;
56. //刻度线的距离
57. average = m_ptOrigin[0].y / 6.0;
58. CPen penRedDot(PS_DOT, 1, RGB(200, 0, 0));
59.
60. //画K线坐标
61. for (i=0; i<5; i++)
62. {
63. pDC->SelectObject(&penRedDot);
64. pDC->MoveTo(0, average*(i+1));
65. pDC->LineTo(rt.Width()-100, average*(i+1));
66. pDC->SelectObject(&penRedSolid);
67. pDC->LineTo(rt.Width()-100+10, average*(i+1));
68. pDC->MoveTo(rt.Width()-100, average*(i+1) + average/2);
69. pDC->LineTo(rt.Width()-100+5, average*(i+1) + average/2);
70. pDC->MoveTo(rt.Width()-100, average*(i+1) + average/4);
71. pDC->LineTo(rt.Width()-100+3, average*(i+1) + average/4);
72. pDC->MoveTo(rt.Width()-100, average*(i+1) + average/4*3);
73. pDC->LineTo(rt.Width()-100+3, average*(i+1) + average/4*3);
74.
75. str.Format("%10.2f", ftemp + averPri * (5-i));
76. pDC->DrawText(str, CRect(rt.Width()-100, average*(i+1)-7, rt.Width(), average*(i+1)+10), DT_LEFT);
77. }
78.
79. CPen penGreen(PS_SOLID, 1, RGB(0, 200, 0));
80. CBrush brush(NULL_BRUSH);
81. CBrush brushGreen(RGB(0, 200, 0));
82. CBrush *pOldBrush = pDC->SelectObject(&brush);
83. //画每天最低到最高的线,开盘和收盘的矩形
84. for (i=0; i<days; i++)
85. {
86. if (m_sdCurrData[i].open <= m_sdCurrData[i].close)
87. {
88. pDC->SelectObject(&penRedSolid);
89. pDC->SelectObject(&brush);
90. pDC->MoveTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].high/100.0-ftemp));
91. pDC->LineTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].low/100.0-ftemp));
92. pDC->Rectangle(xAver*i, average*5-average/averPri*(m_sdCurrData[i].open/100.0- ftemp),
93. xAver*i+xAver/3*2, average*5-average/averPri*(m_sdCurrData[i].close/100.0- ftemp));
94. }
95. else
96. {
97. pDC->SelectObject(&penGreen);
98. pDC->SelectObject(&brushGreen);
99. pDC->MoveTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].high/100.0-ftemp));
100. pDC->LineTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].low/100.0-ftemp));
101. pDC->Rectangle(xAver*i, average*5-average/averPri*(m_sdCurrData[i].close/100.0- ftemp),
102. xAver*i+xAver/3*2, average*5-average/averPri*(m_sdCurrData[i].open/100.0- ftemp));
103. }
104.
105. if (!m_bMouseMove)
106. {
107. if (m_iDrawCount == i)
108. {
109. m_ptSavePoint.x = xAver * i+xAver/3;
110. m_ptSavePoint.y = average*5-average/averPri*(m_sdCurrData[i].open/100.0-ftemp);
111. }
112. }
113. }
114.
115. //计算5日平均值和10日平均值
116. int *fiveAverData = new int[days];
117. int *tenAverData = new int[days];
118. for (i=0; i<days; i++)
119. {
120. if (i >= 4)
121. {
122. fiveAverData[i] = (m_sdCurrData[i].close + m_sdCurrData[i-1].close +m_sdCurrData[i-2].close +
123. m_sdCurrData[i-3].close + m_sdCurrData[i-4].close) / 5;
124. }
125. else
126. {
127. fiveAverData[i] = m_sdCurrData[i].close * 5 / 5;
128. }
129.
130. if (i >= 9)
131. {
132. tenAverData[i] = (m_sdCurrData[i].close + m_sdCurrData[i-1].close +m_sdCurrData[i-2].close +
133. m_sdCurrData[i-3].close + m_sdCurrData[i-4].close + m_sdCurrData[i-5].close +
134. m_sdCurrData[i-6].close +m_sdCurrData[i-7].close + m_sdCurrData[i-8].close +
135. m_sdCurrData[i-9].close) / 10;
136. }
137. else
138. {
139. tenAverData[i] = m_sdCurrData[i].close * 10 / 10;
140. }
141. }
142.
143. //画5日均线和10日均线
144. CPen penWhite(PS_SOLID, 1, RGB(200, 200, 200));
145. CPen penYellow(PS_SOLID, 1, RGB(200, 200, 0));
146. for (i=1; i<days; i++)
147. {
148. pDC->SelectObject(&penWhite);
149. pDC->MoveTo(xAver * (i-1)+xAver/3, average*5-average/averPri*(fiveAverData[i-1]/100.0-ftemp));
150. pDC->LineTo(xAver * i+xAver/3, average*5-average/averPri*(fiveAverData[i]/100.0-ftemp));
151.
152. pDC->SelectObject(&penYellow);
153. pDC->MoveTo(xAver * (i -1)+xAver/3, average*5-average/averPri*(tenAverData[i-1]/100.0-ftemp));
154. pDC->LineTo(xAver * i+xAver/3, average*5-average/averPri*(tenAverData[i]/100.0-ftemp));
155. }
156.
157. //画柱状成交量的坐标和柱状图
158. average = (m_ptOrigin[1].y - m_ptOrigin[0].y) / 5;
159. float averVol = m_iVolMax / 4.0;
160. int temp = m_iVolMax / 4 / 100000 * 1000;//显示刻度用的临时变量
161.
162. pDC->SelectObject(&penRedSolid);
163. pDC->SelectObject(&brush);
164. str.Format("%s", " X100");
165. pDC->Rectangle(rt.Width()-100, m_ptOrigin[1].y-10, rt.Width()-50, m_ptOrigin[1].y+5);
166. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[1].y-10, rt.Width(), m_ptOrigin[1].y+5), DT_LEFT);
167. for (i=0; i<4; i++)
168. {
169. pDC->SelectObject(&penRedDot);
170. pDC->MoveTo(0, m_ptOrigin[0].y + average*(i+1));
171. pDC->LineTo(rt.Width()-100, m_ptOrigin[0].y + average*(i+1));
172. pDC->SelectObject(&penRedSolid);
173. pDC->LineTo(rt.Width()-100+10, m_ptOrigin[0].y + average*(i+1));
174. pDC->MoveTo(rt.Width()-100, m_ptOrigin[0].y + average*(i+1) + average / 2);
175. pDC->LineTo(rt.Width()-100+5, m_ptOrigin[0].y + average*(i+1) + average / 2);
176.
177. str.Format("%10d", temp * (4-i));
178. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[0].y + average*(i+1)-7, rt.Width(),
179. m_ptOrigin[0].y + average*(i+1)+10), DT_LEFT);
180. }
181.
182. for (i=0; i<days; i++)
183. {
184. if (m_sdCurrData[i].open <= m_sdCurrData[i].close)
185. {
186. pDC->SelectObject(&penRedSolid);
187. pDC->SelectObject(&brush);
188. }
189. else
190. {
191. pDC->SelectObject(&penGreen);
192. pDC->SelectObject(&brushGreen);
193. }
194. pDC->Rectangle(xAver*i, m_ptOrigin[1].y - m_sdCurrData[i].vol / averVol * average,
195. xAver*i+xAver/3*2, m_ptOrigin[1].y);
196. }
197.
198. //1.画收盘价四色线的刻度值
199. average = (m_ptOrigin[2].y - m_ptOrigin[1].y) / 5;
200. str.Format("%8d", 0);
201. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[2].y-7, rt.Width(), m_ptOrigin[2].y+10), DT_LEFT);
202. for (i=0; i<4; i++)
203. {
204. pDC->SelectObject(&penRedDot);
205. pDC->MoveTo(0, m_ptOrigin[1].y + average*(i+1));
206. pDC->LineTo(rt.Width()-100, m_ptOrigin[1].y + average*(i+1));
207.
208. pDC->SelectObject(&penRedSolid);
209. pDC->LineTo(rt.Width()-100+10, m_ptOrigin[1].y + average*(i+1));
210. for (int j=0; j<10; j++)
211. {
212. pDC->MoveTo(rt.Width()-100, m_ptOrigin[1].y + average*(i+1) + average / 10 *(j+1));
213. pDC->LineTo(rt.Width()-100+5, m_ptOrigin[1].y + average*(i+1) + average / 10*(j+1));
214. }
215.
216.
217. pDC->SelectObject(&penRedDot);
218. pDC->MoveTo(0, m_ptOrigin[2].y + average*(i+1));
219. pDC->LineTo(rt.Width()-100, m_ptOrigin[2].y + average*(i+1));
220.
221. pDC->SelectObject(&penRedSolid);
222. pDC->LineTo(rt.Width()-100+10, m_ptOrigin[2].y + average*(i+1));
223. for (j=0; j<10; j++)
224. {
225. pDC->MoveTo(rt.Width()-100, m_ptOrigin[2].y + average*i + average / 10 *(j+1));
226. pDC->LineTo(rt.Width()-100+5, m_ptOrigin[2].y + average*i + average / 10*(j+1));
227. }
228.
229. str.Format("%8d", 4-i);
230. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[1].y + average*(i+1)-7, rt.Width(),
231. m_ptOrigin[1].y + average*(i+1)+10), DT_LEFT);
232. str.Format("%8d", -(i+1));
233. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[2].y + average*(i+1)-7, rt.Width(),
234. m_ptOrigin[2].y + average*(i+1)+10), DT_LEFT);
235. }
236.
237. //2.计算四线的点值
238. float *fPrice1 = new float[days];
239. float *fPrice2 = new float[days];
240. float *fPrice3 = new float[days];
241. float *fPrice4 = new float[days];
242.
243. for (i=0; i<days; i++)
244. {
245. fPrice1[i] = m_sdCurrData[i].close / 100.0 / 2 - m_sdCurrData[i].close / 100 / 2
246. + m_sdCurrData[i].close / 100 / 2 % 4;
247. fPrice2[i] = m_sdCurrData[i].close / 100.0 / 3 - m_sdCurrData[i].close / 100 / 3
248. + m_sdCurrData[i].close / 100 / 3 % 4;
249. fPrice3[i] = m_sdCurrData[i].close / 100.0 / 4 - m_sdCurrData[i].close / 100 / 4
250. + m_sdCurrData[i].close / 100 / 4 % 4;
251. fPrice4[i] = m_sdCurrData[i].close / 100.0 / 5 - m_sdCurrData[i].close / 100 / 5
252. + m_sdCurrData[i].close / 100 / 5 % 4;
253. }
254.
255. for (i=0; i<days-1; i++)
256. {
257. pDC->SelectObject(&penWhite);
258. pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice1[i]);
259. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice1[i+1]);
260. pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice1[i]);
261. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice1[i+1]);
262.
263. pDC->SelectObject(&penYellow);
264. pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice2[i]);
265. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice2[i+1]);
266. pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice2[i]);
267. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice2[i+1]);
268.
269. pDC->SelectObject(&penRedSolid);
270. pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice3[i]);
271. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice3[i+1]);
272. pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice3[i]);
273. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice3[i+1]);
274.
275. pDC->SelectObject(&penGreen);
276. pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice4[i]);
277. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice4[i+1]);
278. pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice4[i]);
279. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice4[i+1]);
280. }
281. DrawDateText(pDC);
282. //释放动态分配的内存
283. delete fiveAverData;
284. delete tenAverData;
285. delete fPrice1;
286. delete fPrice2;
287. delete fPrice3;
288. delete fPrice4;
289. //还原绘画环境
290. pDC->SelectObject(pOldPen);
291. pDC->SelectObject(pOldBrush);
292.}
说明:上面最麻烦的是数据的处理,如果处理不好就不能把图画到合适的位置,所以计算的时候一定要用float,不然就算是很小的数据差别都会造成绘图不到指定的位置。还有这个函数代码量比较多,是因为绘图都需要用到平均每天的宽度和一些公用的参数,所以就在一个函数里完成了。其实可以写成很多个函数模块,比如每一个部分可以写成一个函数,然后数据计算可以用专门的函数封装。
(3)补充功能,在最下面显示当前鼠标对应的日期
view plaincopy to clipboard
01./*
02.* 函数名称: DrawDateText
03.* 输 入: pDC
04.* pDC: 绘画用的CDC指针
05.* 输 出:
06.* 功能描述: 显示日期
07.* 全局变量:
08.* 作 者: 吴友强
09.* 日 期: 2009年12月02日
10.* 修 改:
11.* 日 期:
12.*/
13.void CTongXinDaView::DrawDateText(CDC *pDC)
14.{
15. CRect rt;
16. GetClientRect(&rt);
17. float xAver;
18. CString strDate;
19. xAver = (rt.Width()-100) / (float)m_iDays;
20.
21. for (int i=0; i<m_iDays; i++)
22. {
23. if ((m_ptDatePoint.x > xAver * i) && (m_ptDatePoint.x < (xAver * i+xAver / 3 *2)))
24. {
25. m_iDateCount = i;
26. }
27. }
28. CPen pen(PS_SOLID, 1, RGB(200, 0, 0));
29. CPen *pOldPen = pDC->SelectObject(&pen);
30. CBrush brushBlue(RGB(0, 0, 150));
31. CBrush *pOldBrush = pDC->SelectObject(&brushBlue);
32. pDC->SetTextColor(RGB(200, 200, 200));
33. pDC->MoveTo(0, rt.Height()-15);
34. pDC->LineTo(rt.Width()-100, rt.Height()-15);
35. strDate.Format("%d", m_sdCurrData[m_iDateCount]);
36. strDate.Insert(4, '/');
37. strDate.Insert(7, '/');
38. pDC->Rectangle(m_ptDatePoint.x, rt.Height()-15, m_ptDatePoint.x+75, rt.Height());
39. pDC->DrawText(strDate, CRect(m_ptDatePoint.x, rt.Height()-15, m_ptDatePoint.x+75, rt.Height()), DT_LEFT);
40. pDC->SelectObject(&pOldPen);
41. pDC->SelectObject(&pOldBrush);
42.}
说明:为了显示出这个日期,需要记录当前屏幕绘画了哪些天的图,然后根据鼠标的坐标位置判断处于哪一天并显示出来。
(4)图形放大缩小以及左右平移的实现思路:
其实原理都是一样的,就是根据需要的天数显示,放大就是显示的天数比较少,缩小就是多显示一些天数,左右移动就是重新提取一些数据。显示的天数可以根据需要按照固定需要按固定的比例放大和缩小。然后就是通过一些按键来控制或是鼠标控制,其中还有一个辅助线的设置比较靠逻辑,需要细心才能弄好。具体的请参看代码,有注释。
3.心得体会:
刚开始的时候感觉什么都不会,但是我还是勇敢地迈出了自己的第一步,因为在这几年的学习过程中我发现自己解决问题的能力提升了很多,所以相信自己能够完成。然后自己就静下心来一行一行代码的写,经过几天努力,完成了大部分功能,自己的信心也是越来越强。现在这个程度还算过的去了吧。
4.存在的不足:
由于对于股票行业术语不是很了解,很多变量的命名不是很合理和规范,代码的组织也不是很好。
5.得到的帮助与指导:
感谢老师提供的思路,特别是对于所谓的图形放大和缩小,左右移动,老师的提醒使我恍然大悟,以后基本上没有什么太大的困难。就是数据的提取问题。
文档说明:
此文档适合VC++的初学者,高手也可参考(希望能提出宝贵意见)。
开发前准备:
这是一个根据股票信息的数据绘的几个制界面,数据来源通信达软件的数据文件,通常在安装了通信达以后并下载数据到本地以后就有了。
1.数据文件的准备:
假如你安装通信达的目录是:D:\jcb_gx。那么对应的数据文件就在D:\jcb_gx\vipdoc\目录下,里面每一个目录下就是一类股票的数据,我们开发这个界面需要用到的是每个目录下的lday目录下的.lday后缀名的文件。每一个文件里面存放的是一支滚票的数据信息。我当时开发用到了两类股票的信息,分别对应的目录是:D:\jcb_gx\vipdoc\sh\lday和D:\jcb_gx\vipdoc\sz\lday。其实每类开发的方法完全一样,唯一不同就是读取不同的目录而已。
2.文件数据结构:
准备好数据以后,还有一点是必须知道的,不然也没有办法进行下去,就是文件里面的数据格式是怎样的。因为我用的是通信达的数据文件,所以只需要在www.g.cn查询通信达的数据格式就可以了,如下:
view plaincopy to clipboard
01.typedef struct
02.{ //共32字节
03. int date; //4字节 如20091229
04. int open; //开盘价
05. int high; //最高价
06. int low; //最低价
07. int close; //收盘价
08. float amount; //成交额
09. int vol; //成交量
10. int reservation; //保留值
11.} StockData;
详细开发过程(包括我的思路和具体实现):
1.实现如上图的界面,需要做如下事情
(1)读一个目录下的所有文件,并从文件名中提取出相应股票的代码
可行性分析:
首先我们打开的是一个目录,然后从这个目录中读出里面所有的文件名,目录存放的内容其实就是此目录下的文件名或目录名。用到两个函数,一个FindFirstFile查找到一个目录下的第一个文件名,另一个FindNextFile查找下一个文件名。这样就可以遍历一个目录下的所有文件名了。
具体实现:
view plaincopy to clipboard
01.BOOLCTongXinDaView::ReadFileData(LPCSTR path)
02.{
03. //path是这种形式的参数:D:\\jcb_gx\\vipdoc\\sh\\lday\\*
04. m_iCount = 0;
05. WIN32_FIND_DATA tFind = {0};
06. int i = 0;
07. CString strTemp;
08.
09. HANDLE hSearch = ::FindFirstFile(path, &tFind);
10. if (hSearch == INVALID_HANDLE_VALUE)
11. {
12. return FALSE;
13. }
14.
15. //过滤掉.和..文件
16. ::FindNextFile(hSearch, &tFind);
17. while (::FindNextFile(hSearch, &tFind))
18. {
19. strTemp.Format("%s",tFind.cFileName);
20. m_File[i].Format("%s",path);
21. //去掉查询用到的*通配符
22. m_File[i] =m_File[i].Left(m_File[i].GetLength()-1);
23. m_File[i] +=strTemp;
24. //从文件名中提取股票代码
25. m_FileName[i] =strTemp.Mid(2, 6);
26. i++;
27. }
28.
29. m_iPageCount = i / 31 + 1; //求出需要显示的总页面数
30. m_iLeave = i % 31; //最后一页显示的数据
31. m_CurrFile = m_File[m_iCount]; //保存选中的文件名
32. ::FindClose(hSearch);
33. return TRUE;
34.}
注意事项:每一个目录下都有这两个目录文件:“.”和“..”。它们分别代表本目录和父目录(就是上层目录),必须过滤掉这两个目录文件。还好每次这两个目录文件总是最先被读出,所以前两次读出来的信息直接不管就可以了。
上面的函数被相应的每一个菜单项事件调用,就是针对不同的股票用一个菜单项打开。
(2)页面的显示:
可行性分析:
先说说我当时需要完成的现实任务,每页显示31行(具体可以变动,但是31 行效果比较好),显示3列,第一列索引号,也就是起个计数的作用,第二列就是刚才我们提取到的股票代码号,第三列随便填充4个汉字。还要求画一条线表示当前选中的股票,鼠标上下滚动和PageDown,PageUp按键实现上下翻页功能,鼠标点击选中点击最近的一支股票,按键上下键也可以移动股票选择。明白了需要实现的功能,我现在就一步一步来完成。这里需要用到文字输出函数DrawText。
具体实现:
view plaincopy to clipboard
01.void CTongXinDaView::DrawText(CDC *pDC, int page)
02.{
03. CRect rt;
04. GetClientRect(&rt);
05. int high = rt.Width() / 55;
06. int y = high;//控制每一行显示数据的增量
07.
08. CString strLine;
09. int number = 1;
10. pDC->SetBkMode(TRANSPARENT);
11. pDC->SetTextColor(RGB(200, 200, 200));
12. strLine.Format(" 代码 名称 日期 开牌价 最高价 最低价 收盘价 多多 成交量 ");
13. pDC->TextOut(0, 0, strLine);
14. //控制最后一页只显示剩余的
15. if (page == m_iPageCount-1)
16. {
17. for (int i=page * SCREENHEIGHT; i<(page*SCREENHEIGHT + m_iLeave); i++)
18. {
19. if (i == m_iCount)
20. {
21. CPen pen(PS_SOLID, 1, RGB(255, 255, 0));
22. CPen *pOldPen = pDC->SelectObject(&pen);
23. pDC->MoveTo(50, y+high-5);
24. pDC->LineTo(rt.right, y+high-5);
25. pDC->SelectObject(pOldPen);
26. }
27. strLine.Format("%d", i+1);
28. pDC->DrawText(strLine, CRect(0, y, 40, y+high), DT_RIGHT);
29. pDC->DrawText(m_FileName[i], CRect(50, y, 100, y+high), DT_LEFT);
30. pDC->DrawText("长城开发", CRect(110, y, 180, y+high), DT_LEFT);
31. y = y + high;
32. }
33. }
34. else
35. {
36. for (int i=page * SCREENHEIGHT; i<(page+1)*SCREENHEIGHT; i++)
37. {
38. if (i == m_iCount)
39. {
40. CPen pen(PS_SOLID, 1, RGB(255, 255, 0));
41. CPen *pOldPen = pDC->SelectObject(&pen);
42. pDC->MoveTo(50, y+high-7);
43. pDC->LineTo(rt.right, y+high-7);
44. pDC->SelectObject(pOldPen);
45. }
46. strLine.Format("%d", i+1);
47. pDC->DrawText(strLine, CRect(0, y, 40, y+high), DT_RIGHT);
48. pDC->DrawText(m_FileName[i], CRect(50, y, 100, y+high), DT_LEFT);
49. pDC->DrawText("长城开发", CRect(110, y, 180, y+high), DT_LEFT);
50. y = y + high;
51. }
52. }
53.}
注意事项:
1.最后一页数据条目不够,需要特殊处理。
2.输出函数用的是DawText而不是TextOut,是为了使输出对齐。
3.鼠标和按键的响应只是简单逻辑处理和显示不同的数据。
(3)分页显示和选取当前一支股票的实现思想说明:
可行性分析:
上面已经实现了页面的显示,现在说说怎样控制上下翻页和鼠标键盘实现选中一支股票(我是用一条黄色的线标示)。上下翻页时通过键盘上的PageDown和PageUp,还有鼠标滚轮控制的。其实原理很简单,只需要我们在读目录下每个股票文件时记录一下这个目录下的股票数量,也就是我们需要显示的所有行数。我们一个常量记录每一页显示的数目,用总数除以这个数就是总共需要的页数。然后用一个变量记录当前显示的是第几页,上下翻页就是对这个变量的加减操作了。选中一支股票则是根据我们点击的鼠标的位置来决定,因为每一行所占的页面宽度是一样的,只需要判断点击在哪一行所处的位置就可以了。当然也需要用变量记录选中的是那一只股票,总数刚才我们也记录了,所以很容易记录当前的哪一只股票,只是需要注意翻页后选择的股票相应的加减一页的显示的股票数。最后一点就是注意一些边界条件的处理。
2.实现如下图的界面:
(1)读取选中的股票文件,并保存以为绘图使用这些数据
可行性分析:
按照固定的数据格式把文件中的数据读入到一个结构体中保存,用fread每次读入固定长度的数据格式接可以了。
具体实现:
view plaincopy to clipboard
01./*
02.* 函数名称: ReadData
03.* 输 入:
04.* 输 出:
05.* 功能描述: 从当前文件中读取数据
06.* 全局变量:
07.* 作 者: 吴友强
08.* 日 期: 2009年11月29日
09.* 修 改:
10.* 日 期:
11.*/
12.void CTongXinDaView::ReadData()
13.{
14. FILE *fp;
15. m_iDataItemCount = -1;
16. //打开当前文件
17. if ((fp = fopen(m_CurrFile, "rb")) == NULL)
18. {
19. return ;
20. }
21.
22. while (!feof(fp))
23. {
24. m_iDataItemCount++;
25. fread(&m_StockData[m_iDataItemCount],sizeof(StockData),1,fp);
26. //求最大的日期
27. if (m_StockData[m_iDataItemCount].date > m_iMaxDate)
28. {
29. m_iMaxDate = m_StockData[m_iDataItemCount].date;
30. }
31. //求最小的日期
32. if (m_iDataItemCount == 0)
33. {
34. m_iMinDate = m_StockData[m_iDataItemCount].date;
35. }
36. else if (m_StockData[m_iDataItemCount].date > 0
37.&& m_StockData[m_iDataItemCount].date < m_iMinDate)
38. {
39. m_iMinDate = m_StockData[m_iDataItemCount].date;
40. }
41. }
42.
43. m_iDataItemCount--;//去掉最后一条无用的记录
44. m_iStartDay = m_iDataItemCount;
45. if (m_iDays > m_iDataItemCount)
46. {
47. m_iDays = m_iDataItemCount+1;
48. }
49.//以前在这里没有关闭文件,所以当打开一定数量的时候(windows限制的)在打开文件就会失败
50. fclose(fp);
51.}
(2)提取当前需要显示的数据:
可行性分析:
根据当前需要显示多少天的数据来提取,从上面我们从文件里面读取的数据中提取,以后的所谓放大缩小,左右移动就是提取不同天的数据就是了。
具体实现:
view plaincopy to clipboard
01./*
02.* 函数名称: GetStockData
03.* 输 入: days
04.* days: 屏幕需要显示的天数
05.* 输 出:
06.* 功能描述: 得到显示的数据和求取各个最值
07.* 全局变量:
08.* 作 者: 吴友强
09.* 日 期: 2009年11月30日
10.* 修 改:
11.* 日 期:
12.*/
13.void CTongXinDaView::GetStockData(int days)
14.{
15. m_iLowMax = 0;
16. m_iHighMax = 0;
17. m_iVolMax = 0;
18. if (m_sdCurrData != NULL)
19. {
20. delete m_sdCurrData;
21. }
22.
23. m_sdCurrData = new StockData[days];
24. for (int i=0; i<days; i++)
25. {
26. m_sdCurrData[days-i-1] = m_StockData[m_iStartDay-i-1];
27.
28. //求本次显示成交量的最大值
29. if (m_sdCurrData[days-i-1].vol > m_iVolMax)
30. {
31. m_iVolMax = m_sdCurrData[days-i-1].vol;
32. }
33. //求本次显示最高值的最大值
34. if (m_sdCurrData[days-i-1].high > m_iHighMax)
35. {
36. m_iHighMax = m_sdCurrData[days-i-1].high;
37. }
38. //求本次显示最低值的最小值
39. if (i == 0)
40. {
41. m_iLowMax = m_sdCurrData[days-i-1].low;
42. }
43. else if (m_sdCurrData[days-i-1].low < m_iLowMax)
44. {
45. m_iLowMax = m_sdCurrData[days-i-1].low;
46. }
47. }
48.}
(3)得到需要显示的数据以后,我们就可以开始绘图了
可行性分析:
我们得到需要显示的数据以后,就需要根据当前显示的宽度和高度来划分屏幕了,根据客户的需求大致需要把屏幕分为上中下三部分,第一部分画成交量的平均值线和一天中的最高值到最低值的一条竖线,还有开盘价和收盘价的矩形图;第二部分成交总量;第三部分成交价格除以2,3,4,5刻度控制在-4到4资料的线形图。首先我们必须确定三部分的高度,然后把3个坐标固定下来并绘画出来,至于坐标的刻度我们可以动态的根据每次需要显示的数据的最大值和最小值来计算确定,然后根据刻度的比例来画所有的图形。其中很多需要计算,具体的情看代码。下面是整个系统的黑心部分,具体请看代码注释。
具体实现:
view plaincopy to clipboard
01./*
02.* 函数名称: DrawGraphic
03.* 输 入: pDC, days
04.* pDC: 画图的CDC指针
05.* days: 显示数据的天数
06.* 输 出:
07.* 功能描述: 画各种坐标以及图形
08.* 全局变量:
09.* 作 者: 吴友强
10.* 日 期: 2009年11月29日
11.* 修 改: 吴友强
12.* 日 期: 2009年12月4日
13.*/
14.void CTongXinDaView::DrawGraphic(CDC *pDC, int days)
15.{
16.//设置透明绘图模式
17. pDC->SetBkMode(TRANSPARENT);
18. pDC->SetTextColor(RGB(200, 0 ,0));
19.
20. CRect rt;
21. GetClientRect(&rt);
22. float average;
23. float averPri;
24. float ftemp, ftext;
25. CString str;
26. int i = 0;
27. float xAver;
28.
29. pDC->DrawText("当前代码: " + m_FileName[m_iCount], CRect(0, 0, 200, 20), DT_LEFT);
30. //初始化各个坐标原点
31. m_ptOrigin[0].x = rt.Width()-100;
32. m_ptOrigin[0].y = rt.Height()/8 * 3;
33. m_ptOrigin[1].x = rt.Width()-100;
34. m_ptOrigin[1].y = rt.Height()/4 * 2;
35. m_ptOrigin[2].x = rt.Width()-100;
36. m_ptOrigin[2].y = rt.Height()/4 * 3;
37.
38.//划分屏幕为三部分
39. CPen penRedSolid(PS_SOLID, 1, RGB(200, 0, 0));
40. CPen *pOldPen = pDC->SelectObject(&penRedSolid);
41. pDC->MoveTo(0, m_ptOrigin[0].y);
42. pDC->LineTo(rt.Width(), m_ptOrigin[0].y);
43. pDC->MoveTo(0, m_ptOrigin[1].y);
44. pDC->LineTo(rt.Width(), m_ptOrigin[1].y);
45. pDC->MoveTo(0, m_ptOrigin[2].y);
46. pDC->LineTo(rt.Width()-100, m_ptOrigin[2].y);
47. pDC->MoveTo(rt.Width()-100, 0);
48. pDC->LineTo(rt.Width()-100, rt.Height());
49.
50. //每一天显示的宽度xAver
51. xAver = (rt.Width()-100) / (float)days;
52. //平均刻度代表的价格
53. averPri = (m_iHighMax-m_iLowMax) / 5.0 / 100;
54. //第一条价格起始线
55. ftemp = m_iLowMax/ 100.0;
56. //刻度线的距离
57. average = m_ptOrigin[0].y / 6.0;
58. CPen penRedDot(PS_DOT, 1, RGB(200, 0, 0));
59.
60. //画K线坐标
61. for (i=0; i<5; i++)
62. {
63. pDC->SelectObject(&penRedDot);
64. pDC->MoveTo(0, average*(i+1));
65. pDC->LineTo(rt.Width()-100, average*(i+1));
66. pDC->SelectObject(&penRedSolid);
67. pDC->LineTo(rt.Width()-100+10, average*(i+1));
68. pDC->MoveTo(rt.Width()-100, average*(i+1) + average/2);
69. pDC->LineTo(rt.Width()-100+5, average*(i+1) + average/2);
70. pDC->MoveTo(rt.Width()-100, average*(i+1) + average/4);
71. pDC->LineTo(rt.Width()-100+3, average*(i+1) + average/4);
72. pDC->MoveTo(rt.Width()-100, average*(i+1) + average/4*3);
73. pDC->LineTo(rt.Width()-100+3, average*(i+1) + average/4*3);
74.
75. str.Format("%10.2f", ftemp + averPri * (5-i));
76. pDC->DrawText(str, CRect(rt.Width()-100, average*(i+1)-7, rt.Width(), average*(i+1)+10), DT_LEFT);
77. }
78.
79. CPen penGreen(PS_SOLID, 1, RGB(0, 200, 0));
80. CBrush brush(NULL_BRUSH);
81. CBrush brushGreen(RGB(0, 200, 0));
82. CBrush *pOldBrush = pDC->SelectObject(&brush);
83. //画每天最低到最高的线,开盘和收盘的矩形
84. for (i=0; i<days; i++)
85. {
86. if (m_sdCurrData[i].open <= m_sdCurrData[i].close)
87. {
88. pDC->SelectObject(&penRedSolid);
89. pDC->SelectObject(&brush);
90. pDC->MoveTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].high/100.0-ftemp));
91. pDC->LineTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].low/100.0-ftemp));
92. pDC->Rectangle(xAver*i, average*5-average/averPri*(m_sdCurrData[i].open/100.0- ftemp),
93. xAver*i+xAver/3*2, average*5-average/averPri*(m_sdCurrData[i].close/100.0- ftemp));
94. }
95. else
96. {
97. pDC->SelectObject(&penGreen);
98. pDC->SelectObject(&brushGreen);
99. pDC->MoveTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].high/100.0-ftemp));
100. pDC->LineTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].low/100.0-ftemp));
101. pDC->Rectangle(xAver*i, average*5-average/averPri*(m_sdCurrData[i].close/100.0- ftemp),
102. xAver*i+xAver/3*2, average*5-average/averPri*(m_sdCurrData[i].open/100.0- ftemp));
103. }
104.
105. if (!m_bMouseMove)
106. {
107. if (m_iDrawCount == i)
108. {
109. m_ptSavePoint.x = xAver * i+xAver/3;
110. m_ptSavePoint.y = average*5-average/averPri*(m_sdCurrData[i].open/100.0-ftemp);
111. }
112. }
113. }
114.
115. //计算5日平均值和10日平均值
116. int *fiveAverData = new int[days];
117. int *tenAverData = new int[days];
118. for (i=0; i<days; i++)
119. {
120. if (i >= 4)
121. {
122. fiveAverData[i] = (m_sdCurrData[i].close + m_sdCurrData[i-1].close +m_sdCurrData[i-2].close +
123. m_sdCurrData[i-3].close + m_sdCurrData[i-4].close) / 5;
124. }
125. else
126. {
127. fiveAverData[i] = m_sdCurrData[i].close * 5 / 5;
128. }
129.
130. if (i >= 9)
131. {
132. tenAverData[i] = (m_sdCurrData[i].close + m_sdCurrData[i-1].close +m_sdCurrData[i-2].close +
133. m_sdCurrData[i-3].close + m_sdCurrData[i-4].close + m_sdCurrData[i-5].close +
134. m_sdCurrData[i-6].close +m_sdCurrData[i-7].close + m_sdCurrData[i-8].close +
135. m_sdCurrData[i-9].close) / 10;
136. }
137. else
138. {
139. tenAverData[i] = m_sdCurrData[i].close * 10 / 10;
140. }
141. }
142.
143. //画5日均线和10日均线
144. CPen penWhite(PS_SOLID, 1, RGB(200, 200, 200));
145. CPen penYellow(PS_SOLID, 1, RGB(200, 200, 0));
146. for (i=1; i<days; i++)
147. {
148. pDC->SelectObject(&penWhite);
149. pDC->MoveTo(xAver * (i-1)+xAver/3, average*5-average/averPri*(fiveAverData[i-1]/100.0-ftemp));
150. pDC->LineTo(xAver * i+xAver/3, average*5-average/averPri*(fiveAverData[i]/100.0-ftemp));
151.
152. pDC->SelectObject(&penYellow);
153. pDC->MoveTo(xAver * (i -1)+xAver/3, average*5-average/averPri*(tenAverData[i-1]/100.0-ftemp));
154. pDC->LineTo(xAver * i+xAver/3, average*5-average/averPri*(tenAverData[i]/100.0-ftemp));
155. }
156.
157. //画柱状成交量的坐标和柱状图
158. average = (m_ptOrigin[1].y - m_ptOrigin[0].y) / 5;
159. float averVol = m_iVolMax / 4.0;
160. int temp = m_iVolMax / 4 / 100000 * 1000;//显示刻度用的临时变量
161.
162. pDC->SelectObject(&penRedSolid);
163. pDC->SelectObject(&brush);
164. str.Format("%s", " X100");
165. pDC->Rectangle(rt.Width()-100, m_ptOrigin[1].y-10, rt.Width()-50, m_ptOrigin[1].y+5);
166. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[1].y-10, rt.Width(), m_ptOrigin[1].y+5), DT_LEFT);
167. for (i=0; i<4; i++)
168. {
169. pDC->SelectObject(&penRedDot);
170. pDC->MoveTo(0, m_ptOrigin[0].y + average*(i+1));
171. pDC->LineTo(rt.Width()-100, m_ptOrigin[0].y + average*(i+1));
172. pDC->SelectObject(&penRedSolid);
173. pDC->LineTo(rt.Width()-100+10, m_ptOrigin[0].y + average*(i+1));
174. pDC->MoveTo(rt.Width()-100, m_ptOrigin[0].y + average*(i+1) + average / 2);
175. pDC->LineTo(rt.Width()-100+5, m_ptOrigin[0].y + average*(i+1) + average / 2);
176.
177. str.Format("%10d", temp * (4-i));
178. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[0].y + average*(i+1)-7, rt.Width(),
179. m_ptOrigin[0].y + average*(i+1)+10), DT_LEFT);
180. }
181.
182. for (i=0; i<days; i++)
183. {
184. if (m_sdCurrData[i].open <= m_sdCurrData[i].close)
185. {
186. pDC->SelectObject(&penRedSolid);
187. pDC->SelectObject(&brush);
188. }
189. else
190. {
191. pDC->SelectObject(&penGreen);
192. pDC->SelectObject(&brushGreen);
193. }
194. pDC->Rectangle(xAver*i, m_ptOrigin[1].y - m_sdCurrData[i].vol / averVol * average,
195. xAver*i+xAver/3*2, m_ptOrigin[1].y);
196. }
197.
198. //1.画收盘价四色线的刻度值
199. average = (m_ptOrigin[2].y - m_ptOrigin[1].y) / 5;
200. str.Format("%8d", 0);
201. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[2].y-7, rt.Width(), m_ptOrigin[2].y+10), DT_LEFT);
202. for (i=0; i<4; i++)
203. {
204. pDC->SelectObject(&penRedDot);
205. pDC->MoveTo(0, m_ptOrigin[1].y + average*(i+1));
206. pDC->LineTo(rt.Width()-100, m_ptOrigin[1].y + average*(i+1));
207.
208. pDC->SelectObject(&penRedSolid);
209. pDC->LineTo(rt.Width()-100+10, m_ptOrigin[1].y + average*(i+1));
210. for (int j=0; j<10; j++)
211. {
212. pDC->MoveTo(rt.Width()-100, m_ptOrigin[1].y + average*(i+1) + average / 10 *(j+1));
213. pDC->LineTo(rt.Width()-100+5, m_ptOrigin[1].y + average*(i+1) + average / 10*(j+1));
214. }
215.
216.
217. pDC->SelectObject(&penRedDot);
218. pDC->MoveTo(0, m_ptOrigin[2].y + average*(i+1));
219. pDC->LineTo(rt.Width()-100, m_ptOrigin[2].y + average*(i+1));
220.
221. pDC->SelectObject(&penRedSolid);
222. pDC->LineTo(rt.Width()-100+10, m_ptOrigin[2].y + average*(i+1));
223. for (j=0; j<10; j++)
224. {
225. pDC->MoveTo(rt.Width()-100, m_ptOrigin[2].y + average*i + average / 10 *(j+1));
226. pDC->LineTo(rt.Width()-100+5, m_ptOrigin[2].y + average*i + average / 10*(j+1));
227. }
228.
229. str.Format("%8d", 4-i);
230. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[1].y + average*(i+1)-7, rt.Width(),
231. m_ptOrigin[1].y + average*(i+1)+10), DT_LEFT);
232. str.Format("%8d", -(i+1));
233. pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[2].y + average*(i+1)-7, rt.Width(),
234. m_ptOrigin[2].y + average*(i+1)+10), DT_LEFT);
235. }
236.
237. //2.计算四线的点值
238. float *fPrice1 = new float[days];
239. float *fPrice2 = new float[days];
240. float *fPrice3 = new float[days];
241. float *fPrice4 = new float[days];
242.
243. for (i=0; i<days; i++)
244. {
245. fPrice1[i] = m_sdCurrData[i].close / 100.0 / 2 - m_sdCurrData[i].close / 100 / 2
246. + m_sdCurrData[i].close / 100 / 2 % 4;
247. fPrice2[i] = m_sdCurrData[i].close / 100.0 / 3 - m_sdCurrData[i].close / 100 / 3
248. + m_sdCurrData[i].close / 100 / 3 % 4;
249. fPrice3[i] = m_sdCurrData[i].close / 100.0 / 4 - m_sdCurrData[i].close / 100 / 4
250. + m_sdCurrData[i].close / 100 / 4 % 4;
251. fPrice4[i] = m_sdCurrData[i].close / 100.0 / 5 - m_sdCurrData[i].close / 100 / 5
252. + m_sdCurrData[i].close / 100 / 5 % 4;
253. }
254.
255. for (i=0; i<days-1; i++)
256. {
257. pDC->SelectObject(&penWhite);
258. pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice1[i]);
259. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice1[i+1]);
260. pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice1[i]);
261. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice1[i+1]);
262.
263. pDC->SelectObject(&penYellow);
264. pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice2[i]);
265. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice2[i+1]);
266. pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice2[i]);
267. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice2[i+1]);
268.
269. pDC->SelectObject(&penRedSolid);
270. pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice3[i]);
271. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice3[i+1]);
272. pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice3[i]);
273. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice3[i+1]);
274.
275. pDC->SelectObject(&penGreen);
276. pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice4[i]);
277. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice4[i+1]);
278. pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice4[i]);
279. pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice4[i+1]);
280. }
281. DrawDateText(pDC);
282. //释放动态分配的内存
283. delete fiveAverData;
284. delete tenAverData;
285. delete fPrice1;
286. delete fPrice2;
287. delete fPrice3;
288. delete fPrice4;
289. //还原绘画环境
290. pDC->SelectObject(pOldPen);
291. pDC->SelectObject(pOldBrush);
292.}
说明:上面最麻烦的是数据的处理,如果处理不好就不能把图画到合适的位置,所以计算的时候一定要用float,不然就算是很小的数据差别都会造成绘图不到指定的位置。还有这个函数代码量比较多,是因为绘图都需要用到平均每天的宽度和一些公用的参数,所以就在一个函数里完成了。其实可以写成很多个函数模块,比如每一个部分可以写成一个函数,然后数据计算可以用专门的函数封装。
(3)补充功能,在最下面显示当前鼠标对应的日期
view plaincopy to clipboard
01./*
02.* 函数名称: DrawDateText
03.* 输 入: pDC
04.* pDC: 绘画用的CDC指针
05.* 输 出:
06.* 功能描述: 显示日期
07.* 全局变量:
08.* 作 者: 吴友强
09.* 日 期: 2009年12月02日
10.* 修 改:
11.* 日 期:
12.*/
13.void CTongXinDaView::DrawDateText(CDC *pDC)
14.{
15. CRect rt;
16. GetClientRect(&rt);
17. float xAver;
18. CString strDate;
19. xAver = (rt.Width()-100) / (float)m_iDays;
20.
21. for (int i=0; i<m_iDays; i++)
22. {
23. if ((m_ptDatePoint.x > xAver * i) && (m_ptDatePoint.x < (xAver * i+xAver / 3 *2)))
24. {
25. m_iDateCount = i;
26. }
27. }
28. CPen pen(PS_SOLID, 1, RGB(200, 0, 0));
29. CPen *pOldPen = pDC->SelectObject(&pen);
30. CBrush brushBlue(RGB(0, 0, 150));
31. CBrush *pOldBrush = pDC->SelectObject(&brushBlue);
32. pDC->SetTextColor(RGB(200, 200, 200));
33. pDC->MoveTo(0, rt.Height()-15);
34. pDC->LineTo(rt.Width()-100, rt.Height()-15);
35. strDate.Format("%d", m_sdCurrData[m_iDateCount]);
36. strDate.Insert(4, '/');
37. strDate.Insert(7, '/');
38. pDC->Rectangle(m_ptDatePoint.x, rt.Height()-15, m_ptDatePoint.x+75, rt.Height());
39. pDC->DrawText(strDate, CRect(m_ptDatePoint.x, rt.Height()-15, m_ptDatePoint.x+75, rt.Height()), DT_LEFT);
40. pDC->SelectObject(&pOldPen);
41. pDC->SelectObject(&pOldBrush);
42.}
说明:为了显示出这个日期,需要记录当前屏幕绘画了哪些天的图,然后根据鼠标的坐标位置判断处于哪一天并显示出来。
(4)图形放大缩小以及左右平移的实现思路:
其实原理都是一样的,就是根据需要的天数显示,放大就是显示的天数比较少,缩小就是多显示一些天数,左右移动就是重新提取一些数据。显示的天数可以根据需要按照固定需要按固定的比例放大和缩小。然后就是通过一些按键来控制或是鼠标控制,其中还有一个辅助线的设置比较靠逻辑,需要细心才能弄好。具体的请参看代码,有注释。
3.心得体会:
刚开始的时候感觉什么都不会,但是我还是勇敢地迈出了自己的第一步,因为在这几年的学习过程中我发现自己解决问题的能力提升了很多,所以相信自己能够完成。然后自己就静下心来一行一行代码的写,经过几天努力,完成了大部分功能,自己的信心也是越来越强。现在这个程度还算过的去了吧。
4.存在的不足:
由于对于股票行业术语不是很了解,很多变量的命名不是很合理和规范,代码的组织也不是很好。
5.得到的帮助与指导:
感谢老师提供的思路,特别是对于所谓的图形放大和缩小,左右移动,老师的提醒使我恍然大悟,以后基本上没有什么太大的困难。就是数据的提取问题。
相关推荐
【标题】"C++编写的股票软件 完整源代码" 涉及的知识点非常丰富,涵盖了C++编程语言的高级应用以及金融领域的软件开发。C++是一种强大的静态类型、编译型、通用的编程语言,以其面向对象的特性、高效性能和灵活性在...
C++基于Qt的流数据分析与可视化软件源码。库 blitz - inline,无须单独编译 exprtkX KDDockWidgets kfr - inline,无须单独编译 libsndfile praat - inline,无须单独编译 QCustomPlot QtnProperty ...
c++制造股票软件案.doc,项目需求书。了解实际项目中软件需求怎么写
本篇将深入探讨一个由学生在课程中利用C++实现的股市软件,以此来揭示C++在金融数据处理和应用开发中的潜力。 首先,我们要理解股市软件的核心功能,它通常包括实时股票报价、历史数据查询、技术分析图表、交易下单...
一个用C++开发的股票分析系统的原代码程序
在IT行业中,尤其是在金融数据分析领域,股票数据软件的开发是一项重要的任务。本实例"股票数据软件实例C++"提供了一个基础的平台,用于展示如何利用C++编程语言处理和显示股票数据。C++是一种强大的、面向对象的...
【Qt股票软件开发详解】 Qt是一个跨平台的C++图形用户界面应用程序开发框架,由挪威的Trolltech公司(现已被Nokia收购)开发。它广泛应用于桌面、移动设备和嵌入式系统的用户界面设计。在Linux系统下,Qt提供了丰富...
标题中的“一个用C++开发的股票分析系统的原代码程序”揭示了这是一个使用C++编程语言构建的软件项目,主要用于股票市场数据的分析。在IT领域,C++是一种高效的、面向对象的编程语言,常用于开发系统软件、游戏引擎...
C++股票交易系统是一种基于C++编程语言构建的软件应用,它模拟了实际股票市场的各项功能,包括股票的买卖、交易记录查询、走势图绘制、用户注册、市场开收盘管理以及订单挂起与解挂等。这样的系统对于学习金融交易...
对于那些想要学习或开发股票软件的人来说,这是一个宝贵的资源,可以从中了解股票软件的基本框架、数据处理方式以及用户界面设计等关键点。 1. **指标编写平台**:在股票分析中,技术指标是必不可少的工具,如MACD...
在IT行业中,C++是一种强大的、面向对象的编程语言,被广泛用于开发高效、高性能的应用程序,尤其是在系统软件、游戏引擎、嵌入式系统等领域。MFC(Microsoft Foundation Classes)是微软提供的一套C++库,它基于...
这个软件可能是用于接收实时证券行情数据,并且它在Visual C++(VC)环境下开发,可能包含了一个名为“netts”的组件或类库,专门处理股票相关的数据接收。 描述中的“接收证券行情数据接口的VC源代码”意味着这个...
C++是一种强大的、面向对象的编程语言,广泛应用于系统软件、应用软件、游戏开发以及金融领域的复杂系统,如股票交易系统。这个项目的核心目标是理解和构建一个功能完备的股票交易平台,能够模拟真实的股票市场操作...
这款纯C++开发的行情分析软件以其高速稳定的特点,深受技术爱好者和专业交易者的青睐。其界面设计灵感来源于通达信和大智慧,这两大在国内极具影响力的行情软件,意味着它具有直观易用的特性,同时也提供了丰富的...
《基于MFC开发的股票软件详解》 在IT行业中,MFC(Microsoft Foundation Classes)是微软提供的一种C++类库,用于构建Windows应用程序。MFC为开发者提供了丰富的接口和类,使得开发人员能够更容易地利用Windows API...
C++是一种强大的、面向对象的编程语言,广泛用于系统软件、游戏开发、嵌入式系统和各种应用程序。它提供了丰富的控制结构、类、模板等特性,使程序设计更加灵活和高效。在这个项目中,C++用于处理数据、执行计算以及...
在本项目中,我们关注的是一...总之,这个C++实现的股市软件项目涵盖了编程基础、数据处理、网络通信、图形界面设计等多个方面,对于想深入理解股市软件开发或者提升C++技能的开发者来说,是一个非常有价值的实践项目。
综上所述,这个C++股票信息查询系统涵盖了众多重要的计算机科学概念,不仅让学生熟悉C++编程,还能加深对数据结构和算法的理解,同时锻炼了他们实际项目开发的能力。通过这样的课程设计,学生能更好地准备未来的职业...
总的来说,《VC++6.0股票软件源代码》是一个深入学习金融软件开发的实践案例,它融合了C++编程、金融数据处理、图形界面设计等多个领域的知识,对于有志于在该领域发展的程序员而言,是一份难得的学习资料。