VC++开发股票软件

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查询通信达的数据格式就可以了,如下:

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{   //共32字节
    int date;    //4字节   如20091229
    int open;    //开盘价
    int high;    //最高价
    int low;     //最低价
    int close;   //收盘价
    float amount; //成交额
    int vol;      //成交量
    int reservation;  //保留值
} StockData;

 

详细开发过程(包括我的思路和具体实现):                             

1.实现如上图的界面,需要做如下事情

(1)读一个目录下的所有文件,并从文件名中提取出相应股票的代码

可行性分析:

首先我们打开的是一个目录,然后从这个目录中读出里面所有的文件名,目录存放的内容其实就是此目录下的文件名或目录名。用到两个函数,一个FindFirstFile查找到一个目录下的第一个文件名,另一个FindNextFile查找下一个文件名。这样就可以遍历一个目录下的所有文件名了。

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
BOOLCTongXinDaView::ReadFileData(LPCSTR path)
{
    //path是这种形式的参数:D:\\jcb_gx\\vipdoc\\sh\\lday\\*
    m_iCount = 0;
    WIN32_FIND_DATA  tFind   =   {0};
    int i = 0;
    CString strTemp;
 
    HANDLE hSearch = ::FindFirstFile(path, &tFind);  
    if (hSearch == INVALID_HANDLE_VALUE)  
    {  
         return FALSE;  
    }  
 
     //过滤掉.和..文件
     ::FindNextFile(hSearch, &tFind);
     while (::FindNextFile(hSearch, &tFind))
     {
         strTemp.Format("%s",tFind.cFileName);
         m_File[i].Format("%s",path);
         //去掉查询用到的*通配符
         m_File[i] =m_File[i].Left(m_File[i].GetLength()-1);
         m_File[i] +=strTemp;
         //从文件名中提取股票代码
         m_FileName[i] =strTemp.Mid(2, 6);
         i++;
       }
 
       m_iPageCount = i / 31 + 1;          //求出需要显示的总页面数
       m_iLeave = i % 31;                  //最后一页显示的数据
       m_CurrFile = m_File[m_iCount];      //保存选中的文件名
       ::FindClose(hSearch);
       return TRUE;
}

 注意事项:每一个目录下都有这两个目录文件:“.”和“..”。它们分别代表本目录和父目录(就是上层目录),必须过滤掉这两个目录文件。还好每次这两个目录文件总是最先被读出,所以前两次读出来的信息直接不管就可以了。

上面的函数被相应的每一个菜单项事件调用,就是针对不同的股票用一个菜单项打开。

(2)页面的显示:

可行性分析:

先说说我当时需要完成的现实任务,每页显示31行(具体可以变动,但是31 行效果比较好),显示3列,第一列索引号,也就是起个计数的作用,第二列就是刚才我们提取到的股票代码号,第三列随便填充4个汉字。还要求画一条线表示当前选中的股票,鼠标上下滚动和PageDown,PageUp按键实现上下翻页功能,鼠标点击选中点击最近的一支股票,按键上下键也可以移动股票选择。明白了需要实现的功能,我现在就一步一步来完成。这里需要用到文字输出函数DrawText。

