上回做了個簡單的部落格園精華用戶端,還挺實用的,現在打算做一個複雜點的應用。額,大家都有學習理财知識嗎,我是有玩股票分級的,我的終極目标是實作機器自動買賣股票,奈何這個目标暫時還是挺難實作的,那先簡單點吧。其實股票小助手和分級基金小助手我都有做,項目位址是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看吧