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; //螺纹钢的日线数据准备,从回测期开始之前10个交易开始取 int number = 10; double k1 = 0.2; double k2 = -0.1; List <FuturesDaily> dailyData = new List <FuturesDaily>(); dailyData = Platforms.container.Resolve <FuturesDailyDataService>().fetchFromLocalCsvOrWindAndSave(underlying, startDate, endDate); List <double> highList = new List <double>(); List <double> closeList = new List <double>(); List <double> lowList = new List <double>(); List <double> buyLineList = new List <double>(); List <double> sellLineList = new List <double>(); List <double> rangeList = new List <double>(); for (int i = 0; i < dailyData.Count; i++) { if (highList.Count == number) { highList.RemoveAt(0); closeList.RemoveAt(0); lowList.RemoveAt(0); } highList.Add(dailyData[i].high); closeList.Add(dailyData[i].close); lowList.Add(dailyData[i].low); //N日High的最高价HH double hightestHigh = highList.Max(); //N日Close的最高价HC double hightestClose = closeList.Max(); //N日Close的最低价LC double lowestClose = closeList.Min(); //N日Low的最低价LL double open = dailyData[i].open; double lowestLow = lowList.Min(); double range = Math.Max(hightestHigh - lowestClose, hightestClose - lowestLow); double buyLine = open + k1 * range; double sellLine = open + k2 * range; rangeList.Add(range); buyLineList.Add(buyLine); sellLineList.Add(sellLine); } //第一层循环:所有交易日循环一遍... for (int i = 0; i < tradeDays.Count(); i++) { if (i == 513) { double buyLine1 = buyLineList[i]; double sellLine1 = sellLineList[i]; } DateTime today = tradeDays[i]; //从wind或本地CSV获取相应交易日的数据list //这里不能直接去调用FreqTransferUtils.minuteToNMinutes(data, frequency),因为data.count可能为0 var dataOnlyToday = getData(today, underlying);//一个交易日里有多条分钟线数据 //将获取的数据,储存为KLine格式 Dictionary <string, List <KLine> > dataToday = new Dictionary <string, List <KLine> >(); dataToday.Add(underlying, dataOnlyToday.Cast <KLine>().ToList()); //第二层循环:只循环某当天的数据(开始的索引值为前一天数据的List.count) for (int j = 15; j < dataOnlyToday.Count() - 30; j++) { DateTime now = dataOnlyToday[j].time; # region 追踪止损判断 触发止损平仓 //追踪止损判断 触发止损平仓 if (positionVolume != 0) //头寸量不为0,额外要做的操作 { //计算开盘价和头寸当前价的差价 double incomeNow = individualIncome(positions.Last().Value[underlying], dataOnlyToday[j].open); //若当前收入大于最大收入值,则更新最大收入值 if (incomeNow > maxIncome) { maxIncome = incomeNow; } //若盈利回吐大于5个点 或者 最大收入大于45,则进行平仓 else if ((maxIncome - incomeNow) > lossPercent * Math.Abs(dataOnlyToday[j].open) || incomeNow < -lossPercent * Math.Abs(dataOnlyToday[j].open)) //从最高点跌下来3%,就止损 { positionVolume = 0; // Console.WriteLine("追踪止损!平仓价格: {0}", dataOnlyToday[j].open); MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, j, slipPoint); maxIncome = 0; } } #endregion var price = dataOnlyToday[j].open; //价格向上突破上轨 if (price > buyLineList[i]) { //如果持有空仓,则先平仓 if (positionVolume == -1) { positionVolume = 0; //Console.WriteLine("追踪止损!平仓价格: {0}", dataOnlyToday[j].open); MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, j, slipPoint); } //如果没有仓位,则直接多仓 if (positionVolume == 0) { double volume = 1; //长头寸信号 MinuteSignal longSignal = new MinuteSignal() { code = underlying, volume = volume, time = now, tradingVarieties = "futures", price = dataOnlyToday[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}", dataOnlyToday[j].open); //头寸量叠加 positionVolume += volume; } } //价格向下突破下轨 if (price < sellLineList[i]) { //如果持有多仓,则先平仓 if (positionVolume == 1) { positionVolume = 0; // Console.WriteLine("追踪止损!平仓价格: {0}", dataOnlyToday[j].open); MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, j, slipPoint); } //如果没有仓位,则直接空仓 if (positionVolume == 0) { double volume = -1; MinuteSignal shortSignal = new MinuteSignal() { code = underlying, volume = volume, time = now, tradingVarieties = "futures", price = dataOnlyToday[j].open, minuteIndex = j }; // Console.WriteLine("做空期货!空头开仓价格: {0}", dataOnlyToday[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); } } } int closeIndex = dataOnlyToday.Count() - 1; if (positionVolume != 0) { positionVolume = 0; MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, dataOnlyToday[closeIndex].time, closeIndex, slipPoint); // Console.WriteLine("{2} 每日收盘前强制平仓,平仓价格:{0},账户价值:{1}", dataOnlyToday[closeIndex].open, myAccount.totalAssets, today); } if (dataOnlyToday.Count > 0) { //更新当日属性信息 AccountUpdatingWithMinuteBar.computeAccount(ref myAccount, positions, dataOnlyToday.Last().time, dataOnlyToday.Count() - 1, dataToday); //记录历史仓位信息 accountHistory.Add(new BasicAccount(myAccount.time, myAccount.totalAssets, myAccount.freeCash, myAccount.positionValue, myAccount.margin, myAccount.initialAssets)); benchmark.Add(dataOnlyToday.Last().close); if (netValue.Count() == 0) { netValue.Add(new NetValue { time = today, netvalueReturn = 0, benchmarkReturn = 0, netvalue = myAccount.totalAssets, benchmark = dataOnlyToday.Last().close }); } else { var netValueLast = netValue.Last(); netValue.Add(new NetValue { time = today, netvalueReturn = myAccount.totalAssets / netValueLast.netvalue - 1, benchmarkReturn = dataOnlyToday.Last().close / netValueLast.benchmark - 1, netvalue = myAccount.totalAssets, benchmark = dataOnlyToday.Last().close }); } } }
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>(); //标记当日跨式组合多空信号。1表示多头,0表示无信号,-1表示空头。 double orignalSignal = 0; Straddle holdingStatus = new Straddle(); //统计历史波动率分位数,从回测期开始前一天,统计到最后一天 double[][] fractile = new double[backTestingDuration + 1][]; fractile = computeRollingFractile(startIndex - 1, etfDailyData.Count() - 1, 100); //统计隐含波动率 computeImpv(); //统计隐含波动率和历史波动率之差 epsilon=max[E(IV-HV),0] computeEpsilon(); //按时间遍历,2015年02月09日50ETF期权上市开始,2月10日开始昨日收盘的隐含波动率数据。 for (int i = startIndex + 1; i < startIndex + backTestingDuration; i++) { DateTime today = etfDailyData[i].time; //获取当日上市的期权合约列表 var optionInfoList = OptionUtilities.getUnmodifiedOptionInfoList(this.optionInfoList, today); //若当日发生50ETF分红派息,myAccount要加上分红的钱,并且需要调整持有头寸的strike if (today == timeOf50ETFDividend2016 && positions.Count > 0 && positions.Last().Value.ContainsKey("510050.SH")) { //50ETF的头寸中记入分红 positions.Last().Value["510050.SH"].totalCashFlow += positions.Last().Value["510050.SH"].volume * bonusOf50ETFDividend2016; //期权持仓行权价调整 foreach (var item in optionInfoList) { if (item.optionCode == holdingStatus.callCode) { holdingStatus.strike = item.strike; holdingStatus.callPosition *= item.contractMultiplier / standardContractMultiplier; holdingStatus.putPosition *= item.contractMultiplier / standardContractMultiplier; } } } Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); double fractile90Yesterday = fractile[i - 1][9]; //昨日历史波动率90分位数 double fractile70Yesterday = fractile[i - 1][7]; //昨日历史波动率70分位数 double fractile50Yesterday = fractile[i - 1][5]; //昨日历史波动率50分位数 double fractile30Yesterday = fractile[i - 1][3]; //昨日历史波动率30分位数 double volYesterday = etfVol[i - 1]; //昨日历史波动率 double impvYesterday = optionVol[i - 1]; //昨日隐含波动率 //获取当日ATM期权合约代码 double etfPrice = etfDailyData[i].close; double duration; //获取当日期限结构,选取当月合约,若当日合约到日期小于等于3天,直接开仓下月合约 List <double> dateStructure = OptionUtilities.getDurationStructure(optionInfoList, today); double duration0 = dateStructure[0] <= 3 ? dateStructure[1] : dateStructure[0]; duration = duration0; var call = OptionUtilities.getOptionListByOptionType(OptionUtilities.getOptionListByDuration(optionInfoList, today, duration), "认购").OrderBy(x => Math.Abs(x.strike - etfPrice)).Where(x => x.startDate <= today).ToList(); var callATM = call[0]; var callPrice = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(callATM.optionCode, today); // double callImpv = ImpliedVolatilityUtilities.ComputeImpliedVolatility(callATM.strike, duration / 252.0, 0.04, 0, callATM.optionType, callPrice[0].close, etfPrice); var put = OptionUtilities.getOptionListByOptionType(OptionUtilities.getOptionListByDuration(optionInfoList, today, duration), "认沽").OrderBy(x => Math.Abs(x.strike - callATM.strike)).ToList(); var putATM = put[0]; var putPrice = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(putATM.optionCode, today); //double putImpv = ImpliedVolatilityUtilities.ComputeImpliedVolatility(putATM.strike, duration / 252.0, 0.04, 0, putATM.optionType, putPrice[0].close, etfPrice); //整合当日分钟线数据 Dictionary <string, List <KLine> > dataToday = new Dictionary <string, List <KLine> >(); var etfData = Platforms.container.Resolve <StockMinuteRepository>().fetchFromLocalCsvOrWindAndSave("510050.SH", today); dataToday.Add("510050.SH", etfData.Cast <KLine>().ToList()); dataToday.Add(callATM.optionCode, callPrice.Cast <KLine>().ToList()); dataToday.Add(putATM.optionCode, putPrice.Cast <KLine>().ToList()); //策略信号处理 //信号1 //orignalSignal = 0; //if (volYesterday >= fractile70Yesterday) //{ // //卖出跨式期权 // orignalSignal = -1; //} //else if (impvYesterday < volYesterday) //{ // //买入跨式期权 // orignalSignal = 1; //} //else if (impvYesterday - volYesterday > epsilon[i - 1]) //{ // //卖出跨式期权 // orignalSignal = -1; //} //信号2 orignalSignal = 0; if (volYesterday - impvYesterday > 0 && volYesterday <= fractile50Yesterday) { //买入跨式期权 orignalSignal = 1; } else if (impvYesterday - volYesterday > epsilon[i - 1]) { //卖出跨式期权 orignalSignal = -1; } //指定平仓时间为开盘第一个分钟。 int openIndex = 0; DateTime now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(today), openIndex); Console.WriteLine("time: {0}, 昨日历史波动率: {1}, 历史波动率70分位数: {2}, 昨日隐含波动率: {3}", now, volYesterday.ToString("N"), fractile70Yesterday.ToString("N"), optionVol[i - 1].ToString("N")); //如果有持仓先判断持仓状态和信号方向是否相同,如果不同先平仓 if (holdingStatus.callPosition != 0) { if (dataToday.ContainsKey(holdingStatus.callCode) == false) { var callLastDay = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.callCode, today); dataToday.Add(holdingStatus.callCode, callLastDay.Cast <KLine>().ToList()); } if (dataToday.ContainsKey(holdingStatus.putCode) == false) { var putLastDay = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.putCode, today); dataToday.Add(holdingStatus.putCode, putLastDay.Cast <KLine>().ToList()); } if (holdingStatus.callPosition * orignalSignal < 0) //仓位和信号相反,强制平仓 { Console.WriteLine("平仓!"); MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, openIndex, slipPoint); holdingStatus = new Straddle(); } if (DateUtils.GetSpanOfTradeDays(today, holdingStatus.endDate) <= 3) //有仓位无信号,判断是否移仓 { Console.WriteLine("平仓!"); MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, openIndex, slipPoint); holdingStatus = new Straddle(); } } //指定开仓时间为开盘第10分钟。错开开平仓的时间。 openIndex = 10; now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(today), openIndex); if (holdingStatus.callPosition == 0 && orignalSignal != 0) //无仓位有信号,开仓 { if (orignalSignal == 1) //做多跨式期权 { MinuteSignal openSignalCall = new MinuteSignal() { code = callATM.optionCode, volume = optionVolume, time = now, tradingVarieties = "option", price = callPrice[openIndex].open, minuteIndex = openIndex }; MinuteSignal openSignalPut = new MinuteSignal() { code = putATM.optionCode, volume = optionVolume, time = now, tradingVarieties = "option", price = putPrice[openIndex].open, minuteIndex = openIndex }; Console.WriteLine("做多跨式期权!"); signal.Add(callATM.optionCode, openSignalCall); signal.Add(putATM.optionCode, openSignalPut); //变更持仓状态 holdingStatus.callCode = callATM.optionCode; holdingStatus.putCode = putATM.optionCode; holdingStatus.callPosition = optionVolume; holdingStatus.putPosition = optionVolume; holdingStatus.etfPrice_open = etfData[openIndex].open; holdingStatus.straddlePrice_open = callPrice[openIndex].close + putPrice[openIndex].open; holdingStatus.straddleOpenDate = today; holdingStatus.endDate = callATM.endDate; holdingStatus.strike = callATM.strike; } else if (orignalSignal == -1) //做空跨式期权 { MinuteSignal openSignalCall = new MinuteSignal() { code = callATM.optionCode, volume = -optionVolume, time = now, tradingVarieties = "option", price = callPrice[openIndex].open, minuteIndex = openIndex }; MinuteSignal openSignalPut = new MinuteSignal() { code = putATM.optionCode, volume = -optionVolume, time = now, tradingVarieties = "option", price = putPrice[openIndex].open, minuteIndex = openIndex }; Console.WriteLine("做空跨式期权!"); signal.Add(callATM.optionCode, openSignalCall); signal.Add(putATM.optionCode, openSignalPut); //变更持仓状态 holdingStatus.callCode = callATM.optionCode; holdingStatus.putCode = putATM.optionCode; holdingStatus.callPosition = -optionVolume; holdingStatus.putPosition = -optionVolume; holdingStatus.etfPrice_open = etfData[openIndex].open; holdingStatus.straddlePrice_open = callPrice[openIndex].open + putPrice[openIndex].open; holdingStatus.straddleOpenDate = today; holdingStatus.endDate = callATM.endDate; holdingStatus.strike = callATM.strike; } MinuteTransactionWithBar.ComputePosition(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now, nowIndex: openIndex); } //每日收盘前,整理持仓情况 int thisIndex = 239; double delta = 0; var thisTime = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(today), thisIndex); if (today >= timeOf50ETFDividend2016) { benchmark.Add(etfData[thisIndex].close + bonusOf50ETFDividend2016); } else { benchmark.Add(etfData[thisIndex].close); } if (holdingStatus.callPosition != 0) { if (dataToday.ContainsKey(holdingStatus.callCode) == false) { var callLastDay = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.callCode, today); dataToday.Add(holdingStatus.callCode, callLastDay.Cast <KLine>().ToList()); } if (dataToday.ContainsKey(holdingStatus.putCode) == false) { var putLastDay = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.putCode, today); dataToday.Add(holdingStatus.putCode, putLastDay.Cast <KLine>().ToList()); } //计算期权delta值,并用50ETF对冲 var positionLast = positions.Last().Value; delta = computeOptionDelta(positionLast, holdingStatus, today, dataToday, thisIndex); double etfChangeVolume = Math.Round(-delta - holdingStatus.etfPosition); MinuteSignal openSignalETF = new MinuteSignal() { code = "510050.SH", volume = etfChangeVolume, time = thisTime, tradingVarieties = "stock", price = dataToday["510050.SH"][thisIndex].open, minuteIndex = thisIndex }; signal = new Dictionary <string, MinuteSignal>(); signal.Add("510050.SH", openSignalETF); MinuteTransactionWithBar.ComputePosition(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: thisTime, nowIndex: thisIndex); holdingStatus.etfPosition += etfChangeVolume; // AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, thisTime, dataToday); } //更新当日属性信息 AccountUpdatingWithMinuteBar.computeAccount(ref myAccount, positions, thisTime, thisIndex, dataToday); //记录历史仓位信息 accountHistory.Add(new BasicAccount(myAccount.time, myAccount.totalAssets, myAccount.freeCash, myAccount.positionValue, myAccount.margin, myAccount.initialAssets)); //在控制台上数据每日持仓信息 if (holdingStatus.callPosition != 0) { Console.WriteLine("time: {0},etf: {1}, strike: {2}, position: {3}, call: {4}, put: {5}, endDate: {6}, delta: {7}, etfVolume: {8}", thisTime, etfData[thisIndex].close, holdingStatus.strike, holdingStatus.callPosition, dataToday[holdingStatus.callCode][thisIndex].close, dataToday[holdingStatus.putCode][thisIndex].close, holdingStatus.endDate, delta, holdingStatus.etfPosition); } //Console.WriteLine("time: {0}, total: {1}, cash: {2}, option: {3}, margin: {4}", thisTime, myAccount.totalAssets, myAccount.freeCash, myAccount.positionValue, myAccount.margin); } //策略绩效统计及输出 PerformanceStatisics myStgStats = new PerformanceStatisics(); myStgStats = PerformanceStatisicsUtils.compute(accountHistory, positions, benchmark.ToArray()); //画图 Dictionary <string, double[]> line = new Dictionary <string, double[]>(); double[] netWorth = accountHistory.Select(a => a.totalAssets / initialCapital).ToArray(); line.Add("NetWorth", netWorth); //记录净值数据 RecordUtil.recordToCsv(accountHistory, GetType().FullName, "account", parameters: "straddle", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录持仓变化 var positionStatus = OptionRecordUtil.Transfer(positions); RecordUtil.recordToCsv(positionStatus, GetType().FullName, "positions", parameters: "straddle", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录统计指标 var performanceList = new List <PerformanceStatisics>(); performanceList.Add(myStgStats); RecordUtil.recordToCsv(performanceList, GetType().FullName, "performance", parameters: "straddle", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //统计指标在console 上输出 Console.WriteLine("--------Strategy Performance Statistics--------\n"); Console.WriteLine(" netProfit:{0,5:F4} \n totalReturn:{1,-5:F4} \n anualReturn:{2,-5:F4} \n anualSharpe :{3,-5:F4} \n winningRate:{4,-5:F4} \n PnLRatio:{5,-5:F4} \n maxDrawDown:{6,-5:F4} \n maxProfitRatio:{7,-5:F4} \n informationRatio:{8,-5:F4} \n alpha:{9,-5:F4} \n beta:{10,-5:F4} \n averageHoldingRate:{11,-5:F4} \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"); //benchmark净值 List <double> netWorthOfBenchmark = benchmark.Select(x => x / benchmark[0]).ToList(); line.Add("Base", netWorthOfBenchmark.ToArray()); string[] datestr = accountHistory.Select(a => a.time.ToString("yyyyMMdd")).ToArray(); Application.Run(new PLChart(line, datestr)); }
public void compute() { log.Info("开始回测(回测期{0}到{1})", Kit.ToInt_yyyyMMdd(startDate), Kit.ToInt_yyyyMMdd(endDate)); var repo = Platforms.container.Resolve <OptionInfoRepository>(); optionInfoList = repo.fetchFromLocalCsvOrWindAndSaveAndCache(1); Caches.put("OptionInfo", optionInfoList); //初始化头寸信息 SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions = new SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> >(); //初始化Account信息 BasicAccount myAccount = new BasicAccount(); myAccount.initialAssets = initialCapital; myAccount.totalAssets = initialCapital; myAccount.freeCash = myAccount.totalAssets; //初始化持仓信息 StranglePair holdPair = new StranglePair(); //记录历史账户信息 List <BasicAccount> accountHistory = new List <BasicAccount>(); List <double> benchmark = new List <double>(); //可以变动的策略参数 int length = 40; double range = 0.04; //50ETF的日线数据准备,从回测期开始之前100个交易开始取 int number = 100; List <StockDaily> dailyData = new List <StockDaily>(); dailyData = Platforms.container.Resolve <StockDailyRepository>().fetchFromLocalCsvOrWindAndSave(targetVariety, DateUtils.PreviousTradeDay(startDate, number), endDate); var closePrice = dailyData.Select(x => x.close).ToArray(); var ETFMA = TA_MA.SMA(closePrice, length); //获取中国波指的数据 List <StockDaily> iVix = Platforms.container.Resolve <StockDailyRepository>().fetchFromLocalCsvOrWindAndSave("000188.SH", startDate, endDate); //按交易日回测 for (int day = 0; day < tradeDays.Count(); day++) { benchmark.Add(closePrice[day + number]); double MAyesterday = ETFMA[day + number - 1]; double lastETFPrice = dailyData[number + day - 1].close; var today = tradeDays[day]; //获取当日上市的期权合约列表 var optionInfoList = OptionUtilities.getUnmodifiedOptionInfoList(this.optionInfoList, today); //初始化信号的数据结构 Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); //获取今日日内50ETF数据 var etfData = Platforms.container.Resolve <StockMinuteRepository>().fetchFromLocalCsvOrWindAndSave(targetVariety, tradeDays[day]); //初始化行情信息,将50ETF的价格放入dataToday Dictionary <string, List <KLine> > dataToday = new Dictionary <string, List <KLine> >(); dataToday.Add(targetVariety, etfData.Cast <KLine>().ToList()); //记录今日账户信息 myAccount.time = today; //获取今日期权的到期日期 var dateStructure = OptionUtilities.getDurationStructure(optionInfoList, tradeDays[day]); //选定到日期在40个交易日至60个交易日的合约 double duration = 0; //for (int i = 0; i < dateStructure.Count(); i++) //{ // if (dateStructure[i] >= 40 && dateStructure[i] <= 80) // { // duration = dateStructure[i]; // break; // } //} duration = dateStructure[1]; //如果没有持仓就开仓 if (holdPair.endDate == new DateTime()) { if (duration == 0) { continue; } //按照开盘1分钟的价格来开平仓。 int index = 0; DateTime now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(tradeDays[day]), index); double etfPriceNow = etfData[index].open; if (etfPriceNow > MAyesterday * (1 + range))//看涨,卖出虚值的put { var list = OptionUtilities.getOptionListByDate(OptionUtilities.getOptionListByStrike(OptionUtilities.getOptionListByOptionType(OptionUtilities.getOptionListByDuration(optionInfoList, today, duration), "认沽"), etfPriceNow - 0.1, etfPriceNow - 0.5), Kit.ToInt_yyyyMMdd(today)).OrderBy(x => - x.strike).ToList(); if (list.Count == 0) { continue; } OptionInfo put = list[0]; if (put.strike != 0 && (put.modifiedDate > today.AddDays(10) || put.modifiedDate < today)) //开仓 { tradeAssistant(ref dataToday, ref signal, put.optionCode, -put.contractMultiplier, today, now, index); holdPair = new StranglePair() { callCode = "", putCode = put.optionCode, callPosition = 0, putPosition = -put.contractMultiplier, endDate = put.endDate, etfPrice = etfPriceNow, callStrike = 0, putStrike = put.strike }; MinuteTransactionWithBar.ComputePosition(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now, nowIndex: index); } } else if (etfPriceNow < MAyesterday * (1 - range))//看跌,卖出虚值的call { var list = OptionUtilities.getOptionListByDate(OptionUtilities.getOptionListByStrike(OptionUtilities.getOptionListByOptionType(OptionUtilities.getOptionListByDuration(optionInfoList, today, duration), "认购"), etfPriceNow + 0.1, etfPriceNow + 0.5), Kit.ToInt_yyyyMMdd(today)).OrderBy(x => x.strike).ToList(); if (list.Count == 0) { continue; } OptionInfo call = list[0]; if (call.strike != 0 && (call.modifiedDate > today.AddDays(10) || call.modifiedDate < today)) //开仓 { tradeAssistant(ref dataToday, ref signal, call.optionCode, -call.contractMultiplier, today, now, index); holdPair = new StranglePair() { callCode = call.optionCode, putCode = "", callPosition = -call.contractMultiplier, putPosition = 0, endDate = call.endDate, etfPrice = etfPriceNow, callStrike = call.strike, putStrike = 0 }; MinuteTransactionWithBar.ComputePosition(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now, nowIndex: index); } } else//不涨不跌,卖出宽跨式期权 { var putList = OptionUtilities.getOptionListByDate(OptionUtilities.getOptionListByStrike(OptionUtilities.getOptionListByOptionType(OptionUtilities.getOptionListByDuration(optionInfoList, today, duration), "认沽"), etfPriceNow - 0.1, etfPriceNow - 0.5), Kit.ToInt_yyyyMMdd(today)).OrderBy(x => - x.strike).ToList(); var callList = OptionUtilities.getOptionListByDate(OptionUtilities.getOptionListByStrike(OptionUtilities.getOptionListByOptionType(OptionUtilities.getOptionListByDuration(optionInfoList, today, duration), "认购"), etfPriceNow + 0.1, etfPriceNow + 0.5), Kit.ToInt_yyyyMMdd(today)).OrderBy(x => x.strike).ToList(); if (putList.Count == 0 || callList.Count == 0) { continue; } OptionInfo call = callList[0]; OptionInfo put = putList[0]; if (put.strike != 0 && (put.modifiedDate > today.AddDays(10) || put.modifiedDate < today) && call.strike != 0 && (call.modifiedDate > today.AddDays(10) || call.modifiedDate < today)) //开仓 { tradeAssistant(ref dataToday, ref signal, put.optionCode, -put.contractMultiplier, today, now, index); tradeAssistant(ref dataToday, ref signal, call.optionCode, -call.contractMultiplier, today, now, index); holdPair = new StranglePair() { callCode = call.optionCode, putCode = put.optionCode, callPosition = -call.contractMultiplier, putPosition = -put.contractMultiplier, endDate = put.endDate, etfPrice = etfPriceNow, callStrike = call.strike, putStrike = put.strike }; MinuteTransactionWithBar.ComputePosition(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now, nowIndex: index); } } } else //如果有持仓就判断需不需要移仓 { double durationNow = DateUtils.GetSpanOfTradeDays(today, holdPair.endDate); int index = 234; DateTime now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(tradeDays[day]), index); if (holdPair.callPosition != 0) { tradeAssistant(ref dataToday, ref signal, holdPair.callCode, -holdPair.callPosition, today, now, index); } if (holdPair.putPosition != 0) { tradeAssistant(ref dataToday, ref signal, holdPair.putCode, -holdPair.putPosition, today, now, index); } if (durationNow <= 10) //强制平仓 { //按照收盘前5分钟的价格来开平仓。 MinuteTransactionWithBar.ComputePosition(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now, nowIndex: index); holdPair = new StranglePair(); } } if (etfData.Count > 0) { //更新当日属性信息 AccountUpdatingWithMinuteBar.computeAccount(ref myAccount, positions, etfData.Last().time, etfData.Count() - 1, dataToday); //记录历史仓位信息 accountHistory.Add(new BasicAccount(myAccount.time, myAccount.totalAssets, myAccount.freeCash, myAccount.positionValue, myAccount.margin, myAccount.initialAssets)); benchmark.Add(etfData.Last().close); if (netValue.Count() == 0) { netValue.Add(new NetValue { time = today, netvalueReturn = 0, benchmarkReturn = 0, netvalue = myAccount.totalAssets, benchmark = etfData.Last().close }); } else { var netValueLast = netValue.Last(); netValue.Add(new NetValue { time = today, netvalueReturn = myAccount.totalAssets / netValueLast.netvalue - 1, benchmarkReturn = etfData.Last().close / netValueLast.benchmark - 1, netvalue = myAccount.totalAssets, benchmark = etfData.Last().close }); } } } //策略绩效统计及输出 PerformanceStatisics myStgStats = new PerformanceStatisics(); myStgStats = PerformanceStatisicsUtils.compute(accountHistory, positions); //画图 Dictionary <string, double[]> line = new Dictionary <string, double[]>(); double[] netWorth = accountHistory.Select(a => a.totalAssets / initialCapital).ToArray(); line.Add("NetWorth", netWorth); //记录净值数据 RecordUtil.recordToCsv(accountHistory, GetType().FullName, "account", parameters: "ShortOptionByMA", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录持仓变化 var positionStatus = OptionRecordUtil.Transfer(positions); RecordUtil.recordToCsv(positionStatus, GetType().FullName, "positions", parameters: "ShortOptionByMA", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录统计指标 var performanceList = new List <PerformanceStatisics>(); performanceList.Add(myStgStats); RecordUtil.recordToCsv(performanceList, GetType().FullName, "performance", parameters: "ShortOptionByMA", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //统计指标在console 上输出 Console.WriteLine("--------Strategy Performance Statistics--------\n"); Console.WriteLine(" netProfit:{0,5:F4} \n totalReturn:{1,-5:F4} \n anualReturn:{2,-5:F4} \n anualSharpe :{3,-5:F4} \n winningRate:{4,-5:F4} \n PnLRatio:{5,-5:F4} \n maxDrawDown:{6,-5:F4} \n maxProfitRatio:{7,-5:F4} \n informationRatio:{8,-5:F4} \n alpha:{9,-5:F4} \n beta:{10,-5:F4} \n averageHoldingRate:{11,-5:F4} \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"); //benchmark净值 List <double> netWorthOfBenchmark = benchmark.Select(x => x / benchmark[0]).ToList(); line.Add("50ETF", netWorthOfBenchmark.ToArray()); // ivix数据 double[] iVixClose = iVix.Select(x => x.close / iVix[0].close).ToArray(); //line.Add("iVix", iVixClose); string[] datestr = accountHistory.Select(a => a.time.ToString("yyyyMMdd")).ToArray(); //maoheng 画图 //Application.Run(new PLChart(line, datestr)); //cuixun 画图 //绘制图形的标题 string formTitle = this.startDate.ToShortDateString() + "--" + this.endDate.ToShortDateString() + " " + this.targetVariety + " 净值曲线" + "\r\n" + "\r\n" + "净利润:" + myStgStats.netProfit + " " + "夏普率:" + myStgStats.anualSharpe + " " + "最大回撤:" + myStgStats.maxDrawDown + "\r\n" + "\r\n"; //生成图像 PLChart plc = new PLChart(line, datestr, formTitle: formTitle); //运行图像 //Application.Run(plc); plc.LoadForm(); //保存图像 plc.SaveZed(GetType().FullName, this.targetVariety, this.startDate, this.endDate, myStgStats.netProfit.ToString(), myStgStats.anualSharpe.ToString(), myStgStats.maxDrawDown.ToString()); }
/// <summary> /// /// </summary> /// <param name="signal">交易信号(正向/反向)</param> /// <param name="data">KLine格式的交易数据</param> /// <param name="positions">头寸信息</param> /// <param name="myAccount">账户信息</param> /// <param name="now">交易日的时间信息</param> /// <param name="nowIndex">当前索引值(不知道什么意思)</param> /// <param name="slipPoint">滑点</param> /// <returns></returns> public static Dictionary <string, ExecutionReport> ComputePosition(Dictionary <string, MinuteSignal> signal, Dictionary <string, List <KLine> > data, ref SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions, ref BasicAccount myAccount, DateTime now, int nowIndex, double slipPoint = 0.00) { //初始化记录成交回报的变量 Dictionary <string, ExecutionReport> tradingFeedback = new Dictionary <string, ExecutionReport>(); //初始化上一次头寸记录时间 DateTime lastTime = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0); //如果signal无信号,无法成交,直接返回空的成交回报。 if (signal == null || signal.Count == 0) { return(tradingFeedback); } if (positions.Count != 0) { lastTime = positions.Keys.Last(); } //新建头寸变量,作为接受新仓位的容器 Dictionary <string, PositionsWithDetail> positionShot = new Dictionary <string, PositionsWithDetail>(); //如果持仓最后状态时间大于signal信号的时间,无成交,直接返回空的成交回报。 if (lastTime > now) { return(tradingFeedback); } //如果两者时间相等,则把总仓位变化数组positions中的最后一项,添加进新仓位的容器positionShot中 if (now == lastTime) { positionShot = positions[positions.Keys.Last()]; } //如果交易信号时间在最后一次持仓变化时间点之后,则需要重新把最后持仓的仓位变化信息手工copy一份; //然后添加进新仓位的容器positionShot中。 else if (positions.Count > 0) //如果持仓大于0 { foreach (var item in positions[positions.Keys.Last()]) //循环 持仓最后状态时间的持仓数据 { //这里必须手动copy一份,不可以直接传引用。因为最后持仓变化节点的仓位信息是不应该被改变的; //如果直接传引用,交易信号时间点仓位变化会同时改变最后持仓变化节点的仓位信息。 PositionsWithDetail position0 = new PositionsWithDetail().myClone(item.Value);//复制一份新的 positionShot.Add(position0.code, position0); } } //获取前一步的头寸信息,如果没有寸头就设为null Dictionary <string, PositionsWithDetail> positionLast = (positions.Count == 0 ? null : positions[positions.Keys.Last()]); //对信号进行遍历 foreach (var signal0 in signal.Values) { //整理成交信号,剔除不合理信号。 //①信号触发时间必须在positionLast的记录时间之后,在当前时间now之前。 //②信号必须有合理的交易数量。 //③信号必须有对应的数据。 if (signal0.time != now) //【???】不是说必须要在当前时间now之前么 { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.status = "交易时间错误,无效信号"; tradingFeedback.Add(signal0.code, report0); continue; } if (signal0.volume == 0 || signal0.price == 0) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.status = "交易数据错误,无效信号"; tradingFeedback.Add(signal0.code, report0); continue; } if (data.ContainsKey(signal0.code) == false) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.status = "无法找到行情数据,无效信号"; tradingFeedback.Add(signal0.code, report0); continue; } //根据K线来判断成交数量,有可能完全成交,也有可能部分成交 //开多头时,如果价格大于最低价,完全成交,否者不成交 //开空头时,如果价格小于最高价,完全成交,否者不成交 //找出对应的K线 KLine KLineData = data[signal0.code][nowIndex]; //确定滑点 double slip = Math.Max(slipPoint, signal0.bidAskSpread); //开多头时,如果价格大于最低价,完全成交,否者不成交 if (signal0.volume > 0 && signal0.price >= KLineData.low) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.volume = signal0.volume; report0.price = signal0.price + slip; report0.status = "完全成交"; tradingFeedback.Add(signal0.code, report0); } if (signal0.volume > 0 && signal0.price < KLineData.low) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.price = signal0.price + slip; report0.status = "无法成交"; tradingFeedback.Add(signal0.code, report0); continue; } //开空头时,如果价格小于最高价,完全成交,否者不成交 if (signal0.volume < 0 && signal0.price <= KLineData.high) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.price = signal0.price - slip; report0.volume = signal0.volume; report0.status = "完全成交"; tradingFeedback.Add(signal0.code, report0); } if (signal0.volume < 0 && signal0.price > KLineData.high) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.price = signal0.price - slip; report0.status = "无法成交"; tradingFeedback.Add(signal0.code, report0); continue; } ///【解释:以下部分 position,positionShot和position0的关系】 /// position:是传入的参数, /// //接下来处理能够成交的signal0,信号下单的时间只能是lastTime或者now。 PositionsWithDetail position0 = new PositionsWithDetail(); //查询当前持仓数量 double nowHoldingVolume; //当前证券已有持仓 if (positionShot.Count > 0 && positionShot.ContainsKey(signal0.code)) { //将当前证券持仓情况赋值给临时持仓变量 position0 = positionShot[signal0.code]; nowHoldingVolume = position0.volume; } else //若历史无持仓 { //当前信号证券代码 position0.code = signal0.code; //多空头寸初始化 position0.LongPosition = new PositionDetail(); position0.ShortPosition = new PositionDetail(); position0.record = new List <TransactionRecord>(); nowHoldingVolume = 0; positionShot.Add(position0.code, position0); } //持仓和开仓方向一致 if (nowHoldingVolume * signal0.volume >= 0) { //开多仓 if (signal0.volume > 0) { //重新计算仓位和价格 double volume = signal0.volume + position0.LongPosition.volume; double cost = (signal0.price + slip) * signal0.volume + position0.LongPosition.volume * position0.LongPosition.averagePrice; double averagePrice = cost / volume; position0.LongPosition = new PositionDetail { volume = volume, totalCost = cost, averagePrice = averagePrice }; } else //开空仓 { //重新计算仓位和价格 double volume = signal0.volume + position0.ShortPosition.volume; double cost = (signal0.price - slip) * signal0.volume + position0.ShortPosition.volume * position0.ShortPosition.averagePrice; double averagePrice = cost / volume; position0.ShortPosition = new PositionDetail { volume = volume, totalCost = cost, averagePrice = averagePrice }; } } else //持仓和开仓方向不一致 { if (nowHoldingVolume > 0) //原先持有多头头寸,现开空仓 { //计算总头寸,分类讨论 double volume = signal0.volume + position0.LongPosition.volume; if (volume > 0) { position0.LongPosition.volume = volume; position0.LongPosition.totalCost = position0.LongPosition.volume * position0.LongPosition.averagePrice; } else if (volume < 0) { position0.ShortPosition.volume = volume; position0.ShortPosition.totalCost = volume * (signal0.price - slip); position0.ShortPosition.averagePrice = (signal0.price - slip); position0.LongPosition = new PositionDetail(); } else { position0.LongPosition = new PositionDetail(); } } else //原先持有空头头寸,现开多仓 { //计算总头寸,分类讨论 double volume = signal0.volume + position0.ShortPosition.volume; if (volume < 0) { position0.ShortPosition.volume = volume; position0.ShortPosition.totalCost = position0.ShortPosition.volume * position0.ShortPosition.averagePrice; } else if (volume > 0) { position0.LongPosition.volume = volume; position0.LongPosition.averagePrice = (signal0.price + slip); position0.LongPosition.totalCost = (signal0.price - slip) * volume; position0.ShortPosition = new PositionDetail(); } else { position0.ShortPosition = new PositionDetail(); } } } //更新其他信息 position0.record.Add(new TransactionRecord { time = now, volume = signal0.volume, price = signal0.price + slip * (signal0.volume > 0?1:-1), code = position0.code }); position0.totalCashFlow = position0.totalCashFlow - (signal0.price + (signal0.volume > 0 ? 1 : -1) * slip) * signal0.volume; position0.transactionCost = position0.transactionCost + Math.Abs(slip * signal0.volume); position0.volume = position0.volume + signal0.volume; position0.time = now; position0.code = signal0.code; position0.currentPrice = KLineData.close; position0.tradingVarieties = signal0.tradingVarieties; //总资产=权益现值+现金盈亏 position0.totalAmt = position0.totalCashFlow + position0.volume * position0.currentPrice; } if (now > lastTime) { positions.Add(now, positionShot); } //更新持仓的头寸信息 if (positions.Count != 0) { AccountUpdatingWithMinuteBar.computeAccount(ref myAccount, positions, now, nowIndex, data); } return(tradingFeedback); }