具体实现:      

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void CTongXinDaView::DrawText(CDC *pDC, int page)
{
    CRect rt;
    GetClientRect(&rt);
    int high = rt.Width() / 55;
    int y = high;//控制每一行显示数据的增量
 
    CString strLine;
    int number = 1;
    pDC->SetBkMode(TRANSPARENT);
    pDC->SetTextColor(RGB(200, 200, 200));
    strLine.Format("             代码       名称           日期       开牌价        最高价       最低价       收盘价      多多    成交量   ");
    pDC->TextOut(0, 0, strLine);
    //控制最后一页只显示剩余的
    if (page == m_iPageCount-1)
    {
        for (int i=page * SCREENHEIGHT; i<(page*SCREENHEIGHT + m_iLeave); i++)
        {
            if (i == m_iCount)
            {
                CPen pen(PS_SOLID, 1, RGB(255, 255, 0));
                CPen *pOldPen = pDC->SelectObject(&pen);
                pDC->MoveTo(50, y+high-5);
                pDC->LineTo(rt.right, y+high-5);
                pDC->SelectObject(pOldPen);
            }
            strLine.Format("%d", i+1);
            pDC->DrawText(strLine, CRect(0, y, 40, y+high), DT_RIGHT);
            pDC->DrawText(m_FileName[i], CRect(50, y, 100, y+high), DT_LEFT);
            pDC->DrawText("长城开发", CRect(110, y, 180, y+high), DT_LEFT);
            y = y + high;
        }
    }
    else
    {
        for (int i=page * SCREENHEIGHT; i<(page+1)*SCREENHEIGHT; i++)
        {
            if (i == m_iCount)
            {
                CPen pen(PS_SOLID, 1, RGB(255, 255, 0));
                CPen *pOldPen = pDC->SelectObject(&pen);
                pDC->MoveTo(50, y+high-7);
                pDC->LineTo(rt.right, y+high-7);
                pDC->SelectObject(pOldPen);
            }
            strLine.Format("%d", i+1);
            pDC->DrawText(strLine, CRect(0, y, 40, y+high), DT_RIGHT);
            pDC->DrawText(m_FileName[i], CRect(50, y, 100, y+high), DT_LEFT);
            pDC->DrawText("长城开发", CRect(110, y, 180, y+high), DT_LEFT);
            y = y + high;
        }
    }
}

注意事项:

  1.最后一页数据条目不够,需要特殊处理。

  2.输出函数用的是DawText而不是TextOut,是为了使输出对齐。

  3.鼠标和按键的响应只是简单逻辑处理和显示不同的数据。

(3)分页显示和选取当前一支股票的实现思想说明:

可行性分析:

上面已经实现了页面的显示,现在说说怎样控制上下翻页和鼠标键盘实现选中一支股票(我是用一条黄色的线标示)。上下翻页时通过键盘上的PageDown和PageUp,还有鼠标滚轮控制的。其实原理很简单,只需要我们在读目录下每个股票文件时记录一下这个目录下的股票数量,也就是我们需要显示的所有行数。我们一个常量记录每一页显示的数目,用总数除以这个数就是总共需要的页数。然后用一个变量记录当前显示的是第几页,上下翻页就是对这个变量的加减操作了。选中一支股票则是根据我们点击的鼠标的位置来决定,因为每一行所占的页面宽度是一样的,只需要判断点击在哪一行所处的位置就可以了。当然也需要用变量记录选中的是那一只股票,总数刚才我们也记录了,所以很容易记录当前的哪一只股票,只是需要注意翻页后选择的股票相应的加减一页的显示的股票数。最后一点就是注意一些边界条件的处理。

2.实现如下图的界面:

(1)读取选中的股票文件,并保存以为绘图使用这些数据

可行性分析:

按照固定的数据格式把文件中的数据读入到一个结构体中保存,用fread每次读入固定长度的数据格式接可以了。

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
* 函数名称: ReadData
* 输 入: 
* 输 出:
* 功能描述: 从当前文件中读取数据
* 全局变量:
* 作 者:  吴友强
* 日 期:  2009年11月29日
* 修 改:
* 日 期:
*/
void CTongXinDaView::ReadData()
{
    FILE *fp;
    m_iDataItemCount = -1;
    //打开当前文件
    if ((fp = fopen(m_CurrFile, "rb")) == NULL)
    {
        return ;
    }
 
    while (!feof(fp))
    {
        m_iDataItemCount++;
        fread(&m_StockData[m_iDataItemCount],sizeof(StockData),1,fp);
        //求最大的日期
        if (m_StockData[m_iDataItemCount].date > m_iMaxDate)
        {
            m_iMaxDate = m_StockData[m_iDataItemCount].date;
        }
        //求最小的日期
        if (m_iDataItemCount == 0)
        {
            m_iMinDate = m_StockData[m_iDataItemCount].date;
        }
        else if (m_StockData[m_iDataItemCount].date > 0
&& m_StockData[m_iDataItemCount].date < m_iMinDate)
        {
            m_iMinDate = m_StockData[m_iDataItemCount].date;
        }
    }
 
    m_iDataItemCount--;//去掉最后一条无用的记录
    m_iStartDay = m_iDataItemCount;
    if (m_iDays > m_iDataItemCount)
    {
        m_iDays = m_iDataItemCount+1;
    }
//以前在这里没有关闭文件,所以当打开一定数量的时候(windows限制的)在打开文件就会失败
    fclose(fp);
}

