/// <summary> /// 计算参数 /// </summary> private void computeParameters() { List <FuturesMinute> data = new List <FuturesMinute>(); #region 20170118更新 //回测开始的首个日期,在tradeDays数组的第一个索引值: //当回测期第一个交易日的前三天没有一天有数据,会引发后续代码出现数组索引为负值的情况。 //bool OutOfRange = false; #endregion //获取首个回测日之前的三日(日)数据 data = getData(DateUtils.PreviousTradeDay(tradeDays[0], 3), underlying); data.AddRange(getData(DateUtils.PreviousTradeDay(tradeDays[0], 2), underlying)); data.AddRange(getData(DateUtils.PreviousTradeDay(tradeDays[0], 1), underlying)); #region 20170118更新 //当无法获取回测日之前的三日(日)数据,也就是data.count=0时,就需要... //if (data.Count == 0) //{ // Console.WriteLine("回测开始日期错误,自动调整到有数据记录的开始日期..."); // OutOfRange = true; //} #endregion //如果回测开始时间早于该品种有数据的时间, //逐日获取K线数据(频率为1分钟) for (int i = 0; i < tradeDays.Count(); i++) { var data0 = getData(tradeDays[i], underlying); data.AddRange(data0); } //var dataModified = FreqTransferUtils.minuteToNMinutes(data, frequency); //按交易日逐日计算,每日遍历所有的参数,结果记入字典结构的变量中 ParaPairs pairs = new ParaPairs(); #region debug数据,请勿删除 //int[] frequencySet = new int[] { 3 }; //int[] numbersSet = new int[] { 5 }; //double[] lossPercentSet = new double[] { 0.015 }; //double[] longERSet = new double[] { 0.7 }; //double[] shortERSet = new double[] {-0.7 }; //int[] frequencySet = new int[] { 3, 4, 5, 6, 7, 8 }; //int[] numbersSet = new int[] { 3, 4, 5, 6, 8, 10, 15 }; //double[] lossPercentSet = new double[] { 0.005, 0.01, 0.015, 0.02 }; //double[] longERSet = new double[] { 0.5, 0.6, 0.7, 0.8, 0.9 }; //double[] shortERSet = new double[] { -0.5, -0.6, -0.7, -0.8, -0.9 }; #endregion int[] frequencySet = new int[] { 3, 4, 5, 6, 7, 8 }; int[] numbersSet = new int[] { 3, 4, 5, 6, 8, 10, 15 }; double[] lossPercentSet = new double[] { 0.005, 0.01, 0.015, 0.02 }; double[] longERSet = new double[] { 0.5, 0.6, 0.7, 0.8, 0.9 }; double[] shortERSet = new double[] { -0.5, -0.6, -0.7, -0.8, -0.9 }; //记录frequency的边际分布 List <double> frequencyDistrbution = new List <double>(); foreach (var fre in frequencySet) { frequency = fre; //给定K线周期 pairs.frequency = frequency; //data是(1分钟)K线数据,dataModified是(frequency周期)K线数据 var dataModified = FreqTransferUtils.minuteToNMinutes(data, frequency); foreach (var num in numbersSet) { numbers = num; //给定前推的(frequency周期)K线数量 pairs.numbers = numbers; foreach (var loss in lossPercentSet) { lossPercent = loss; //给定追踪止损的参数 pairs.lossPercent = lossPercent; foreach (var er in longERSet) { longER = er; //给定ER比例的参数 pairs.longER = longER; foreach (var shortEr in shortERSet) { shortER = shortEr; pairs.shortER = shortER; #region ... double profitInDay = 0; double positionVolume = 0; double openPrice = 0; double maxIncomeIndividual = 0; Console.WriteLine("开始回测参数--> K线:{0}, 回望时间:{1},追踪止损:{2},longER值:{3}, shortER值:{4}", pairs.frequency, pairs.numbers, pairs.lossPercent, pairs.longER, pairs.shortER); //[新版]记录该组参数对应的, 所有交易日的收益 FiveParameterPairs newPairs0 = new FiveParameterPairs { longER = pairs.longER, shortER = pairs.shortER, frequency = pairs.frequency, lossPercent = pairs.lossPercent, numbers = pairs.numbers }; //用来记录同一套策略情况下,不同交易日的盈利情况 SortedDictionary <DateTime, double> sortedDic0 = new SortedDictionary <DateTime, double>(); for (int i = 0; i < dataModified.Count(); i++) //开始按日期遍历 { var now = dataModified[i]; #region 20170118更新 //if (OutOfRange) //{ // if (now.tradeday <DateUtils.NextTradeDay(dataModified[0].tradeday)) // { // continue; // } //} //else //{ //} //在5分钟K线数据表dataModified中,找到首个交易日 tradeDays[0]开始位置对应的index if (now.tradeday < tradeDays[0]) { continue; } #endregion pairs.tradeday = now.tradeday; //当日最后一根K线,进入结算。 if (i == dataModified.Count() - 1 || (i + 1 < dataModified.Count() && dataModified[i + 1].tradeday > now.tradeday)) { //强制平仓 if (positionVolume != 0) { //减去2倍的滑点,是因为买入和卖出均有手续费 profitInDay += positionVolume * (now.open - openPrice) - 2 * slipPoint; // Console.WriteLine("时间:{0},价格:{1}, volume:0", dataModified[i].time, now.open); } //记录该组参数当日收益(一个交易日记录一次数据) sortedDic0.Add(pairs.tradeday, profitInDay); //重置数据 profitInDay = 0; positionVolume = 0; maxIncomeIndividual = 0; } else { double[] prices = new double[numbers]; for (int k = i - numbers; k < i; k++) { //导入(前numbersK线)的收盘价 prices[k - (i - numbers)] = dataModified[k].close; } //计算出ER值 double ER = computeER(prices); if (positionVolume == 0) //持空仓 { if (ER > longER && now.open > now.low) //开多仓,且能够开仓 { openPrice = now.open; positionVolume = 1; // Console.WriteLine("时间:{0},价格:{1}, volume:1", dataModified[i].time, now.open); } if (ER < shortER && now.open < now.high) //开空仓 { openPrice = now.open; positionVolume = -1; // Console.WriteLine("时间:{0},价格:{1}, volume:-1", dataModified[i].time, now.open); } } else if (positionVolume == 1) //持多仓 { if ((now.open - openPrice) > maxIncomeIndividual) { maxIncomeIndividual = now.open - openPrice; } //追踪止损,强制平仓 else if (((now.open - openPrice) - maxIncomeIndividual) < -lossPercent * now.open) { profitInDay += now.open - openPrice - 2 * slipPoint; //重置数据 positionVolume = 0; maxIncomeIndividual = 0; // Console.WriteLine("时间:{0},价格:{1}, volume:0", dataModified[i].time, now.open); } } else if (positionVolume == -1) //持空仓 { if ((openPrice - now.open) > maxIncomeIndividual) { maxIncomeIndividual = (openPrice - now.open); } else if (((openPrice - now.open) - maxIncomeIndividual) < -lossPercent * now.open) { profitInDay += openPrice - now.open - 2 * slipPoint; positionVolume = 0; maxIncomeIndividual = 0; // Console.WriteLine("时间:{0},价格:{1}, volume:0", dataModified[i].time, now.open); } } } } //写入每一套参数,对应的所有交易日的收益情况 newResult.Add(newPairs0, sortedDic0); #endregion } } } } //第一层循环底部.... } }
/// <summary> /// 选择最优参数对。用前choicePeriod个交易日的数据计算出最优参数对,作为之后serviceLife个交易日的交易参数 /// </summary> /// <param name="result"></param> /// <param name="startDate"></param> /// <param name="endDate"></param> /// <param name="choicePeriod"></param> /// <param name="serviceLife"></param> /// <returns></returns> private Dictionary <DateTime, FiveParameterPairs> chooseParameters(Dictionary <FiveParameterPairs, SortedDictionary <DateTime, double> > result, DateTime startDate, DateTime endDate, int choicePeriod, int serviceLife) { DateTime start = startDate; DateTime end = DateUtils.NextTradeDay(start, choicePeriod - 1); DateTime paraStart = DateUtils.NextTradeDay(start, choicePeriod); DateTime paraEnd = DateUtils.NextTradeDay(start, choicePeriod + serviceLife - 1); //如果choicePeriod的数值,超过了回测周期的长度。做一些特殊处理。 if (end > endDate) { end = endDate; paraStart = start; paraEnd = end; } ///最高分。因为参数对计算得分可能会出现负数,所以这里初始默认值不能为0。 ///否则会出现循环所有参数对结束之后,最优参数对bestPara依然没有复制的情况。 double marks = -1000000; //用来记录(在choicePeriod时间段中)得分最高的参数对 FiveParameterPairs bestPara = new FiveParameterPairs(); //记录每个交易日的最佳参数对(Dictionary类型) Dictionary <DateTime, FiveParameterPairs> paras = new Dictionary <DateTime, FiveParameterPairs>(); //记录每个交易日使用的参数对(List类型),用于输出到CSV文件 List <ParaPairsWithScore> parasWithScore = new List <ParaPairsWithScore>(); #region debug专用 //debug专用:存贮所有参数组合在规定start, end期间的得分... Dictionary <FiveParameterPairs, double> scoreWithParameterPair = new Dictionary <FiveParameterPairs, double>(); #endregion while (end <= endDate) { //循环所有的参数组合,选出(在choicePeriod时间段中)得分最高的参数对 foreach (var item in result) { double mark0 = getMarks(result, item.Key, start, end); #region debug专用 //debug专用:存贮debug信息... if (mark0 > 0.1) { scoreWithParameterPair.Add(item.Key, mark0); } #endregion if (mark0 > marks) { marks = mark0; bestPara = item.Key; } } if (paras.Count() == 0) { List <DateTime> dates0 = DateUtils.GetTradeDays(start, end); foreach (var item in dates0) { paras.Add(item, bestPara); parasWithScore.Add(new ParaPairsWithScore { tradeday = item, frequency = bestPara.frequency, numbers = bestPara.numbers, lossPercent = bestPara.lossPercent, longER = bestPara.longER, shortER = bestPara.shortER, Score = marks }); } } //将选出的(choicePeriod时间段中)最优参数对,作为最优解保存到( choicePeriod + serviceLife时间段) List <DateTime> dates = DateUtils.GetTradeDays(paraStart, paraEnd); foreach (var item in dates) { paras.Add(item, bestPara); parasWithScore.Add(new ParaPairsWithScore { tradeday = item, frequency = bestPara.frequency, numbers = bestPara.numbers, lossPercent = bestPara.lossPercent, longER = bestPara.longER, shortER = bestPara.shortER, Score = marks }); } //计算日期往后顺延serviceLife时间段,重置相关数据 start = DateUtils.NextTradeDay(start, serviceLife); end = DateUtils.NextTradeDay(end, serviceLife); paraStart = DateUtils.NextTradeDay(paraStart, serviceLife); paraEnd = DateUtils.NextTradeDay(paraEnd, serviceLife); marks = -1000000; bestPara = new FiveParameterPairs(); if (end >= endDate) { break; } #region debug专用 scoreWithParameterPair = new Dictionary <FiveParameterPairs, double>(); #endregion } //每个交易日使用的策略,写入CSV文件 //List<ParameterPairs> tradeDaysWithBestParas = convertType(paras); string recordName = underlying.Replace(".", "_") + "_" + startDate.ToShortDateString().Replace("/", "_") + "_to_" + endDate.ToShortDateString().Replace("/", "_"); RecordUtil.recordToCsv(data: parasWithScore, type: "tradeDaysWithBestParas", tag: GetType().FullName, parameters: recordName, performance: ""); return(paras); }
/// <summary> /// 获得所有参数对的评分 /// </summary> /// <param name="result"></param> /// <param name="pair"></param> /// <param name="startDate"></param> /// <param name="endDate"></param> /// <returns></returns> public double getMarks(Dictionary <FiveParameterPairs, SortedDictionary <DateTime, double> > result, FiveParameterPairs pair, DateTime startDate, DateTime endDate) { List <double> netvalue = new List <double>(); //净值 List <double> returnRatio = new List <double>(); //收益率 netvalue.Add(1); //【问题】netvalue这里为什么要先add一个1,而returnRatio没有? double total = initialCapital; foreach (var item in result) { if (item.Key.longER == pair.longER && item.Key.shortER == pair.shortER && item.Key.frequency == pair.frequency && item.Key.lossPercent == pair.lossPercent && item.Key.numbers == pair.numbers) { foreach (var num in item.Value) { if (num.Key >= startDate && num.Key <= endDate) { total += num.Value; netvalue.Add(total / initialCapital); returnRatio.Add(num.Value / initialCapital); } } break; } } //获取最大回撤率 double MDD = PerformanceStatisicsUtils.computeMaxDrawDown(netvalue); double sum = 0; //和 double squareSum = 0; //平方和 for (int i = 0; i < returnRatio.Count(); i++) { sum += returnRatio[i]; squareSum += Math.Pow(returnRatio[i], 2); } //日收益率 double average = sum / returnRatio.Count(); //标准差 double std = Math.Sqrt(squareSum / returnRatio.Count() - average * average); //夏普率 double sharpe = average * 252 / (std * Math.Sqrt(252));//夏普率 //average * 252=年化收益 //处理MDD为0的情况 double calmar = (MDD == 0.0 ? 4 : average * 252 / MDD);//Calmar比率 return((0.3 * sharpe + 0.7 * calmar) / 8); //不处理MDD为0的情况 //double calmar = (MDD == 0 ? 4 : average / MDD);//Calmar比率 //return (0.5 * sharpe + 0.5 * calmar)/8; //只用年化收益率来打分 //return average * 252; //年化收益率 / 最大回撤 //return average * 252 / MDD; }
public void compute() { //初始化头寸信息 SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions = new SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> >(); //初始化Account信息 BasicAccount myAccount = new BasicAccount(initialAssets: initialCapital, totalAssets: initialCapital, freeCash: initialCapital); //记录历史账户信息 List <BasicAccount> accountHistory = new List <BasicAccount>(); //基准衡量 List <double> benchmark = new List <double>(); //持仓量 double positionVolume = 0; //最大收入值 double maxIncome = 0; //第一层循环:所有交易日循环一遍... for (int i = 0; i < tradeDays.Count(); i++) { DateTime today = tradeDays[i]; //找到当日参数 FiveParameterPairs pair = parameters[today]; frequency = pair.frequency; numbers = pair.numbers; longER = pair.longER; shortER = pair.shortER; lossPercent = pair.lossPercent; //从wind或本地CSV获取相应交易日的数据list //这里不能直接去调用FreqTransferUtils.minuteToNMinutes(data, frequency),因为data.count可能为0 var dataOnlyToday = getData(today, underlying); //一个交易日里有多条分钟线数据 var data = getData(DateUtils.PreviousTradeDay(today, 3), underlying); //前3交易日的分钟线频率数据list data.AddRange(getData(DateUtils.PreviousTradeDay(today, 2), underlying)); data.AddRange(getData(DateUtils.PreviousTradeDay(today, 1), underlying)); int indexStart = data.Count(); if (indexStart == 0) //前一天没数据 { indexStart = numbers; } //将当天的数据add到前一天的数据之后 data.AddRange(dataOnlyToday); #region 20170118更新 //将data转换成FuturesMinute分钟线频率 //data= FreqTransferUtils.minuteToNMinutes(data, frequency); #endregion //将获取的数据,储存为KLine格式 Dictionary <string, List <KLine> > dataToday = new Dictionary <string, List <KLine> >(); dataToday.Add(underlying, data.Cast <KLine>().ToList()); //第二层循环:只循环某当天的数据(开始的索引值为前一天数据的List.count) #region 第二层循环 //这里减1:最后一个周期只平仓,不开仓 for (int j = indexStart; j < data.Count() - 1; j++) { DateTime now = data[j].time; double[] prices = new double[numbers]; for (int k = j - numbers; k < j; k++) { //导入收盘价 prices[k - (j - numbers)] = data[k].close; } //计算出ER值 double ER = computeER(prices); # region 追踪止损判断 触发止损平仓 //追踪止损判断 触发止损平仓 if (positionVolume != 0) //头寸量不为0,额外要做的操作 { //计算开盘价和头寸当前价的差价 double incomeNow = individualIncome(positions.Last().Value[underlying], data[j].open); //若当前收入大于最大收入值,则更新最大收入值 if (incomeNow > maxIncome) { maxIncome = incomeNow; } //若盈利回吐大于5个点 或者 最大收入大于45,则进行平仓 //&& ((positionVolume>0 && ER<longLevel) || (positionVolume<0 && ER>shortLevel)) else if ((maxIncome - incomeNow) > lossPercent * Math.Abs(data[j].open) || incomeNow < -lossPercent * Math.Abs(data[j].open)) //从最高点跌下来3%,就止损 { positionVolume = 0; Console.WriteLine("追踪止损!平仓价格: {0}", data[j].open); MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, j, slipPoint); maxIncome = 0; } //if (positionVolume>0 && ER<0 && incomeNow<-10) //{ // positionVolume = 0; // Console.WriteLine("信号止损!平仓价格: {0}", data[j].open); // MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, j, slipPoint); // maxIncome = 0; //} //else if (positionVolume < 0 && ER >0 && incomeNow < -10) //{ // positionVolume = 0; // Console.WriteLine("信号止损!平仓价格: {0}", data[j].open); // MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, j, slipPoint); // maxIncome = 0; //} } #endregion if (ER >= longER && positionVolume == 0) //多头信号,无头寸,则开多仓 { double volume = 1; //长头寸信号 MinuteSignal longSignal = new MinuteSignal() { code = underlying, volume = volume, time = now, tradingVarieties = "futures", price = data[j].open, minuteIndex = j }; //signal保存长头寸longSignal信号 Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); signal.Add(underlying, longSignal); MinuteTransactionWithBar.ComputePosition(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now, nowIndex: longSignal.minuteIndex); Console.WriteLine("做多期货!多头开仓价格: {0}", data[j].open); //头寸量叠加 positionVolume += volume; //单笔最大收益重置 maxIncome = 0; } else if (ER <= shortER && positionVolume == 0) //空头信号,无头寸,则开空仓 { double volume = -1; maxIncome = 0; MinuteSignal shortSignal = new MinuteSignal() { code = underlying, volume = volume, time = now, tradingVarieties = "futures", price = data[j].open, minuteIndex = j }; Console.WriteLine("做空期货!空头开仓价格: {0}", data[j].open); Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); signal.Add(underlying, shortSignal); positionVolume += volume; //分钟级交易 MinuteTransactionWithBar.ComputePosition(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now, nowIndex: shortSignal.minuteIndex); } } #endregion int closeIndex = data.Count() - 1; if (positionVolume != 0) { positionVolume = 0; maxIncome = 0; MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, data[closeIndex].time, closeIndex, slipPoint); Console.WriteLine("{2} 每日收盘前强制平仓,平仓价格:{0},账户价值:{1}", data[closeIndex].open, myAccount.totalAssets, today); } if (data.Count > 0) { //更新当日属性信息 AccountOperator.Minute.maoheng.AccountUpdatingWithMinuteBar.computeAccount(ref myAccount, positions, data.Last().time, data.Count() - 1, dataToday); //记录历史仓位信息 accountHistory.Add(new BasicAccount(myAccount.time, myAccount.totalAssets, myAccount.freeCash, myAccount.positionValue, myAccount.margin, myAccount.initialAssets)); benchmark.Add(data.Last().close); if (netValue.Count() == 0) { netValue.Add(new NetValue { time = today, netvalueReturn = 0, benchmarkReturn = 0, netvalue = myAccount.totalAssets, benchmark = data.Last().close }); } else { var netValueLast = netValue.Last(); netValue.Add(new NetValue { time = today, netvalueReturn = myAccount.totalAssets / netValueLast.netvalue - 1, benchmarkReturn = data.Last().close / netValueLast.benchmark - 1, netvalue = myAccount.totalAssets, benchmark = data.Last().close }); } } }