上回做了个简单的博客园精华客户端,还挺实用的,现在打算做一个复杂点的应用。额,大家都有学习理财知识吗,我是有玩股票分级的,我的终极目标是实现机器自动买卖股票,奈何这个目标暂时还是挺难实现的,那先简单点吧。其实股票小助手和分级基金小助手我都有做,项目地址是https://github.com/ihambert/test,由于股票逆大盘的太多,所以我觉得分级基金小助手更有参考价值,本文就简绍我的分级基金小助手吧。先来个效果图吧
那需求有哪些呢,首先,我需要把想要监控的分级基金都监控起来,监控哪些信息呢,比如当前价,最高价,最低价,涨幅等。其次,我需要把想要监控的行业和概念主力资金动向,为毛要监控这个呢,因为大家都知道,咱散户的力量是分散无序的,只有主力才能有效的拉动股价,所以主力资金的动向可作为我们的买卖依据,比如总体来看主力都在拼命跑路了,你还在抄底,,那哭可没人可怜,主力都在拼命买入银行了,你还说你从不买银行,那不是你傻吗。最好可以生成日志,如下图
这里暂时有3个关键字,抛吸和抄,抛的意思是建议高抛一部分,吸的意思是建议低吸一部分,抄的意思是在大盘主力资金不明显流动的情况下,个别股票异动所带来的抄底机会,一般情况下大盘主力资金流出-40亿的情况下建议稳健者空仓,此时出现的买入机会只适合激进者玩以此类推,每隔10亿可以自定义一个仓位,比如流入30亿我就半仓或全仓。
先不管怎么耍这玩意吧,研究一下怎么实现比较务实。老套路,首先准备基础类:
日志类
public static class Log { /// <summary> /// 记录异常信息 /// </summary> /// <param name="msg">异常附加信息</param> /// <param name="e">异常</param> public static void Error(string msg, Exception e) { if (e == null) { Warn(msg); } else { var innerEx = e.InnerException == null ? string.Empty : $",InMessage:{e.InnerException.Message},InSource:{e.InnerException.Source},InStackTrace:{e.InnerException.StackTrace}"; Logger(FileError, $"{msg},Message:{e.Message},Source:{e.Source},StackTrace:{e.StackTrace}{innerEx}"); } } /// <summary> /// 记录警告信息 /// </summary> /// <param name="msg">警告内容</param> public static void Warn(string msg) { Logger(FileWarn, msg); } /// <summary> /// 记录普通信息 /// </summary> /// <param name="msg">一般信息</param> public static void Info(string msg) { Logger(FileInfo, msg); } /// <summary> /// 记录调试信息 /// </summary> /// <param name="msg">调试信息</param> public static void Debug(string msg) { Logger(FileDebug, msg); } private static void Logger(string fileName, string msg) { //开新线程写日志不阻塞原线程(虽然也无需多长时间) Task.Factory.StartNew(() => { msg = $"{DateTime.Now:yy-M-d H:m:s}:{msg}{Environment.NewLine}"; if (!Directory.Exists(FileBase)) Directory.CreateDirectory(FileBase); try { //加锁排队是必须的,否则快速插入日志的情况下会出现异常 lock (fileName) { File.AppendAllText(fileName, msg); } } catch { //发生异常一般是文件被占用,先写到另一个文件吧 File.AppendAllText(fileName.Replace(".txt", "2.txt"), msg); } }); } #region 常量 private const string FileBase = "File/Log"; //以一个月对一个单位,每个月生成一个文件 private static readonly string FileError = "File/Log/Error" + DateTime.Now.ToString("yyMM") + ".txt"; private static readonly string FileWarn = "File/Log/Warn" + DateTime.Now.ToString("yyMM") + ".txt"; private static readonly string FileInfo = "File/Log/Info" + DateTime.Now.ToString("yyMM") + ".txt"; private static readonly string FileDebug = "File/Log/Debug" + DateTime.Now.ToString("yyMM") + ".txt"; #endregion }
这个其实是简易日志类,这种小项目还不需要log4net,nlog那些,自己写一个岂不美哉,我这个日志类是静态类来的,用起来很方便,但为毛log4net,nlog那些大佬都不用静态类呢,估计是因为他们需要多种日志类,比如网站的日志类,控制台的日志类等,需要不同的配置文件,读写路径也是可配置的,我这里路径就写死算了,反正自己用。。。用法的话非常easy。
Log.Debug($"主线程ID:{Thread.CurrentThread.ManagedThreadId}");
Log.Info(tip);
Log.Error("异常啦", _tasks[i].Exception);
Log.Warn("空数据");
介绍完毕。
接下来进入实盘演练。首先需要准备Stock股票类,GradingFund基金类,Industry行业概念类,GradingFundData数据类,数据类存放前面几个实体类,用于数据服务类和窗体之间的通信,当然,这些可以抽离出来,也可以用于网站的。
那么窗体和数据服务类之间是如何交互的呢,我是用委托事件来实现的,如上图,主窗体new一个数据服务类,然后用一个新委托来处理更新事件取得的数据,但由于窗体的主线程才能操作控件,所以需要用BeginInvoke来返回主线程,看Debug日志
看线程ID,GradingFundUtil主线程ID居然和窗体主线程是同一条线程,,难怪数据服务类不用新线程的话窗体会卡死啦,所以我在数据服务类里面new了一个新线程,每分钟循环获取数据一次
当然,每分钟获取数据最好都是同一个线程啦,然后把那条晾起来一分钟,看看大盘现在在交易时间不,是开盘时间则更新数据,否则继续晾起来一分钟。
当然,如何快速获取数据是一个问题,我采用HttpClient,
//概念数据耗时大概188ms-290ms var taskConcept = _web.GetStringAsync(UrlConcept); //行业数据耗时大概60ms-188ms var taskIndustry = _web.GetStringAsync(UrlIndustry); for (var i = 0; i < _data.Stocks.Count; i += RequsetStockCount) { var url = UrlStock + string.Join(",", _data.Stocks.Skip(i).Take(RequsetStockCount).Select(o => o.Code)); //股票数据耗时大概86ms _tasks.Add(_web.GetStringAsync(url)); } //分级数据耗时大概82ms var taskGradings = _web.GetStringAsync(UrlGradingFunds);
先一次性把URL都发出异步请求,然后先处理耗时低的请求,举个例子,关于如何处理股票数据
#region 处理股票实时数据 for (var i = 0; i < _tasks.Count; i++) { if (_tasks[i].IsFaulted) { Log.Error("金融界股票接口", _tasks[i].Exception); ContinueUpdate(); return; } try { html = _tasks[i].Result; } catch (Exception e) { Log.Error("金融界股票接口异常", e); ContinueUpdate(); return; } var hq = StringUtil.GetVal(html, "HqData:[", "]}"); var fs = StringUtil.GetList(hq, "[", "]"); if (fs.Count == 0) { Log.Warn("金融界股票接口获取空数据"); ContinueUpdate(); return; } var ii = i*RequsetStockCount; foreach (var f in fs) { var a = f.Split(','); var stock = _data.Stocks[ii++]; stock.Name = a[0].Substring(1, a[0].Length - 2); stock.YesterdayPrice = Convert.ToSingle(a[1]); stock.StartPrice = Convert.ToSingle(a[2]); stock.MaxPrice = Convert.ToSingle(a[3]); stock.MinPrice = Convert.ToSingle(a[4]); stock.Price = Convert.ToSingle(a[5]); stock.Tm = float.Parse((Convert.ToSingle(a[6])/10000).ToString("F1")); stock.Cat = Convert.ToSingle(a[7]); stock.Tr = Convert.ToSingle(a[8]); stock.Ape = Convert.ToSingle(a[9]); var rate = (stock.Price - stock.YesterdayPrice)*100/stock.YesterdayPrice; stock.Speed = Math.Round(rate - stock.Rate, 2); stock.Rate = Math.Round(rate, 2); stock.Swing = Math.Round((stock.MaxPrice - stock.MinPrice)*100/stock.YesterdayPrice, 2); } } _tasks.Clear(); if (_data.IsOpen) { //删除停牌个股 var rc = _data.Stocks.RemoveAll(o => o.MaxPrice == 0); if (rc > 0) { foreach (var fund in _data.GradingFunds) { fund.Stocks.RemoveAll(o => o.MaxPrice == 0); } } } #endregion
用异步多线程获取数据这样的话获取数据就快啦。
关于本程序需要的文件
其中GradingFunds.txt中存放需要检测的分级基金,一行一个
程序运行后会自动填充右边的一大把信息,这些信息其实是该分级的10大重仓股和对应的仓位。这些数据有助于作为该分级的买卖依据。
还有个Industry.txt存放需要监测的行业概念数据,第一行存放行业数据,第二行存放概念数据,这个已经有默认数据了,一般只存放和分级相关的行业概念,本行业概念数据来源于东方财富行业资金流向图还有东方财富概念资金流向图,以后也可能使用同花顺的数据。
日志是自动生成的
其中Info用于记录大盘资金流向和买卖推荐,以后可以对着这个来看数据是否有用,作为优化依据。Error记录异常信息。1701代表2017年一月份。
额,说的有点乱,大家感兴趣的话还是去我的github看吧
所有评论(0)