(2)提取当前需要显示的数据:

可行性分析:

根据当前需要显示多少天的数据来提取,从上面我们从文件里面读取的数据中提取,以后的所谓放大缩小,左右移动就是提取不同天的数据就是了。

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
* 函数名称: GetStockData
* 输 入:  days
*   days:   屏幕需要显示的天数
* 输 出:
* 功能描述: 得到显示的数据和求取各个最值
* 全局变量:
* 作 者:  吴友强
* 日 期:  2009年11月30日
* 修 改:
* 日 期:
*/
void CTongXinDaView::GetStockData(int days)
{
    m_iLowMax = 0;
    m_iHighMax = 0;
    m_iVolMax = 0;
    if (m_sdCurrData != NULL)
    {
        delete m_sdCurrData;
    }
 
    m_sdCurrData = new StockData[days];
    for (int i=0; i<days; i++)
    {
        m_sdCurrData[days-i-1] = m_StockData[m_iStartDay-i-1];
         
        //求本次显示成交量的最大值
        if (m_sdCurrData[days-i-1].vol > m_iVolMax)
        {
            m_iVolMax = m_sdCurrData[days-i-1].vol;
        }
        //求本次显示最高值的最大值
        if (m_sdCurrData[days-i-1].high > m_iHighMax)
        {
            m_iHighMax = m_sdCurrData[days-i-1].high;
        }
        //求本次显示最低值的最小值
        if (i == 0)
        {
            m_iLowMax = m_sdCurrData[days-i-1].low;
        }
        else if (m_sdCurrData[days-i-1].low < m_iLowMax)
        {
            m_iLowMax = m_sdCurrData[days-i-1].low;
        }
    }
}

(3)得到需要显示的数据以后,我们就可以开始绘图了

可行性分析:

