/// <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;
        }
Пример #4
0
        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
                        });
                    }
                }
            }