/// <summary> /// 取出1分钟数据的时间,开收盘价,高低价,成交量等信息输入该图 /// </summary> private void Form_Load(object sender, EventArgs e) { //画一张大图,包含价格K线和成交量 MasterPane myPaneMaster = zedG.MasterPane; myPaneMaster.Title.Text = secCode; myPaneMaster.Title.FontSpec.FontColor = Color.Black; //PaneMaster里面画一张价格的小图 GraphPane panePrice = new GraphPane(new Rectangle(10, 10, 10, 10), "Mes", " t ( h )", "Rate"); myPaneMaster.PaneList[0] = (panePrice); //PaneMaster里面画一张成交量的小图 GraphPane paneVolume = new GraphPane(new Rectangle(10, 10, 10, 10), "Mes", " t ( h )", "Rate"); myPaneMaster.PaneList.Add(paneVolume); //蜡烛线例子 //设置名称和坐标轴 panePrice.Title.Text = "K线图"; panePrice.XAxis.Title.Text = "日期"; panePrice.XAxis.Title.FontSpec.FontColor = Color.Black; panePrice.YAxis.Title.Text = "价格"; panePrice.YAxis.Title.FontSpec.FontColor = Color.Black; //spl装载时间,价格数据 StockPointList spl = new StockPointList(); Random rand = new Random(); //将系统时间转化为xDate时间 XDate xStart = XDate.DateTimeToXLDate(startTime); XDate xEnd = XDate.DateTimeToXLDate(endTime); //取Sec的分钟数据,存储于data中 List <DateTime> tradeDays = DateUtils.GetTradeDays(startTime, endTime); //数据准备,取minute数据,然后再将数据进行转换为各个频率 Dictionary <string, List <KLine> > data = new Dictionary <string, List <KLine> >(); foreach (var tempDay in tradeDays) { var stockData = Platforms.container.Resolve <StockMinuteRepository>().fetchFromLocalCsvOrWindAndSave(secCode, tempDay); if (!data.ContainsKey(secCode)) { data.Add(secCode, stockData.Cast <KLine>().ToList()); } else { data[secCode].AddRange(stockData.Cast <KLine>().ToList()); } } //定义变量存储分钟数据 Dictionary <string, List <KLine> > minuteData = new Dictionary <string, List <KLine> >(); foreach (var variety in data) { minuteData.Add(variety.Key, data[variety.Key]); } //定义成交量 double[] volume = new double[minuteData[secCode].Count]; //根据频率选择累加的时间 switch (frequency) { //取tick数据 case 0: log.Info("暂时没有tick数据"); break; //1min K线 case 1: for (int i = 0; i < minuteData[secCode].Count; i++) { double timePoint = i; double open = minuteData[secCode][i].open; double close = minuteData[secCode][i].close; double high = minuteData[secCode][i].high; double low = minuteData[secCode][i].low; volume[i] = minuteData[secCode][i].volume; StockPt pt = new StockPt(timePoint, high, low, open, close, volume[i]); spl.Add(pt); // 时间加1分钟 xStart.AddMinutes(1.0); // but skip the weekends if (XDate.XLDateToDayOfWeek(xStart.XLDate) == 6) { xStart.AddDays(2.0); } } break; //显示5min K线 case 2: Dictionary <string, List <KLine> > minuteData5Min = new Dictionary <string, List <KLine> >(); foreach (var variety in data) { List <KLine> data5K = new List <KLine>(); data5K = MinuteFrequencyTransferUtils.MinuteToNPeriods(minuteData[variety.Key], "Minutely", 5); minuteData5Min.Add(variety.Key, data5K); } for (int i = 0; i < minuteData5Min[secCode].Count; i++) { double timePoint = i; double open = minuteData5Min[secCode][i].open; double close = minuteData5Min[secCode][i].close; double high = minuteData5Min[secCode][i].high; double low = minuteData5Min[secCode][i].low; volume[i] = minuteData[secCode][i].volume; StockPt pt = new StockPt(timePoint, high, low, open, close, volume[i]); spl.Add(pt); // 时间加5分钟 xStart.AddMinutes(5.0); // but skip the weekends if (XDate.XLDateToDayOfWeek(xStart.XLDate) == 6) { xStart.AddDays(2.0); } } break; //显示15min K线 case 3: Dictionary <string, List <KLine> > minuteData15Min = new Dictionary <string, List <KLine> >(); foreach (var variety in data) { List <KLine> data15K = new List <KLine>(); data15K = MinuteFrequencyTransferUtils.MinuteToNPeriods(minuteData[variety.Key], "Minutely", 15); minuteData15Min.Add(variety.Key, data15K); } for (int i = 0; i < minuteData15Min[secCode].Count; i++) { double timePoint = i; double open = minuteData15Min[secCode][i].open; double close = minuteData15Min[secCode][i].close; double high = minuteData15Min[secCode][i].high; double low = minuteData15Min[secCode][i].low; volume[i] = minuteData[secCode][i].volume; StockPt pt = new StockPt(timePoint, high, low, open, close, volume[i]); spl.Add(pt); // 时间加15分钟 xStart.AddMinutes(15.0); // but skip the weekends if (XDate.XLDateToDayOfWeek(xStart.XLDate) == 6) { xStart.AddDays(2.0); } } break; //显示30min K线 case 4: Dictionary <string, List <KLine> > minuteData30Min = new Dictionary <string, List <KLine> >(); foreach (var variety in data) { List <KLine> data30K = new List <KLine>(); data30K = MinuteFrequencyTransferUtils.MinuteToNPeriods(minuteData[variety.Key], "Minutely", 30); minuteData30Min.Add(variety.Key, data30K); } for (int i = 0; i < minuteData30Min[secCode].Count; i++) { double timePoint = i; double open = minuteData30Min[secCode][i].open; double close = minuteData30Min[secCode][i].close; double high = minuteData30Min[secCode][i].high; double low = minuteData30Min[secCode][i].low; volume[i] = minuteData[secCode][i].volume; StockPt pt = new StockPt(timePoint, high, low, open, close, volume[i]); spl.Add(pt); // 时间加30分钟 xStart.AddMinutes(30.0); // but skip the weekends if (XDate.XLDateToDayOfWeek(xStart.XLDate) == 6) { xStart.AddDays(2.0); } } break; //显示60min K线 case 5: Dictionary <string, List <KLine> > minuteData60Min = new Dictionary <string, List <KLine> >(); foreach (var variety in data) { List <KLine> data60K = new List <KLine>(); data60K = MinuteFrequencyTransferUtils.MinuteToNPeriods(minuteData[variety.Key], "Minutely", 60); minuteData60Min.Add(variety.Key, data60K); } for (int i = 0; i < minuteData60Min[secCode].Count; i++) { double timePoint = i; double open = minuteData60Min[secCode][i].open; double close = minuteData60Min[secCode][i].close; double high = minuteData60Min[secCode][i].high; double low = minuteData60Min[secCode][i].low; volume[i] = minuteData[secCode][i].volume; StockPt pt = new StockPt(timePoint, high, low, open, close, volume[i]); spl.Add(pt); // 时间加60分钟 xStart.AddMinutes(60.0); // but skip the weekends if (XDate.XLDateToDayOfWeek(xStart.XLDate) == 6) { xStart.AddDays(2.0); } } break; //显示日K线 case 6: Dictionary <string, List <KLine> > minuteDataDaily = new Dictionary <string, List <KLine> >(); foreach (var variety in data) { List <KLine> dataDaily = new List <KLine>(); dataDaily = MinuteFrequencyTransferUtils.MinuteToNPeriods(minuteData[variety.Key], "Minutely", 240); minuteDataDaily.Add(variety.Key, dataDaily); } for (int i = 0; i < minuteDataDaily[secCode].Count; i++) { double timePoint = i; double open = minuteDataDaily[secCode][i].open; double close = minuteDataDaily[secCode][i].close; double high = minuteDataDaily[secCode][i].high; double low = minuteDataDaily[secCode][i].low; volume[i] = minuteData[secCode][i].volume; StockPt pt = new StockPt(timePoint, high, low, open, close, volume[i]); spl.Add(pt); // 时间加1天 xStart.AddDays(1.0); // but skip the weekends if (XDate.XLDateToDayOfWeek(xStart.XLDate) == 6) { xStart.AddDays(2.0); } } break; } //添加栅格线 //myPane.XAxis.MajorGrid.IsVisible = true; //myPane.YAxis.MajorGrid.IsVisible = true; //myPane.XAxis.MajorGrid.Color = Color.LightGray; //myPane.YAxis.MajorGrid.Color = Color.LightGray; //myPane.YAxis.MajorGrid.DashOff = 0; //myPane.XAxis.MajorGrid.DashOff = 0; panePrice.XAxis.Type = AxisType.Date; panePrice.XAxis.Scale.Format = "MM-dd"; //myPane.XAxis.Scale.FontSpec.Angle = 45;//X轴文字方向,0-90度 //开始Y轴坐标设置 ////设置Y轴坐标的范围 //myPane.YAxis.Scale.Max = Math.Round(maxhi * 1.2, 2);//Math.Ceiling(maxhi); //myPane.YAxis.Scale.Min = Math.Round(minlow * 0.8, 2); //Y轴最大刻度,注意minStep只会显示刻度线不会显示刻度值,minStep为纵坐标步长 panePrice.YAxis.Scale.MajorStep = 0.01; //myPane.XAxis.Scale.FontSpec.FontColor = Color.Black; //myPane.YAxis.Scale.FontSpec.FontColor = Color.Black; panePrice.XAxis.Type = AxisType.DateAsOrdinal; //myPane.Legend.FontSpec.Size = 18f; //myPane.Legend.Position = LegendPos.InsideTopRight; //myPane.Legend.Location = new Location(0.5f, 0.6f, CoordType.PaneFraction, // AlignH.Right, AlignV.Top); JapaneseCandleStickItem myCurve = panePrice.AddJapaneseCandleStick(secCode, spl); myCurve.Stick.IsAutoSize = true; //myCurve.Stick.Color = Color.Blue; myCurve.Stick.FallingFill = new Fill(Color.Green); //下跌颜色 myCurve.Stick.RisingFill = new Fill(Color.Red); //上扬颜色 // pretty it up a little //myPane.Chart.Fill = new Fill(Color.LightBlue, Color.LightGoldenrodYellow, 135.0f); //myPane.Fill = new Fill(Color.Orange, Color.FromArgb(220, 220, 255), 45.0f); Color c1 = ColorTranslator.FromHtml("#ffffff"); Color c2 = ColorTranslator.FromHtml("#ffd693"); panePrice.Chart.Fill = new Fill(c1); //图形区域颜色 panePrice.Fill = new Fill(c2); //整体颜色 //成交量线例子 // Set the Titles paneVolume.Title.Text = "成交量"; paneVolume.XAxis.Title.Text = "Time"; paneVolume.YAxis.Title.Text = "Volume Num"; // Make up some random data points //string[] labels = { "Panther", "Lion", "Cheetah","Cougar", "Tiger", "Leopard" }; //double[] y1 = { 100, 115, 75, 22, 98, 40, -100, -20 }; //double[] y2 = { 90, 100, 95, 35, 80, 35 }; //double[] y3 = { 80, 110, 65, 15, 54, 67 }; //double[] y4 = { 120, 125, 100, 40, 105, 75 }; // Generate a red bar with "Curve 1" in the legend BarItem myBar = paneVolume.AddBar(null, null, volume, Color.Red); //myBar.Bar.Fill = new Fill(Color.Red); // Generate a blue bar with "Curve 2" in the legend //myBar = paneVolume.AddBar("Curve 2", null, y2, Color.Blue); //myBar.Bar.Fill = new Fill(Color.Blue, Color.White, Color.Blue); //设置bar宽度 paneVolume.BarSettings.ClusterScaleWidth = 0.5; log.Info(paneVolume.BarSettings.GetClusterWidth()); paneVolume.BarSettings.Type = BarType.Cluster; // Generate a green bar with "Curve 3" in the legend //myBar = myPane.AddBar("Curve 3", null, y3, Color.Green); //myBar.Bar.Fill = new Fill(Color.Green, Color.White, // Color.Green); // Generate a black line with "Curve 4" in the legend //LineItem myCurve = myPane.AddCurve("Curve 4", //null, y4, Color.Black, SymbolType.Circle); //myCurve.Line.Fill = new Fill(Color.White, //Color.LightSkyBlue, -45F); // Fix up the curve attributes a little //myCurve.Symbol.Size = 8.0F; //myCurve.Symbol.Fill = new Fill(Color.White); //myCurve.Line.Width = 2.0F; // Draw the X tics between the labels instead of // at the labels paneVolume.XAxis.MajorTic.IsBetweenLabels = true; // Set the XAxis labels //myPane.XAxis.Scale.TextLabels = labels; // Set the XAxis to Text type paneVolume.XAxis.Type = AxisType.Text; // Fill the Axis and Pane backgrounds paneVolume.Chart.Fill = new Fill(Color.White, Color.FromArgb(255, 255, 166), 90F); paneVolume.Fill = new Fill(Color.FromArgb(250, 250, 255)); using (Graphics g = CreateGraphics()) myPaneMaster.SetLayout(g, 2, 0); zedG.AxisChange(); }
/// <summary> /// 50ETF择时策略测试,N-Days Reversion,单边多 /// </summary> public void compute() { log.Info("开始回测(回测期{0}到{1})", Kit.ToInt_yyyyMMdd(startDate), Kit.ToInt_yyyyMMdd(endDate)); ///账户初始化 //初始化positions SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions = new SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> >(); //初始化Account信息 BasicAccount myAccount = new BasicAccount(); myAccount.totalAssets = initialCapital; myAccount.freeCash = myAccount.totalAssets; myAccount.initialAssets = initialCapital; //记录历史账户信息 List <BasicAccount> accountHistory = new List <BasicAccount>(); //记录benchmark数据 List <double> benchmark = new List <double>(); ///数据准备 //交易日信息 List <DateTime> tradeDays = DateUtils.GetTradeDays(startDate, endDate); //50etf分钟数据准备,取全回测期的数据存放于data Dictionary <string, List <KLine> > data = new Dictionary <string, List <KLine> >(); foreach (var tempDay in tradeDays) { var ETFData = Platforms.container.Resolve <StockMinuteRepository>().fetchFromLocalCsvOrWindAndSave(targetVariety, tempDay); if (!data.ContainsKey(targetVariety)) { data.Add(targetVariety, ETFData.Cast <KLine>().ToList()); } else { data[targetVariety].AddRange(ETFData.Cast <KLine>().ToList()); } } //频率转换测试 List <KLine> data_5min = MinuteFrequencyTransferUtils.MinuteToNPeriods(data[targetVariety], "Minutely", 5); List <KLine> data_15min = MinuteFrequencyTransferUtils.MinuteToNPeriods(data[targetVariety], "Minutely", 15); //List<KLine> data_1Day = MinuteFrequencyTransferUtils.MinuteToNPeriods(data[targetVariety], "Daily", 1); //List<KLine> data_1Month = MinuteFrequencyTransferUtils.MinuteToNPeriods(data[targetVariety], "Monthly", 1); // List<KLine> data_1Week = MinuteFrequencyTransferUtils.MinuteToNPeriods(data[targetVariety], "Weekly", 1); int indexOfNow = -1; //记录整个data的索引,一分钟K线上的索引 int indexOf5min = 0; //五分钟K线上的索引 int indexOf15min = 0; //十五分钟K线上的索引 ///回测循环 //回测循环--By Day foreach (var day in tradeDays) { //取出当天的数据 Dictionary <string, List <KLine> > dataToday = new Dictionary <string, List <KLine> >(); foreach (var variety in data) { dataToday.Add(variety.Key, data[variety.Key].FindAll(s => s.time.Year == day.Year && s.time.Month == day.Month && s.time.Day == day.Day)); } int index = 0; //交易开关设置,控制day级的交易开关 bool tradingOn = true; //总交易开关 bool openingOn = true; //开仓开关 bool closingOn = true; //平仓开关 //是否为回测最后一天 bool isLastDayOfBackTesting = day.Equals(endDate); //回测循环 -- By Minute //不允许在同一根1minBar上开平仓 while (index < 240) { int nextIndex = index + 1; //更新索引值 indexOfNow++; //N分钟k线的当前索引值是当前时间的之前第一个完整的k线,为避免data snooping不取当前时间的索引 indexOf5min = indexOfNow / 5; indexOf15min = indexOfNow / 15; //实际操作从第一个回望期后开始 if (indexOfNow < lengthOfBackLooking - 1 || indexOf5min < lengthOfBackLooking - 1) { index = nextIndex; continue; } DateTime now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(day), index); Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); DateTime next = new DateTime(); myAccount.time = now; //int indexOfNow = data[targetVariety].FindIndex(s => s.time == now); double nowClose = dataToday[targetVariety][index].close; //五分钟反转点 double nowUpReversionPoint = ComputeReversionPoint02.findUpReversionPoint(data_5min, indexOf5min, NDays, lengthOfBackLooking); double nowDownReversionPoint = ComputeReversionPoint02.findDownReversionPoint(data_5min, indexOf5min, NDays, lengthOfBackLooking); if (nowDownReversionPoint < 0 || nowUpReversionPoint < 0) { index = nextIndex; continue; } try { //持仓查询,先平后开 //若当前有持仓 且 允许平仓 //是否是空仓,若position中所有品种volum都为0,则说明是空仓 bool isEmptyPosition = positions.Count != 0 ? positions[positions.Keys.Last()].Values.Sum(x => Math.Abs(x.volume)) == 0 : true; //若当前有持仓且允许交易 if (!isEmptyPosition && closingOn) { ///平仓条件 /// (1)若当前为 回测结束日 或 tradingOn 为false,平仓 /// (2)若当前下穿下反转点*(1-容忍度),平多 //(1)若当前为 回测结束日 或 tradingOn 为false,平仓 if (isLastDayOfBackTesting || tradingOn == false) { next = MinuteCloseAllPositonsWithSlip.closeAllPositions(dataToday, ref positions, ref myAccount, now: now, slipPoint: slipPoint); } //(2)若当前下穿下反转点*(1-容忍度),平多 else { //计算下反转点 if (data[targetVariety][indexOfNow - 1].close >= nowDownReversionPoint * (1 - toleranceDegree) && nowClose < nowDownReversionPoint * (1 - toleranceDegree)) { next = MinuteCloseAllPositonsWithSlip.closeAllPositions(dataToday, ref positions, ref myAccount, now: now, slipPoint: slipPoint); } } } //空仓 且可交易 可开仓 else if (isEmptyPosition && tradingOn && openingOn) { ///开仓条件 /// 可用资金足够,且出现上反转信号 double nowFreeCash = myAccount.freeCash; //开仓量,满仓梭哈 double openVolume = Math.Truncate(nowFreeCash / data[targetVariety][indexOfNow].close / contractTimes) * contractTimes; //若剩余资金至少购买一手 且 出上反转信号 开仓 if (openVolume >= 1 && data[targetVariety][indexOfNow - 1].close <= nowUpReversionPoint * (1 + toleranceDegree) && nowClose > nowUpReversionPoint * (1 + toleranceDegree)) { MinuteSignal openSignal = new MinuteSignal() { code = targetVariety, volume = openVolume, time = now, tradingVarieties = "stock", price = dataToday[targetVariety][index].close, minuteIndex = index }; signal.Add(targetVariety, openSignal); next = MinuteTransactionWithSlip.computeMinuteOpenPositions(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now); //当天买入不可卖出 closingOn = false; } } //账户信息更新 AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, now, dataToday); } catch (Exception) { throw; } nextIndex = Math.Max(nextIndex, TimeListUtility.MinuteToIndex(next)); index = nextIndex; } //账户信息记录By Day //用于记录的临时账户 BasicAccount tempAccount = new BasicAccount(); tempAccount.time = myAccount.time; tempAccount.freeCash = myAccount.freeCash; tempAccount.margin = myAccount.margin; tempAccount.positionValue = myAccount.positionValue; tempAccount.totalAssets = myAccount.totalAssets; accountHistory.Add(tempAccount); //抓取benchmark benchmark.Add(dataToday[targetVariety].Last().close); //显示当前信息 Console.WriteLine("Time:{0,-8:F},netWorth:{1,-8:F3}", day, myAccount.totalAssets / initialCapital); } //遍历输出到console /* * foreach (var account in accountHistory) * Console.WriteLine("time:{0,-8:F}, netWorth:{1,-8:F3}\n", account.time, account.totalAssets / initialCapital); */ //策略绩效统计及输出 PerformanceStatisics myStgStats = new PerformanceStatisics(); myStgStats = PerformanceStatisicsUtils.compute(accountHistory, positions, benchmark.ToArray()); //统计指标在console 上输出 Console.WriteLine("--------Strategy Performance Statistics--------\n"); Console.WriteLine(" netProfit:{0,-3:F} \n totalReturn:{1,-3:F} \n anualReturn:{2,-3:F} \n anualSharpe :{3,-3:F} \n winningRate:{4,-3:F} \n PnLRatio:{5,-3:F} \n maxDrawDown:{6,-3:F} \n maxProfitRatio:{7,-3:F} \n informationRatio:{8,-3:F} \n alpha:{9,-3:F} \n beta:{10,-3:F} \n averageHoldingRate:{11,-3:F} \n", myStgStats.netProfit, myStgStats.totalReturn, myStgStats.anualReturn, myStgStats.anualSharpe, myStgStats.winningRate, myStgStats.PnLRatio, myStgStats.maxDrawDown, myStgStats.maxProfitRatio, myStgStats.informationRatio, myStgStats.alpha, myStgStats.beta, myStgStats.averageHoldingRate); Console.WriteLine("-----------------------------------------------\n"); //画图 Dictionary <string, double[]> line = new Dictionary <string, double[]>(); double[] netWorth = accountHistory.Select(a => a.totalAssets / initialCapital).ToArray(); line.Add("NetWorth", netWorth); //benchmark净值 List <double> netWorthOfBenchmark = benchmark.Select(x => x / benchmark[0]).ToList(); line.Add("50ETF", netWorthOfBenchmark.ToArray()); string[] datestr = accountHistory.Select(a => a.time.ToString("yyyyMMdd")).ToArray(); Application.Run(new PLChart(line, datestr)); /* * //将accountHistory输出到csv * var resultPath = ConfigurationManager.AppSettings["CacheData.ResultPath"] + "accountHistory.csv"; * var dt = DataTableUtils.ToDataTable(accountHistory); // List<MyModel> -> DataTable * CsvFileUtils.WriteToCsvFile(resultPath, dt); // DataTable -> CSV File */ Console.ReadKey(); }