我们得到需要显示的数据以后,就需要根据当前显示的宽度和高度来划分屏幕了,根据客户的需求大致需要把屏幕分为上中下三部分,第一部分画成交量的平均值线和一天中的最高值到最低值的一条竖线,还有开盘价和收盘价的矩形图;第二部分成交总量;第三部分成交价格除以2,3,4,5刻度控制在-4到4资料的线形图。首先我们必须确定三部分的高度,然后把3个坐标固定下来并绘画出来,至于坐标的刻度我们可以动态的根据每次需要显示的数据的最大值和最小值来计算确定,然后根据刻度的比例来画所有的图形。其中很多需要计算,具体的情看代码。下面是整个系统的黑心部分,具体请看代码注释。

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/*
* 函数名称: DrawGraphic
* 输 入:  pDC, days
*   pDC:    画图的CDC指针
*   days:   显示数据的天数
* 输 出:
* 功能描述: 画各种坐标以及图形
* 全局变量:
* 作 者:  吴友强
* 日 期:  2009年11月29日
* 修 改:  吴友强
* 日 期:  2009年12月4日
*/
void CTongXinDaView::DrawGraphic(CDC *pDC, int days)
{
//设置透明绘图模式
    pDC->SetBkMode(TRANSPARENT);
    pDC->SetTextColor(RGB(200, 0 ,0));
 
    CRect rt;
    GetClientRect(&rt);
    float average;
    float averPri;
    float ftemp, ftext;
    CString str;
    int i = 0;
    float xAver;
 
    pDC->DrawText("当前代码: " + m_FileName[m_iCount], CRect(0, 0, 200, 20), DT_LEFT);
    //初始化各个坐标原点
    m_ptOrigin[0].x = rt.Width()-100;
    m_ptOrigin[0].y = rt.Height()/8 * 3;
    m_ptOrigin[1].x = rt.Width()-100;
    m_ptOrigin[1].y = rt.Height()/4 * 2;
    m_ptOrigin[2].x = rt.Width()-100;
    m_ptOrigin[2].y = rt.Height()/4 * 3;
 
//划分屏幕为三部分
    CPen penRedSolid(PS_SOLID, 1, RGB(200, 0, 0));
    CPen *pOldPen = pDC->SelectObject(&penRedSolid);
    pDC->MoveTo(0, m_ptOrigin[0].y);
    pDC->LineTo(rt.Width(), m_ptOrigin[0].y);
    pDC->MoveTo(0, m_ptOrigin[1].y);
    pDC->LineTo(rt.Width(), m_ptOrigin[1].y);
    pDC->MoveTo(0, m_ptOrigin[2].y);
    pDC->LineTo(rt.Width()-100, m_ptOrigin[2].y);
    pDC->MoveTo(rt.Width()-100, 0);
    pDC->LineTo(rt.Width()-100, rt.Height());
 
    //每一天显示的宽度xAver
    xAver = (rt.Width()-100) / (float)days;
    //平均刻度代表的价格
    averPri = (m_iHighMax-m_iLowMax) / 5.0 / 100;
    //第一条价格起始线
    ftemp = m_iLowMax/ 100.0;
    //刻度线的距离
    average = m_ptOrigin[0].y / 6.0;
    CPen penRedDot(PS_DOT, 1, RGB(200, 0, 0));
     
    //画K线坐标
    for (i=0; i<5; i++)
    {
        pDC->SelectObject(&penRedDot);
        pDC->MoveTo(0, average*(i+1));
        pDC->LineTo(rt.Width()-100, average*(i+1));
        pDC->SelectObject(&penRedSolid);
        pDC->LineTo(rt.Width()-100+10, average*(i+1));
        pDC->MoveTo(rt.Width()-100, average*(i+1) + average/2);
        pDC->LineTo(rt.Width()-100+5, average*(i+1) + average/2);
        pDC->MoveTo(rt.Width()-100, average*(i+1) + average/4);
        pDC->LineTo(rt.Width()-100+3, average*(i+1) + average/4);
        pDC->MoveTo(rt.Width()-100, average*(i+1) + average/4*3);
        pDC->LineTo(rt.Width()-100+3, average*(i+1) + average/4*3);
 
        str.Format("%10.2f",  ftemp + averPri * (5-i));
        pDC->DrawText(str, CRect(rt.Width()-100, average*(i+1)-7, rt.Width(), average*(i+1)+10),                     DT_LEFT);
    }
 
    CPen penGreen(PS_SOLID, 1, RGB(0, 200, 0));
    CBrush brush(NULL_BRUSH);
    CBrush brushGreen(RGB(0, 200, 0));
    CBrush *pOldBrush = pDC->SelectObject(&brush);
    //画每天最低到最高的线,开盘和收盘的矩形
    for (i=0; i<days; i++)
    {
        if (m_sdCurrData[i].open <= m_sdCurrData[i].close)
        {
            pDC->SelectObject(&penRedSolid);
            pDC->SelectObject(&brush);
            pDC->MoveTo(xAver * i+xAver/3, average*5-                average/averPri*(m_sdCurrData[i].high/100.0-ftemp));
            pDC->LineTo(xAver * i+xAver/3, average*5-        average/averPri*(m_sdCurrData[i].low/100.0-ftemp));
            pDC->Rectangle(xAver*i, average*5-average/averPri*(m_sdCurrData[i].open/100.0-       ftemp),
                xAver*i+xAver/3*2, average*5-average/averPri*(m_sdCurrData[i].close/100.0-  ftemp));
        }
        else
        {
            pDC->SelectObject(&penGreen);
            pDC->SelectObject(&brushGreen);
            pDC->MoveTo(xAver * i+xAver/3, average*5-    average/averPri*(m_sdCurrData[i].high/100.0-ftemp));
            pDC->LineTo(xAver * i+xAver/3, average*5-    average/averPri*(m_sdCurrData[i].low/100.0-ftemp));
            pDC->Rectangle(xAver*i, average*5-average/averPri*(m_sdCurrData[i].close/100.0-  ftemp),
                xAver*i+xAver/3*2, average*5-average/averPri*(m_sdCurrData[i].open/100.0-   ftemp));
        }
 
        if (!m_bMouseMove)
        {
            if (m_iDrawCount == i)
            {
                m_ptSavePoint.x = xAver * i+xAver/3;
                m_ptSavePoint.y = average*5-average/averPri*(m_sdCurrData[i].open/100.0-ftemp);
            }
        }
    }
 
    //计算5日平均值和10日平均值
    int *fiveAverData = new int[days];
    int *tenAverData = new int[days];
    for (i=0; i<days; i++)
    {
        if (i >= 4)
        {
            fiveAverData[i] = (m_sdCurrData[i].close + m_sdCurrData[i-1].close +m_sdCurrData[i-2].close +
                m_sdCurrData[i-3].close + m_sdCurrData[i-4].close) / 5;
        }
        else
        {
            fiveAverData[i] = m_sdCurrData[i].close * 5 / 5;
        }
         
        if (i >= 9)
        {
            tenAverData[i] = (m_sdCurrData[i].close + m_sdCurrData[i-1].close +m_sdCurrData[i-2].close +
                m_sdCurrData[i-3].close + m_sdCurrData[i-4].close + m_sdCurrData[i-5].close +
 m_sdCurrData[i-6].close +m_sdCurrData[i-7].close + m_sdCurrData[i-8].close +
 m_sdCurrData[i-9].close) / 10;
        }
        else
        {
            tenAverData[i] = m_sdCurrData[i].close * 10 / 10;
        }  
    }
 
    //画5日均线和10日均线
    CPen penWhite(PS_SOLID, 1, RGB(200, 200, 200));
    CPen penYellow(PS_SOLID, 1, RGB(200, 200, 0));
    for (i=1; i<days; i++)
    {  
        pDC->SelectObject(&penWhite);
        pDC->MoveTo(xAver * (i-1)+xAver/3, average*5-average/averPri*(fiveAverData[i-1]/100.0-ftemp));
        pDC->LineTo(xAver * i+xAver/3, average*5-average/averPri*(fiveAverData[i]/100.0-ftemp));
 
        pDC->SelectObject(&penYellow);
        pDC->MoveTo(xAver * (i -1)+xAver/3, average*5-average/averPri*(tenAverData[i-1]/100.0-ftemp));
        pDC->LineTo(xAver * i+xAver/3, average*5-average/averPri*(tenAverData[i]/100.0-ftemp));
    }
 
    //画柱状成交量的坐标和柱状图
    average = (m_ptOrigin[1].y - m_ptOrigin[0].y) / 5;
    float averVol = m_iVolMax / 4.0;
    int temp = m_iVolMax / 4 / 100000 * 1000;//显示刻度用的临时变量
     
    pDC->SelectObject(&penRedSolid);
    pDC->SelectObject(&brush);
    str.Format("%s""   X100");
    pDC->Rectangle(rt.Width()-100, m_ptOrigin[1].y-10, rt.Width()-50, m_ptOrigin[1].y+5);
    pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[1].y-10, rt.Width(), m_ptOrigin[1].y+5), DT_LEFT);
    for (i=0; i<4; i++)
    {
        pDC->SelectObject(&penRedDot);
        pDC->MoveTo(0, m_ptOrigin[0].y + average*(i+1));
        pDC->LineTo(rt.Width()-100, m_ptOrigin[0].y + average*(i+1));
        pDC->SelectObject(&penRedSolid);
        pDC->LineTo(rt.Width()-100+10, m_ptOrigin[0].y + average*(i+1));
        pDC->MoveTo(rt.Width()-100, m_ptOrigin[0].y + average*(i+1) + average / 2);
        pDC->LineTo(rt.Width()-100+5, m_ptOrigin[0].y + average*(i+1) + average / 2);
         
        str.Format("%10d",  temp * (4-i));
        pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[0].y + average*(i+1)-7, rt.Width(),
            m_ptOrigin[0].y + average*(i+1)+10), DT_LEFT);
    }
 
    for (i=0; i<days; i++)
    {
        if (m_sdCurrData[i].open <= m_sdCurrData[i].close)
        {
            pDC->SelectObject(&penRedSolid);
            pDC->SelectObject(&brush);
        }
        else
        {
            pDC->SelectObject(&penGreen);
            pDC->SelectObject(&brushGreen);
        }
        pDC->Rectangle(xAver*i, m_ptOrigin[1].y - m_sdCurrData[i].vol / averVol * average,
            xAver*i+xAver/3*2, m_ptOrigin[1].y);
    }
 
    //1.画收盘价四色线的刻度值
    average = (m_ptOrigin[2].y - m_ptOrigin[1].y) / 5;
    str.Format("%8d",  0);
    pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[2].y-7, rt.Width(), m_ptOrigin[2].y+10), DT_LEFT);
    for (i=0; i<4; i++)
    {
        pDC->SelectObject(&penRedDot);
        pDC->MoveTo(0, m_ptOrigin[1].y + average*(i+1));
        pDC->LineTo(rt.Width()-100, m_ptOrigin[1].y + average*(i+1));
 
        pDC->SelectObject(&penRedSolid);
        pDC->LineTo(rt.Width()-100+10, m_ptOrigin[1].y + average*(i+1));
        for (int j=0; j<10; j++)
        {
            pDC->MoveTo(rt.Width()-100, m_ptOrigin[1].y + average*(i+1) + average / 10 *(j+1));
            pDC->LineTo(rt.Width()-100+5, m_ptOrigin[1].y + average*(i+1) + average / 10*(j+1));
        }
         
 
        pDC->SelectObject(&penRedDot);
        pDC->MoveTo(0, m_ptOrigin[2].y + average*(i+1));
        pDC->LineTo(rt.Width()-100, m_ptOrigin[2].y + average*(i+1));
 
        pDC->SelectObject(&penRedSolid);
        pDC->LineTo(rt.Width()-100+10, m_ptOrigin[2].y + average*(i+1));
        for (j=0; j<10; j++)
        {
            pDC->MoveTo(rt.Width()-100, m_ptOrigin[2].y + average*i + average / 10 *(j+1));
            pDC->LineTo(rt.Width()-100+5, m_ptOrigin[2].y + average*i + average / 10*(j+1));
        }
 
        str.Format("%8d",  4-i);
        pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[1].y + average*(i+1)-7, rt.Width(),
            m_ptOrigin[1].y + average*(i+1)+10), DT_LEFT);
        str.Format("%8d",  -(i+1));
        pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[2].y + average*(i+1)-7, rt.Width(),
            m_ptOrigin[2].y + average*(i+1)+10), DT_LEFT);
    }
 
    //2.计算四线的点值
    float *fPrice1 = new float[days];
    float *fPrice2 = new float[days];
    float *fPrice3 = new float[days];
    float *fPrice4 = new float[days];
 
    for (i=0; i<days; i++)
    {
        fPrice1[i] = m_sdCurrData[i].close / 100.0 / 2 - m_sdCurrData[i].close / 100 / 2
            + m_sdCurrData[i].close / 100 / 2 % 4;
        fPrice2[i] = m_sdCurrData[i].close / 100.0 / 3 - m_sdCurrData[i].close / 100 / 3
            + m_sdCurrData[i].close / 100 / 3 % 4;
        fPrice3[i] = m_sdCurrData[i].close / 100.0 / 4 - m_sdCurrData[i].close / 100 / 4
            + m_sdCurrData[i].close / 100 / 4 % 4;
        fPrice4[i] = m_sdCurrData[i].close / 100.0 / 5 - m_sdCurrData[i].close / 100 / 5
            + m_sdCurrData[i].close / 100 / 5 % 4;
    }
 
    for (i=0; i<days-1; i++)
    {
        pDC->SelectObject(&penWhite);
        pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice1[i]);
        pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice1[i+1]);
        pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice1[i]);
        pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice1[i+1]);
 
        pDC->SelectObject(&penYellow);
        pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice2[i]);
        pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice2[i+1]);
        pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice2[i]);
        pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice2[i+1]);
 
        pDC->SelectObject(&penRedSolid);
        pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice3[i]);
        pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice3[i+1]);
        pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice3[i]);
        pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice3[i+1]);
 
        pDC->SelectObject(&penGreen);
        pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice4[i]);
        pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice4[i+1]);
        pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice4[i]);
        pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice4[i+1]);
    }
    DrawDateText(pDC);
    //释放动态分配的内存
    delete fiveAverData;
    delete tenAverData;
    delete fPrice1;
    delete fPrice2;
    delete fPrice3;
    delete fPrice4;
    //还原绘画环境
    pDC->SelectObject(pOldPen);
    pDC->SelectObject(pOldBrush);
}

