效果展示,如下动图:
首先简述一下股票交易规则:
- 买卖股票,股民可以自行选择股票的买入或卖出价格和股票的数量,但是用户不一定马上就交易成功,只有当股票价格低于买入价才有机会买入,高于卖出价才有机会卖出。所以需要做委托处理。所以我们通常说的是委托下单,而不是说下单。
- 股票买卖以100股为单位,100股也称为1手。
- 同一股票可以多次买入,可以多次卖出,但是卖出的总股数不能高于买入的总股数,并且需要在资金的支持下才能进行。
- 在交易时间的时候,股票的价格是随时波动的
- 等待交易的卖家的委托价格都要比现价高,等待交易的买家的委托价格都要比现价低,
- 红色(涨):当股票价格比前一交易日的收盘价高时,通常用红色来表示涨幅。
- 绿色(跌):当股票价格比前一交易日的收盘价低时,通常用绿色来表示跌幅。
- 细节:如果股票原来为红色,股票下跌却又没有接到前一天的收盘价低的时候,会变为绿色,然后又转变为红色。绿股同理。
- 然后还有总资产与可用资产的区别,总资产不一定都可用。
- 可用资产:可用资产是总资产减去委托买入所损耗的金额,无论交易成功与否。
- 总资产: 总资产是在交易成功之后,各股的涨幅乘买入金额的和再加上可用资产的值
- 仓位:指的是的支出资金占总资金的百分比,并且指的是交易成功之后的支出资金,而不是委托状态的支出资金。
- 盈率:盈率=(成本价-现价)/成本价*100% 只有在交易成功后才涉及到,并且会随着现价的波动而波动。
- 总盈率:用户买入股票后单股的盈率之和。
-
系统功能:
1.程序启动后给用户提供100w的资金,进行模拟炒股
2.用户可以通过输入股票代码查看股票详细信息
3.用户可以对股票进行买进,卖出
4.查看委托状态操作
5.查看仓库状态操作
6.用户可以对自选股进行增删查
7.用户可以通过键盘上的上下键来控制“→”选择操作,回车键确认
8.用户可以对股票进行排序,通过键盘上的上下键来控制“↓ ↓”选择需要排序的数据,提供现价和涨幅的排序,回撤一次,股票进行降序排序,再次回撤进行升序排序,循环往复直到把箭头移走。
设计概要:
1.窗口以及光标设置
1.1.设计合适的窗口大小
1.2.设置窗口名称
1.3.封装光标隐藏函数和光标显示函数
1.4.封装光标坐标设置函数--SetPos
2.股票信息获取
因为股票信息量一些大,所以我们可以通过先把它写到文件里然后从文件里读取,该片文章的股票信息参考是6月9日的股票市场。
3.交易页面设计
我们可以做一个小区间内的随机数,让股票价格在一个小范围内波动,该系统比较简洁,就不去考虑股票前一天的收盘价了,我们直接以现价与开盘价做比较,现价高于开盘价就呈现红色,反之为绿色。并且提供菜单。
4.主逻辑
设计两条线程,一条做股票的界面展示,一条做背后的主逻辑,我在在这简称为打印线和逻辑线,逻辑线接收用户的信息并做处理,并反馈给打印线(通过共用资源)。两个线程互相配合完成工作。
5.头文件的声明
5.1.股票信息:使用用一个结构体来维护,成员包括,股票名称,股票代码,开盘价,现价,涨幅等等。
5.2.委托状态:设计到状态的处理,使用枚举类型更为适应,成员包括,未委托,委托待买入,委托待卖出,买入交易成功,卖出交易成功。
5.3.持股信息:持股信息使用结构体来维护更为适应,并且成员需要有股票信息,然后做成链表。
5.4.仓库信息: 使用结构体维护成员包括:总资产,可用资产,总盈率,仓位和持股信息
5.6头文件的部分声明:
编译环境的配置
一、手搓股票信息
股票信息量比较大,考虑把它放到文件里,然后运行程序的时候再从仓库里取,如下:
每一支股票的信息我们用一个结构体类型来储存,然后再把所有股票放在一个结构体数组里面,因为涉及到股票信息在整个工程中会被频繁使用,所以把它做成全局变量,数组成员个数size随时会改变,所以用一个#define在头文件定义。要注意以下一点:
向文件里读取信息:
FILE* pf = fopen("stcok.txt", "r");if (!pf){perror("fopen");return 1;}for (int i = 0; i < size; i++){fscanf(pf, "%s %s %f %f %f %f %f %f %f %f %f %f",arr[i].name, arr[i].code, &arr[i].price, &arr[i].begin,&arr[i].max, &arr[i].min, &arr[i].amount, &arr[i].rate,&arr[i].sumh, &arr[i].money, &arr[i].value, &arr[i].ratio);}
二、交易页面设计
1.函数封装
color函数,SetPos函数,FCursor函数,OCursor函数的封装:
void FCursor()//关闭光标
{HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO Info;GetConsoleCursorInfo(houtput, &Info);Info.bVisible = false;SetConsoleCursorInfo(houtput, &Info);
}
void OCursor()//显示光标
{HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO Info;GetConsoleCursorInfo(houtput, &Info);Info.bVisible = true;SetConsoleCursorInfo(houtput, &Info);
}
void color(int x)//设置控制台颜色
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);
}
void SetPos(int x, int y)//设置光标的坐标
{HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//GetStdHandle获取柄,HANDLE接收柄COORD pos = { x,y };//坐标SetConsoleCursorPosition(houtput, pos);
}
2.线程创建:
//将仓库信息和持股信息封装送入线程中HoldStock* head = NULL;Warehouse wh = { 1000000,1000000,0,100.00,NULL };HW pw;pw.r = 1, pw.hs = &head, pw.ws = &wh;HANDLE thp1 = NULL, thp2 = NULL;// 初始化临界区InitializeCriticalSection(&cs);// 创建线程thp1 = CreateThread(NULL, 0, Print,(LPVOID)&pw, 0, NULL);//这里不能把pw直接传过去!!thp2 = CreateThread(NULL, 0, Run, (LPVOID)&pw, 0, NULL);//if (!thp1 || !thp2){exit(-1);}// 等待线程结束WaitForSingleObject(thp1, INFINITE);WaitForSingleObject(thp2, INFINITE);// 销毁临界区DeleteCriticalSection(&cs);// 关闭线程句柄CloseHandle(thp1);CloseHandle(thp2);
接下来进入打印线,进入打印线后先给数据解封装,因为考虑到股票信息会在整个工程中打印输出,所有先直接做一个死循环,并且在开头和结尾上锁和解锁。其他问题先不考虑。
在输出前先要设置光标位置,以便信息在准确的位置输出,SetPos用来设置光标信息,color用来设置控制台颜色。
2.价格波动模拟
这个当然需要使用随机数来获取,举一个例子如下,这只是个例子还可以用其它方法来模拟。
srand((unsigned int)time(NULL));for (int i = 0; i < size; i++){int u = rand() % 5;switch (u){case 0:break;case 1:arr[i].price += 0.01;break;case 2:arr[i].price += 0.1;break;case 3:arr[i].price -= 0.01;break;case 4:arr[i].price -= 0.1;break;}arr[i].amount = (arr[i].price - arr[i].begin) / arr[i].begin * 100;if (arr[i].price > arr[i].max)arr[i].max = arr[i].price;if (arr[i].price < arr[i].min)arr[i].min = arr[i].price;}
其它信息的输出和这个类似,这里就不再展示。
三、菜单设计--menu()
- 买入
- 卖出
- 查看股票
- 添加自选股
- 删除自选股
- 查看自选股
- 查看委托
- 查看仓库
菜单,给用户提供8个操作,得用户选定后用switch语句进行分开处理,其中自选股的添加删除查找操作比较简单,就是链表的增删查,对这三个点就不再过多叙述。查看股票的话我们可以通过用户输入个证劵代码去查找对应股票的数组下标,然后利用双线程资源共享的特点把输出下标进行更改。接下来看主要来分析一下其它操作。
当然除此之外还有排序功能,这个可以用qsort函数去处理,这样比较高效。
四、链表设计及更新--List
为了方便,考虑创建一个链表来做数据的储存,对于自选股的增删查使用链表再合适不过了,而这个我们可以把自选股和交易股做成同一支链表。
有的人可能会想,自选股也可能是交易股啊!对,不过没关系。添加自选股的时候我们进行插入节点。进行委托下单的时候也插入节点。而委托状态就可以区分他们。自选股的委托状态一定是未委托,而交易股委托状态一定不是未委托。
链表的更新,因为链表里储存的是交易股和自选择股,股票的价格涨幅等等都会随时变化,所以需要随时的更新,因为股票信息的数组是全局变量,只需要封装一个函数把对应的股票赋上新的值。
五、买入处理--Buy()
在买入处理函数中需要做的事如下:
- 判断股票是否存在。
- 判断可用金额是否充足
- 判断是否是以100股为单位买入
如果用户输入不符合交易规则,要做相应的提示输出。并且需要用户重新输入
符合交易规则后的处理:
- 将委托状态置为委托待买入。
- 将买入价存储到持股信息中。
- 将买入股数储到持股信息中。
- 可用资金减掉本次交易的资金量。
最后反馈给客户委托成功。
六、卖出处理--Sell()
在买入处理函数中需要做的事如下:
- 判断该支股票是否被买入(委托状态是否为成功买入)。
- 判断持有股数是否充足
- 判断是否是以100股为单位卖出
如果用户输入不符合交易规则,要做相应的提示输出。并且需要用户重新输入
符合交易规则后的处理:
- 将委托状态置为委托待卖出。
- 将卖出价存储到持股信息中。
- 将卖出股数储到持股信息中。
最后反馈给客户委托成功。
七、处理委托--EnDealt()
处理买入:如果现价比卖入价低,交易成功
处理卖出:如果卖出价比现价高,交易成功,成功卖出后可用资产需要增加,并且记录盈率,因为卖出股票后它的盈率就不随现价的波动而波动,可能会丢失导致后面的总资产无法准确计算,所以需要记录。
代码如下:
void EnDealt(HoldStock* ch,Warehouse* cw)//处理委托
{//不用保存ch的初始值,因为它只是临时变量while (ch){if (ch->en == BuyWait && ch->stk.price < ch->cost)//现价比卖入价低,交易成功ch->en = BuyOK;if (ch->en == SellWait && ch->stk.price <ch->out)// 现价比卖出价高,交易成功{ch->pf = (ch->out-ch->cost) / ch->cost * 100;//计算盈率cw->own += ch->pf / 100 * ch->cost * ch->sm;//计算可用资产ch->en = SellOK;//交易成功}ch = ch->next;}
}
委托展示--EnShow()
在写这个函数要注意一个细节,先需要思考一下需要展示的是哪些股票信息?只要不是未委托的股票都需要展示,而委托卖出,和卖出成功的股票是需要打印两次的,因为委托卖出和卖出成功的股票一定是买入成功的,也需要打印,而委托状态只能储存一个状态。而这个问题可以用一个新加一个un变量来解决。
void EnShow(HoldStock* head)//展示委托//!!
{SetPos(38, 22);printf(" 代码 名称 现价 涨幅");int i = 1, un = 1;while (head){if (head->en != NO){SetPos(38, 22 + i);printf("%s %s", head->stk.code, head->stk.name);head->stk.amount > 0 ? color(116) : color(114); printf("%7.2f %.2f%% ", head->stk.price, head->stk.amount); color(112);if (un==1){SetPos(69, 22 + i); printf("买:%.2f", head->cost);}else{SetPos(69, 22 + i); printf("卖:%.2f", head->out);}SetPos(78, 22 + i);if (un == 1){if (head->en == BuyWait){color(121); printf("待买入 ");}elsecolor(124); printf("交易成功");SetPos(87, 22 + i); printf("数量:%d股", head->sm); color(112);}else{if (head->en == SellWait){color(121); printf("待卖出 ");}elsecolor(124); printf("交易成功");SetPos(87, 22 + i); printf("数量:%d股", head->sel); color(112);}i++;if (head->en == SellWait || head->en == SellOK)//让要卖出的股票打印两次un = un == 0 ? 1 : 2;}if (un==2)un = 0;elsehead = head->next;}
}
八、更新仓库--WaDealt()
需要对总资金,总盈率,仓位进行计算。而可用金额已经在买入处理,和委托处理中计算了,在这里不用处理。
这里为了这方便。不用考虑原资产。原仓位。和总盈率。直接把它们都初始化一次重新开始计算。注意在计算总资产的时候要将卖出的股票的盈利加上
void WaDealt(Warehouse* cw, HoldStock* ch)//仓库调整
{float cv = 1000000.00;//总资产float prosum = 0.0;//总盈率float cu = 0.0;//仓位while (ch){if (ch->en==BuyOK){cv += (ch->stk.price - ch->cost) * ch->sm;prosum += (ch->stk.price - ch->cost)/ch->cost * 100;cu += ch->cost * ch->sm;}if (ch->en == SellOK){cv += ch->pf / 100 * ch->cost * ch->sm;prosum += ch->pf;}ch = ch->next;}cw->coin = cv;cw->profit = prosum;cw->place = cu/10000;
}
仓库展示--WaShow()
相比委托的展示,仓库的展示还比较简单,不过要注意一点,卖完的股票就不用展示就不用展示。如下:
void WaShow(Warehouse* ck, HoldStock* head)
{color(112); SetPos(34, 22); printf("仓库");SetPos(34, 23); ck->coin >= 1000000 ? color(116) : color(114); printf("总资产:%.2f ", ck->coin); color(112);ck->profit >= 0 ? color(116) : color(114); printf("盈率:%.2f%% ", ck->profit); color(112); printf("仓位:%.2f%% ", ck->place);printf("可用资产:%.2f ", ck->own);HoldStock* ph = head;SetPos(34, 25);printf(" 代码 名称 现价 / 成本 涨幅 ");int i = 1;while (ph){if (ph->en == BuyOK|| ph->en == SellWait||(ph->en==SellOK&&ph->sm-ph->sel>=100)){SetPos(34, 25 + i);printf("%s %s", ph->stk.code, ph->stk.name);ph->stk.amount > 0 ? color(116) : color(114); printf("%8.2f/", ph->stk.price); color(112);printf("%.2f ", ph->cost);ph->stk.amount >= 0 ? color(116) : color(114); printf("%.2f%% ", ph->stk.amount); color(112);//SetPos(68, 25 + i);//ph->stk.money > 1000 ? printf("%.2f万", ph->stk.money) : printf("%.2f亿 ", ph->stk.money);ph->cost <= ph->stk.price ? color(116) : color(114); printf("盈率:%.2f%% ", (ph->stk.price - ph->cost) / ph->cost * 100); color(112);color(121); printf("持股:%d ", ph->sm-ph->sel); color(112);i++;}ph = ph->next;}
}
源码:由于源码代码量比较大,就不放在文章中,如果需要请私信我。