说明:上面最麻烦的是数据的处理,如果处理不好就不能把图画到合适的位置,所以计算的时候一定要用float,不然就算是很小的数据差别都会造成绘图不到指定的位置。还有这个函数代码量比较多,是因为绘图都需要用到平均每天的宽度和一些公用的参数,所以就在一个函数里完成了。其实可以写成很多个函数模块,比如每一个部分可以写成一个函数,然后数据计算可以用专门的函数封装。

(3)补充功能,在最下面显示当前鼠标对应的日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
* 函数名称: DrawDateText
* 输 入:  pDC
*   pDC:    绘画用的CDC指针
* 输 出:
* 功能描述: 显示日期
* 全局变量:
* 作 者:  吴友强
* 日 期:  2009年12月02日
* 修 改:
* 日 期:
*/
void CTongXinDaView::DrawDateText(CDC *pDC)
{
    CRect rt;
    GetClientRect(&rt);
    float xAver;
    CString strDate;
    xAver = (rt.Width()-100) / (float)m_iDays;
                 
    for (int i=0; i<m_iDays; i++)
    {
        if ((m_ptDatePoint.x > xAver * i) && (m_ptDatePoint.x < (xAver * i+xAver / 3 *2)))
        {
            m_iDateCount = i;
        }
    }
    CPen pen(PS_SOLID, 1, RGB(200, 0, 0));
    CPen *pOldPen = pDC->SelectObject(&pen);
    CBrush brushBlue(RGB(0, 0, 150));
    CBrush *pOldBrush = pDC->SelectObject(&brushBlue);
    pDC->SetTextColor(RGB(200, 200, 200));
    pDC->MoveTo(0, rt.Height()-15);
    pDC->LineTo(rt.Width()-100, rt.Height()-15);
    strDate.Format("%d", m_sdCurrData[m_iDateCount]);
    strDate.Insert(4, '/');
    strDate.Insert(7, '/');
    pDC->Rectangle(m_ptDatePoint.x, rt.Height()-15, m_ptDatePoint.x+75, rt.Height());
    pDC->DrawText(strDate, CRect(m_ptDatePoint.x, rt.Height()-15, m_ptDatePoint.x+75, rt.Height()),                                  DT_LEFT);
    pDC->SelectObject(&pOldPen);
    pDC->SelectObject(&pOldBrush);
}

说明:为了显示出这个日期,需要记录当前屏幕绘画了哪些天的图,然后根据鼠标的坐标位置判断处于哪一天并显示出来。

(4)图形放大缩小以及左右平移的实现思路:

其实原理都是一样的,就是根据需要的天数显示,放大就是显示的天数比较少,缩小就是多显示一些天数,左右移动就是重新提取一些数据。显示的天数可以根据需要按照固定需要按固定的比例放大和缩小。然后就是通过一些按键来控制或是鼠标控制,其中还有一个辅助线的设置比较靠逻辑,需要细心才能弄好。具体的请参看代码,有注释。

3.心得体会:

刚开始的时候感觉什么都不会,但是我还是勇敢地迈出了自己的第一步,因为在这几年的学习过程中我发现自己解决问题的能力提升了很多,所以相信自己能够完成。然后自己就静下心来一行一行代码的写,经过几天努力,完成了大部分功能,自己的信心也是越来越强。现在这个程度还算过的去了吧。

4.存在的不足:

由于对于股票行业术语不是很了解,很多变量的命名不是很合理和规范,代码的组织也不是很好。

5.得到的帮助与指导:

感谢老师提供的思路,特别是对于所谓的图形放大和缩小,左右移动,老师的提醒使我恍然大悟,以后基本上没有什么太大的困难。就是数据的提取问题。

 

posted @   蔷薇理想人生  阅读(4835)  评论(2编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示