/// <summary> /// 平仓(所有的) /// </summary> /// <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> CloseAllPosition(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>(); //初始化平仓信号 Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); //查询当前持仓情况 Dictionary <string, PositionsWithDetail> positionShot = new Dictionary <string, PositionsWithDetail>(); //获取最新持仓情况 Dictionary <string, PositionsWithDetail> positionLast = (positions.Count == 0 ? null : positions[positions.Keys.Last()]); //检查最新持仓,若无持仓,直接返回 if (positionLast == null) { return(tradingFeedback); } else { positionShot = new Dictionary <string, PositionsWithDetail>(positionLast); } //生成清仓信号 foreach (var position0 in positionShot.Values) { //若当前品种持仓量为0(仅用于记录历史持仓) if (position0.volume == 0) { continue; } int index = nowIndex; //对所有的持仓,生成现价等量反向的交易信号(cuixun 也就是平仓信号) MinuteSignal nowSignal = new MinuteSignal() { code = position0.code, volume = -position0.volume, time = now, tradingVarieties = position0.tradingVarieties, price = data[position0.code][index].open, minuteIndex = index }; signal.Add(nowSignal.code, nowSignal); } return(MinuteTransactionWithBar.ComputePosition(signal, data, ref positions, ref myAccount, slipPoint: slipPoint, now: now, nowIndex: nowIndex)); }
private void tradeAssistant(ref Dictionary <string, List <KLine> > dataToday, ref Dictionary <string, MinuteSignal> signal, string code, double volume, DateTime today, DateTime now, int index) { List <OptionMinute> myData = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(code, today); //获取给定的期权合约的当日分钟数据 if (dataToday.ContainsKey(code) == false) { dataToday.Add(code, myData.Cast <KLine>().ToList()); } if (volume != 0) { MinuteSignal signal0 = new MinuteSignal() { code = code, volume = volume, time = now, tradingVarieties = "option", price = myData[index].open, minuteIndex = index }; signal.Add(code, signal0); } }
/// <summary> /// 清空当前所有持仓,对所有的持仓生成平仓信号(等量反向) /// </summary> /// <param name="data"></param> /// <param name="positions"></param> /// <param name="myAccount"></param> /// <param name="now"></param> public static DateTime closeAllPositions(Dictionary <string, List <KLine> > data, ref SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions, ref BasicAccount myAccount, DateTime now, double slipPoint = 0.003) { Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); //查询当前持仓情况 Dictionary <string, PositionsWithDetail> positionShot = new Dictionary <string, PositionsWithDetail>(); Dictionary <string, PositionsWithDetail> positionLast = (positions.Count == 0 ? null : positions[positions.Keys.Last()]); //检查最新持仓,若无持仓,直接返回 bool isEmptyPosition = positions.Count != 0 ? positionLast.Values.Sum(x => Math.Abs(x.volume)) == 0 : true; if (positionLast == null || isEmptyPosition) { return(now.AddMinutes(1)); } else { positionShot = new Dictionary <string, PositionsWithDetail>(positionLast); } //生成清仓信号 foreach (var position0 in positionShot.Values) { //若当前品种无持仓,跳过 if (position0.volume == 0) { continue; } //对所有的持仓,生成现价等量反向的交易信号 int index = TimeListUtility.MinuteToIndex(now); MinuteSignal nowSignal = new MinuteSignal() { code = position0.code, volume = -position0.volume, time = now, tradingVarieties = "option", price = data[position0.code][index].close, minuteIndex = index }; signal.Add(nowSignal.code, nowSignal); } //将清仓信号传给成交判断 DateTime next = MinuteTransactionWithSlip2.computeMinutePositions2(signal, data, ref positions, ref myAccount, slipPoint: slipPoint, now: now); return(next); }
public void compute() { 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; //记录历史账户信息 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++) { 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期权合约代码 DateTime today = etfDailyData[i].time; 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 (optionVol[i - 1] < volYesterday) //{ // //买入跨式期权 // orignalSignal = 1; //} //else if (optionVol[i - 1] - volYesterday > epsilon[i - 1]) //{ // //卖出跨式期权 // orignalSignal = -1; //} //信号2 orignalSignal = 0; if (volYesterday - impvYesterday > 0 && volYesterday <= fractile70Yesterday) { //买入跨式期权 //orignalSignal = 1; } else if (impvYesterday - volYesterday > 2 * epsilon[i - 1]) { //卖出跨式期权 orignalSignal = -1; } //指定平仓时间为开盘第一个分钟。 DateTime now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(today), 0); Console.WriteLine("time: {0}, 昨日历史波动率: {1}, 历史波动率70分位数: {2}, 昨日隐含波动率: {3}", now, volYesterday.ToString("N"), fractile70Yesterday.ToString("N"), optionVol[i - 1].ToString("N")); //如果有持仓先判断持仓状态和信号方向是否相同,如果不同先平仓 if (holdingStatus.callPosition != 0) { //平仓之前获取IH数据 if (holdingStatus.IHCode != null) { var IHData = Platforms.container.Resolve <FuturesMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.IHCode, today); dataToday.Add(holdingStatus.IHCode, IHData.Cast <KLine>().ToList()); } 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("平仓!"); MinuteCloseAllPositonsWithSlip.closeAllPositions(dataToday, ref positions, ref myAccount, now, slipPoint); holdingStatus = new Straddle(); } if (DateUtils.GetSpanOfTradeDays(today, holdingStatus.endDate) <= 3) //有仓位无信号,判断是否移仓 { Console.WriteLine("平仓!"); MinuteCloseAllPositonsWithSlip.closeAllPositions(dataToday, ref positions, ref myAccount, now, slipPoint); holdingStatus = new Straddle(); } } //指定开仓时间为开盘第10分钟。错开开平仓的时间。 int 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].close, minuteIndex = openIndex }; MinuteSignal openSignalPut = new MinuteSignal() { code = putATM.optionCode, volume = optionVolume, time = now, tradingVarieties = "option", price = putPrice[openIndex].close, 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].close; holdingStatus.straddlePrice_open = callPrice[openIndex].close + putPrice[openIndex].close; 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].close, minuteIndex = openIndex }; MinuteSignal openSignalPut = new MinuteSignal() { code = putATM.optionCode, volume = -optionVolume, time = now, tradingVarieties = "option", price = putPrice[openIndex].close, 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].close; holdingStatus.straddlePrice_open = callPrice[openIndex].close + putPrice[openIndex].close; holdingStatus.straddleOpenDate = today; holdingStatus.endDate = callATM.endDate; holdingStatus.strike = callATM.strike; } MinuteTransactionWithSlip.computeMinuteOpenPositions(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now, capitalVerification: false); } //每日收盘前,整理持仓情况 int thisIndex = 239; var thisTime = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(today), thisIndex); 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()); } AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, thisTime, dataToday); } //更新当日属性信息 AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, thisTime, 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}", thisTime, etfData[thisIndex].close, holdingStatus.strike, holdingStatus.callPosition, dataToday[holdingStatus.callCode][thisIndex].close, dataToday[holdingStatus.putCode][thisIndex].close, holdingStatus.endDate); } //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>(); var OptionInfoList = repo.fetchFromLocalCsvOrWindAndSaveAndCache(1); Caches.put("OptionInfo", OptionInfoList); List <DateTime> tradeDays = DateUtils.GetTradeDays(startdate, endDate); //var ETFDaily = Platforms.container.Resolve<StockDailyRepository>().fetchFromLocalCsvOrWindAndSave("510050.SH", Kit.ToDate(20150101),Kit.ToDate(20160731)); foreach (var day in tradeDays) { Dictionary <string, List <KLine> > data = new Dictionary <string, List <KLine> >(); var list = OptionUtilities.getOptionListByDate(OptionInfoList, Kit.ToInt_yyyyMMdd(day)); List <DateTime> durationArr = OptionUtilities.getEndDateListByAscending(list); var ETFtoday = Platforms.container.Resolve <StockMinuteRepository>().fetchFromLocalCsvOrWindAndSave("510050.SH", day); data.Add("510050.SH", ETFtoday.Cast <KLine>().ToList()); foreach (var info in list) { string IHCode = OptionUtilities.getCorrespondingIHCode(info, Kit.ToInt_yyyyMMdd(day)); var repoOption = Platforms.container.Resolve <OptionMinuteRepository>(); var optionToday = repoOption.fetchFromLocalCsvOrWindAndSave(info.optionCode, day); data.Add(info.optionCode, optionToday.Cast <KLine>().ToList()); } int index = 0; SortedDictionary <DateTime, Dictionary <string, MinutePositions> > positions = new SortedDictionary <DateTime, Dictionary <string, MinutePositions> >(); while (index < 240) { int nextIndex = index + 1; DateTime now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(day), index); Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); double etfPrice = ETFtoday[index].close; List <double> strikeTodayArr = OptionUtilities.getStrikeListByAscending(list).OrderBy(x => Math.Abs(x - etfPrice)).ToList(); try { OptionInfo callCandidateFront = OptionUtilities.getSpecifiedOption(list, durationArr[0], "认购", strikeTodayArr[0])[0]; OptionInfo putCandidateFront = OptionUtilities.getSpecifiedOption(list, durationArr[0], "认沽", strikeTodayArr[0])[0]; OptionInfo callCandidateNext = OptionUtilities.getSpecifiedOption(list, durationArr[1], "认购", strikeTodayArr[0])[0]; OptionInfo putCandidateNext = OptionUtilities.getSpecifiedOption(list, durationArr[1], "认沽", strikeTodayArr[0])[0]; MinuteSignal callFront = new MinuteSignal() { code = callCandidateFront.optionCode, volume = -1, time = now, tradingVarieties = "option", price = data[callCandidateFront.optionCode][index].close, minuteIndex = index }; MinuteSignal putFront = new MinuteSignal() { code = putCandidateFront.optionCode, volume = -1, time = now, tradingVarieties = "option", price = data[putCandidateFront.optionCode][index].close, minuteIndex = index }; MinuteSignal callNext = new MinuteSignal() { code = callCandidateNext.optionCode, volume = 1, time = now, tradingVarieties = "option", price = data[callCandidateNext.optionCode][index].close, minuteIndex = index }; MinuteSignal putNext = new MinuteSignal() { code = putCandidateNext.optionCode, volume = 1, time = now, tradingVarieties = "option", price = data[putCandidateNext.optionCode][index].close, minuteIndex = index }; signal.Add(callFront.code, callFront); signal.Add(putFront.code, putFront); signal.Add(callNext.code, callNext); signal.Add(putNext.code, putNext); DateTime next = MinuteTransactionWithSlip.computeMinutePositions(signal, data, ref positions, slipPoint: 0.01, now: now); nextIndex = Math.Max(nextIndex, TimeListUtility.MinuteToIndex(next)); } catch (Exception) { throw; } index = nextIndex; } } }
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() { log.Info("开始回测(回测期{0}到{1})", Kit.ToInt_yyyyMMdd(startDate), Kit.ToInt_yyyyMMdd(endDate)); var repo = Platforms.container.Resolve <OptionInfoRepository>(); var 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; //记录历史账户信息 List <BasicAccount> accountHistory = new List <BasicAccount>(); List <double> benchmark = new List <double>(); ///数据准备 //记录牛市价差两条腿的信息 BullSpread myLegs = new BullSpread(); //交易日信息 List <DateTime> tradeDays = DateUtils.GetTradeDays(startDate, endDate); //50ETF的日线数据准备,从回测期开始之前100个交易开始取 int number = 100; List <StockDaily> dailyData = new List <StockDaily>(); dailyData = Platforms.container.Resolve <StockDailyRepository>().fetchFromLocalCsvOrWindAndSave(targetVariety, DateUtils.PreviousTradeDay(startDate, number), endDate); //计算50ETF的EMA var closePrice = dailyData.Select(x => x.close).ToArray(); List <double> ema7 = TA_MA.EMA(closePrice, 5).ToList(); List <double> ema50 = TA_MA.EMA(closePrice, 20).ToList(); List <double> ema10 = TA_MA.EMA(closePrice, 10).ToList(); double maxProfit = 0; for (int day = 1; day < tradeDays.Count(); day++) { benchmark.Add(closePrice[day + number]); var today = tradeDays[day]; myAccount.time = today; var dateStructure = OptionUtilities.getDurationStructure(optionInfoList, tradeDays[day]); double duration = 0; for (int i = 0; i < dateStructure.Count(); i++) { if (dateStructure[i] >= 20 && dateStructure[i] <= 40) { duration = dateStructure[i]; break; } } Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); var etfData = Platforms.container.Resolve <StockMinuteRepository>().fetchFromLocalCsvOrWindAndSave(targetVariety, tradeDays[day]); if (ema7[day + number - 1] - ema50[day + number - 1] > 0 && dailyData[number + day - 1].close > ema10[day + number - 1] && myLegs.strike1 == 0) // EMA7日线大于EMA50日线,并且ETF价格站上EMA10,开牛市价差 { //取出指定日期 double lastETFPrice = dailyData[number + day - 1].close; Dictionary <string, List <KLine> > dataToday = new Dictionary <string, List <KLine> >(); dataToday.Add(targetVariety, etfData.Cast <KLine>().ToList()); DateTime now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(tradeDays[day]), 0); //MinuteSignal openSignal = new MinuteSignal() { code = targetVariety, volume = 10000, time = now, tradingVarieties = "stock", price =averagePrice, minuteIndex = day }; //signal.Add(targetVariety, openSignal); //选取指定的看涨期权 var list = OptionUtilities.getOptionListByDate(OptionUtilities.getOptionListByStrike(OptionUtilities.getOptionListByOptionType(OptionUtilities.getOptionListByDuration(optionInfoList, tradeDays[day], duration), "认购"), lastETFPrice, lastETFPrice + 0.5), Kit.ToInt_yyyyMMdd(today)).OrderBy(x => x.strike).ToList(); //如果可以构成看涨期权牛市价差,就开仓 if (list.Count() >= 2) { var option1 = list[0]; var option2 = list[list.Count() - 1]; var option1Data = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(option1.optionCode, today); var option2Data = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(option2.optionCode, today); if ((option1Data[0].close > 0 && option2Data[0].close > 0) == true) { dataToday.Add(option1.optionCode, option1Data.Cast <KLine>().ToList()); dataToday.Add(option2.optionCode, option2Data.Cast <KLine>().ToList()); //var vol1 = ImpliedVolatilityUtilities.ComputeImpliedVolatility(option1.strike, duration / 252.0, 0.04, 0, option1.optionType, option1Data[0].close, etfData[0].close); //var vol2 = ImpliedVolatilityUtilities.ComputeImpliedVolatility(option2.strike, duration / 252.0, 0.04, 0, option2.optionType, option2Data[0].close, etfData[0].close); MinuteSignal openSignal1 = new MinuteSignal() { code = option1.optionCode, volume = 10000, time = now, tradingVarieties = "option", price = option1Data[0].close, minuteIndex = 0 }; MinuteSignal openSignal2 = new MinuteSignal() { code = option2.optionCode, volume = -10000, time = now, tradingVarieties = "option", price = option2Data[0].close, minuteIndex = 0 }; Console.WriteLine("开仓!"); signal.Add(option1.optionCode, openSignal1); signal.Add(option2.optionCode, openSignal2); myLegs.code1 = option1.optionCode; myLegs.code2 = option2.optionCode; myLegs.strike1 = option1.strike; myLegs.strike2 = option2.strike; myLegs.endDate = option1.endDate; myLegs.spreadPrice_Open = option1Data[0].close - option2Data[0].close; myLegs.etfPrice_Open = etfData[0].close; myLegs.spreadOpenDate = now; maxProfit = 0; Console.WriteLine("time: {0},etf: {1}, call1: {2} call1price: {3}, call2: {4}, call2price: {5}", now, etfData[0].close, myLegs.strike1, option1Data[0].close, myLegs.strike2, option2Data[0].close); } } MinuteTransactionWithSlip.computeMinuteOpenPositions(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: now, capitalVerification: false); } if (positions.Count() > 0 && myLegs.strike1 != 0) { Dictionary <string, List <KLine> > dataToday = new Dictionary <string, List <KLine> >(); dataToday.Add(targetVariety, etfData.Cast <KLine>().ToList()); var option1Data = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(myLegs.code1, today); var option2Data = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(myLegs.code2, today); dataToday.Add(myLegs.code1, option1Data.Cast <KLine>().ToList()); dataToday.Add(myLegs.code2, option2Data.Cast <KLine>().ToList()); int thisIndex = 239; var thisTime = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(today), thisIndex); var etfPriceNow = etfData[thisIndex].close; var durationNow = DateUtils.GetSpanOfTradeDays(today, myLegs.endDate); Console.WriteLine("time: {0},etf: {1}, call1: {2} call1price: {3}, call2: {4}, call2price: {5}", thisTime, etfPriceNow, myLegs.strike1, option1Data[thisIndex].close, myLegs.strike2, option2Data[thisIndex].close); //多个退出条件①收益达到最大收益的60%以上②多日之内不上涨③迅速下跌 double spreadPrice = option1Data[thisIndex].close - option2Data[thisIndex].close; maxProfit = (spreadPrice - myLegs.spreadPrice_Open) > maxProfit ? spreadPrice - myLegs.spreadPrice_Open : maxProfit; double holdingDays = DateUtils.GetSpanOfTradeDays(myLegs.spreadOpenDate, today); //止盈 bool profitTarget = (spreadPrice) > 0.6 * (myLegs.strike2 - myLegs.strike1) && durationNow >= 10; //止损 bool lossTarget1 = (spreadPrice - myLegs.spreadPrice_Open) < 0 && holdingDays > 20; bool lossTarget2 = etfPriceNow < myLegs.strike1 - 0.2; bool lossTarget3 = spreadPrice / myLegs.spreadPrice_Open < 0.6; bool lossTarget4 = maxProfit > 0.02 && (spreadPrice - myLegs.spreadPrice_Open) / maxProfit < 0.8; if (profitTarget || lossTarget1 || lossTarget2 || lossTarget3 || lossTarget4 || durationNow <= 1 || holdingDays >= 7) { Console.WriteLine("平仓!"); maxProfit = 0; myLegs = new BullSpread(); MinuteCloseAllPositonsWithSlip.closeAllPositions(dataToday, ref positions, ref myAccount, thisTime, slipPoint); } AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, thisTime, dataToday); } else { int thisIndex = 239; var thisTime = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(today), thisIndex); Dictionary <string, List <KLine> > dataToday = new Dictionary <string, List <KLine> >(); dataToday.Add(targetVariety, etfData.Cast <KLine>().ToList()); AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, thisTime, dataToday); } BasicAccount tempAccount = new BasicAccount(); tempAccount.time = myAccount.time; tempAccount.freeCash = myAccount.freeCash; tempAccount.margin = myAccount.margin; tempAccount.positionValue = myAccount.positionValue; tempAccount.totalAssets = myAccount.totalAssets; tempAccount.initialAssets = myAccount.initialAssets; accountHistory.Add(tempAccount); } //策略绩效统计及输出 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: "EMA7_EMA50", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录持仓变化 var positionStatus = OptionRecordUtil.Transfer(positions); RecordUtil.recordToCsv(positionStatus, GetType().FullName, "positions", parameters: "EMA7_EMA50", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录统计指标 var performanceList = new List <PerformanceStatisics>(); performanceList.Add(myStgStats); RecordUtil.recordToCsv(performanceList, GetType().FullName, "performance", parameters: "EMA7_EMA50", 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)); }
/// <summary> /// 策略回测部分,期权时间价值策略 /// 在日循环上判断(1)选择操作标的(2)是否开平仓,在分钟循环上进行具体操作 /// 分钟上的操作:(1)开仓(2)到期平仓(3)调仓平值期权(4)止盈止损 /// (1)若直接开平仓,在开盘15分钟时进行操作(2)若判断止盈止损,在收盘15分钟时进行操作 /// </summary> public void compute() { log.Info("开始回测(回测期{0}到{1})", Kit.ToInt_yyyyMMdd(startdate), Kit.ToInt_yyyyMMdd(endDate)); var repo = Platforms.container.Resolve <OptionInfoRepository>(); var optionInfoList = repo.fetchFromLocalCsvOrWindAndSaveAndCache(1); Caches.put("OptionInfo", optionInfoList); List <DateTime> tradeDays = DateUtils.GetTradeDays(startdate, endDate); //var ETFDaily = Platforms.container.Resolve<StockDailyRepository>().fetchFromLocalCsvOrWindAndSave("510050.SH", Kit.ToDate(20150101),Kit.ToDate(20160731)); ///账户初始化 //初始化position SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions = new SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> >(); //初始化Account信息 BasicAccount myAccount = new BasicAccount(); myAccount.totalAssets = initialCapital; myAccount.freeCash = myAccount.totalAssets; //记录历史账户信息 List <BasicAccount> accountHistory = new List <BasicAccount>(); ///回测循环 //回测循环--By Day foreach (var day in tradeDays) { //日内数据准备 Dictionary <string, List <KLine> > data = new Dictionary <string, List <KLine> >(); var list = OptionUtilities.getOptionListByDate(optionInfoList, Kit.ToInt_yyyyMMdd(day)); List <DateTime> endDate = OptionUtilities.getEndDateListByAscending(list); var ETFtoday = Platforms.container.Resolve <StockMinuteRepository>().fetchFromLocalCsvOrWindAndSave("510050.SH", day); data.Add("510050.SH", ETFtoday.Cast <KLine>().ToList()); foreach (var info in list) { string IHCode = OptionUtilities.getCorrespondingIHCode(info, Kit.ToInt_yyyyMMdd(day)); var repoOption = Platforms.container.Resolve <OptionMinuteRepository>(); var optionToday = repoOption.fetchFromLocalCsvOrWindAndSave(info.optionCode, day); data.Add(info.optionCode, optionToday.Cast <KLine>().ToList()); } int index = 0; //交易开关设置,控制day级的交易开关 bool tradingOn = true; //总交易开关 bool openingOn = true; //开仓开关 bool closingOn = true; //平仓开关 //是否为交割日 bool isExpiredDay = day.Equals(endDate[0]); //是否为回测最后一天 bool isLastDayOfBackTesting = day.Equals(endDate); //回测循环 -- By Minute //不允许在同一根1minBar上开平仓 while (index < 240) { int nextIndex = index + 1; DateTime now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(day), index); Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); double etfPrice = ETFtoday[index].close; //按strike price与etf价格的接近程度排序 List <double> strikeTodayArr = OptionUtilities.getStrikeListByAscending(list).OrderBy(x => Math.Abs(x - etfPrice)).ToList(); try { /* * if (index != 225) * { * index = nextIndex; * continue; * } */ //持仓查询,先平后开 //若当前有持仓 且 允许平仓 //是否是空仓,若position中所有品种volum都为0,则说明是空仓 bool isEmptyPosition = positions.Count != 0 ? positions[positions.Keys.Last()].Values.Sum(x => Math.Abs(x.volume)) == 0 : true; if ((positions.Count != 0 && !isEmptyPosition) && tradingOn) { //平仓条件 //(1)若当天为交割日或回测结束日,平仓,且关闭开仓开关,次日才能开仓; //(2)若closingOn为false,平仓; //(3)检查持仓期权是否为平价期权,若否,清掉当前头寸并建立新的持仓; //-------------------------------------------------------------------- //(1)若当天为交割日或回测结束日,平仓,且关闭开仓开关,次日才能开仓; //(2)若closingOn为false,平仓; //取出当前持仓期权的strike double strikePriceOfPositions = optionInfoList[optionInfoList.FindIndex(a => a.optionCode == positions[positions.Keys.Last()].Values.First().code)].strike; bool isParPriceOption = strikePriceOfPositions == strikeTodayArr[0]; //-------------------------------------------------------------------- if (!isEmptyPosition && (isExpiredDay || isLastDayOfBackTesting || closingOn == false)) { //全部平仓 DateTime next = MinuteCloseAllPositonsWithSlip.closeAllPositions(data, ref positions, ref myAccount, now: now, slipPoint: slipPoint); //当天不可再开仓 openingOn = false; } //(3)检查持仓期权是否为平价期权,若否,清掉当前头寸并建立新的持仓; else if (!isEmptyPosition && !isParPriceOption) { //全部平仓 DateTime next = MinuteCloseAllPositonsWithSlip.closeAllPositions(data, ref positions, ref myAccount, now: now, slipPoint: slipPoint); //当天不可再开仓 openingOn = false; } } //若当前无持仓 且 允许开仓 //若当前为交割日,则不开仓 if (isExpiredDay == true) { openingOn = false; } else if ((positions.Count == 0 || isEmptyPosition) && openingOn && tradingOn) { //标的池构建 //选择目标期权品种放入标的池: //四个头寸(1)short当月平价认购(2)short当月平价认沽(3)long下月平价认购(4)long下月平价认沽 OptionInfo callCandidateFront = OptionUtilities.getSpecifiedOption(list, endDate[0], "认购", strikeTodayArr[0])[0]; OptionInfo putCandidateFront = OptionUtilities.getSpecifiedOption(list, endDate[0], "认沽", strikeTodayArr[0])[0]; OptionInfo callCandidateNext = OptionUtilities.getSpecifiedOption(list, endDate[1], "认购", strikeTodayArr[0])[0]; OptionInfo putCandidateNext = OptionUtilities.getSpecifiedOption(list, endDate[1], "认沽", strikeTodayArr[0])[0]; //检查四个标的strike是否相同,若相同则开仓,若不相同是,说明下月平价期权尚未挂出,则continue bool isSameStrike = callCandidateFront.strike == callCandidateFront.strike; //生成开仓信号 if (isSameStrike) { //查询可用资金 double nowFreeCash = myAccount.freeCash; //计算每个头寸的建仓量,原则:尽量使各头寸等金额 double openVolumeOfCallFront = Math.Floor(nowFreeCash / 4 / data[callCandidateFront.optionCode][index].close / optionContractTimes) * optionContractTimes; double openVolumeOfPutFront = Math.Floor(nowFreeCash / 4 / data[putCandidateFront.optionCode][index].close / optionContractTimes) * optionContractTimes; double openVolumeOfCallNext = Math.Floor(nowFreeCash / 4 / data[callCandidateNext.optionCode][index].close / optionContractTimes) * optionContractTimes; double openVolumeOfPutNext = Math.Floor(nowFreeCash / 4 / data[putCandidateNext.optionCode][index].close / optionContractTimes) * optionContractTimes; MinuteSignal callFront = new MinuteSignal() { code = callCandidateFront.optionCode, volume = -openVolumeOfCallFront, time = now, tradingVarieties = "option", price = data[callCandidateFront.optionCode][index].close, minuteIndex = index }; MinuteSignal putFront = new MinuteSignal() { code = putCandidateFront.optionCode, volume = -openVolumeOfPutFront, time = now, tradingVarieties = "option", price = data[putCandidateFront.optionCode][index].close, minuteIndex = index }; MinuteSignal callNext = new MinuteSignal() { code = callCandidateNext.optionCode, volume = openVolumeOfCallNext, time = now, tradingVarieties = "option", price = data[callCandidateNext.optionCode][index].close, minuteIndex = index }; MinuteSignal putNext = new MinuteSignal() { code = putCandidateNext.optionCode, volume = openVolumeOfPutNext, time = now, tradingVarieties = "option", price = data[putCandidateNext.optionCode][index].close, minuteIndex = index }; signal.Add(callFront.code, callFront); signal.Add(putFront.code, putFront); signal.Add(callNext.code, callNext); signal.Add(putNext.code, putNext); DateTime next = MinuteTransactionWithSlip.computeMinuteOpenPositions(signal, data, ref positions, ref myAccount, slipPoint: slipPoint, now: now); nextIndex = Math.Max(nextIndex, TimeListUtility.MinuteToIndex(next)); } } //账户信息更新 AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, now, data); } catch (Exception) { throw; } 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); } //遍历输出到console foreach (var account in accountHistory) { Console.WriteLine("time:{0},netWorth:{1,8:F3}\n", account.time, account.totalAssets / initialCapital); } //将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(); }
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 }); } } }
/// <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.initialAssets = initialCapital; myAccount.freeCash = myAccount.totalAssets; //将账户当前时间定为下一天,因为交易总是在下一天开始 //int nextDay = tradeDays.FindIndex(date => date == startDate) + 1; myAccount.time = startDate; //记录历史账户信息 List <BasicAccount> accountHistory = new List <BasicAccount>(); ///数据准备 //交易日信息 List <DateTime> tradeDays = DateUtils.GetTradeDays(startDate, endDate); //分钟数据准备,取全回测期的数据存放于data Dictionary <string, List <KLine> > data = new Dictionary <string, List <KLine> >(); foreach (var tempDay in tradeDays) { var stockData = Platforms.container.Resolve <StockMinuteRepository>().fetchFromLocalCsvOrWindAndSave(stockCode, tempDay); if (!data.ContainsKey(stockCode)) { data.Add(stockCode, stockData.Cast <KLine>().ToList()); } else { data[stockCode].AddRange(stockData.Cast <KLine>().ToList()); } } //交易开关设置,控制day级的交易开关,tradingOn还没有使用 bool tradingOn = true; //总交易开关 bool openingOn = true; //开仓开关 bool closingOn = false; //平仓开关 //定义交易信号数组 Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); ///回测循环 //回测循环--By Day foreach (var day in tradeDays) { //取出当天的数据,列表类型,包含high,low,amt等数据 //取出当天的数据 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)); } //是否为回测最后一天 bool isLastDayOfBackTesting = day.Equals(endDate); //先测试15K数据,直接将策略写在程序中,将来可以尝试分离过程,将部分策略以函数或者类的形式写在外面 //现将1分钟数据调整为15分钟数据,15K数据占用16个数组空间 List <KLine> data15K = Get15KData(dataToday[stockCode]); //交易信号判断,用信号来判断开仓还是平仓,但是在交易单元,还要靠平均价和当前价,进行止损平仓 List <int> tradeSignal = ClimbMountain(data15K); //回测循环 -- By Minute //不允许在同一根1minBar上开平仓 int index = 0; while (index < 16) { int nextIndex = index + 1; if (nextIndex == 16) { break; } DateTime now = data15K[index].time; DateTime next = data15K[nextIndex].time; if ((tradeSignal[index] == 0) && openingOn) { //设置signal信号,设置时间等参数 MinuteSignal openSignal = new MinuteSignal() { code = stockCode, //开仓只开90%,下一阶段可以分批加仓,全部减仓 volume = myAccount.freeCash / data15K[nextIndex].open * 0.9, time = data15K[nextIndex].time, tradingVarieties = "stock", price = data15K[nextIndex].open, minuteIndex = nextIndex }; openingOn = false; closingOn = true; volumeNow = myAccount.freeCash / data15K[nextIndex].open * 0.9; signal.Add(stockCode, openSignal); //开仓下单 MinuteTransactionWithSlip.computeMinuteOpenPositions(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: data15K[nextIndex].time, capitalVerification: false); signal.Clear(); } else if (((tradeSignal[index] == 1) || (positions[now][stockCode].ShortPosition.averagePrice < data15K[index].close)) && (volumeNow > 0) && closingOn) { //设置signal信号,设置时间等参数 MinuteSignal closeSignal = new MinuteSignal() { code = stockCode, volume = 0 - volumeNow, time = data15K[nextIndex].time, tradingVarieties = "stock", price = data15K[nextIndex].open, minuteIndex = 0 }; openingOn = true; closingOn = false; volumeNow = 0; signal.Add(stockCode, closeSignal); //平仓下单 MinuteTransactionWithSlip.computeMinuteClosePositions(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: data15K[nextIndex].time); signal.Clear(); } 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); //账户信息更新 AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, myAccount.time, dataToday); } //策略绩效统计及输出 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: "EMA7_EMA50", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录持仓变化 //var positionStatus = OptionRecordUtil.Transfer(positions); //RecordUtil.recordToCsv(positionStatus, GetType().FullName, "positions", parameters: "EMA7_EMA50", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录统计指标 var performanceList = new List <PerformanceStatisics>(); performanceList.Add(myStgStats); RecordUtil.recordToCsv(performanceList, GetType().FullName, "performance", parameters: "EMA7_EMA50", 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(); //初始化净值曲线类 PLChart plc = new PLChart(line, datestr); Application.Run(plc); plc.SaveZed("D:\\BTP\\Result\\BackTestingPlatform.Strategies.Stock.StockSample.MABreak\\aa.png"); }
public int compute() { //如果MA周期不对,直接返回 if (MA1 < 0 || MA2 < 0) { log.Info("MA周期出错,MA1:{0}, MA2:{1}", MA1, MA2); return(-1); } log.Info("开始回测(回测期{0}到{1})", Kit.ToInt_yyyyMMdd(startDate), Kit.ToInt_yyyyMMdd(endDate)); //将来可以把这些初始化操作从程序中分离,写在外面 //交易日信息 List <DateTime> tradeDays = DateUtils.GetTradeDays(startDate, endDate); ///账户初始化 //初始化positions SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions = new SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> >(); //初始化Account信息 BasicAccount myAccount = new BasicAccount(); myAccount.totalAssets = initialCapital; myAccount.initialAssets = initialCapital; myAccount.freeCash = myAccount.totalAssets; //将账户当前时间定为下一天,因为交易总是在下一天开始 //int nextDay = tradeDays.FindIndex(date => date == startDate) + 1; myAccount.time = startDate; //记录历史账户信息 List <BasicAccount> accountHistory = new List <BasicAccount>(); //benchmark数据记录 List <double> benchmark = new List <double>(); ///数据准备 //日线数据准备,取全回测期的数据存放于data List <StockDaily> stockData = new List <StockDaily>(); stockData = Platforms.container.Resolve <StockDailyRepository>().fetchFromLocalCsvOrWindAndSave(stockCode, startDate, endDate); //建立close price数组,从stockData里面取出close price int stockData_length = stockData.Count; double[] closePrice = new double[stockData_length]; for (int count = 0; count < stockData_length; ++count) { closePrice[count] = stockData[count].close; } //取两个MA的数组 double[] MA1_array = MA.compute(closePrice, MA1); double[] MA2_array = MA.compute(closePrice, MA2); //****数据准备完毕回测开始****** log.Info("数据准备完毕回测开始"); //交易开关设置,控制day级的交易开关,开始时只能开仓,不能平仓 bool tradingOn = true; //总交易开关 bool openingOn = true; //开仓开关 bool closingOn = false; //平仓开关 //定义交易信号数组 Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); //获得交易信号 for (int count = MA1; count < stockData_length; ++count) { //获取当前时间,供回测信号使用 DateTime timeNow = stockData[count].time; //找出下一个交易日 int nextTradeDay = tradeDays.FindIndex(date => date == timeNow) + 1; if (nextTradeDay == stockData_length) { break; } //分钟数据准备,做交易执行使用 var minuteData = Platforms.container.Resolve <StockMinuteRepository>().fetchFromLocalCsvOrWindAndSave(stockCode, tradeDays[nextTradeDay]); Dictionary <string, List <KLine> > dataToday = new Dictionary <string, List <KLine> >(); dataToday.Add(stockCode, minuteData.Cast <KLine>().ToList()); //上穿买入信号 if ((MA1_array[count] > MA2_array[count]) && openingOn && tradingOn) { //设置signal信号,设置时间等参数 MinuteSignal openSignal = new MinuteSignal() { code = stockCode, volume = myAccount.freeCash / MA1_array[count] * 0.9, time = minuteData[0].time, tradingVarieties = "stock", price = MA1_array[count], minuteIndex = 0 }; openingOn = false; closingOn = true; volumeNow = myAccount.freeCash / MA1_array[count] * 0.9; signal.Add(stockCode, openSignal); //开仓下单 MinuteTransactionWithSlip.computeMinuteOpenPositions(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: minuteData[0].time, capitalVerification: false); signal.Clear(); } ////下穿卖出信号,当存量volumeNow大于0时做卖出操作 if ((MA1_array[count] < MA2_array[count]) && closingOn && tradingOn && (volumeNow > 0)) { //设置signal信号,设置时间等参数 MinuteSignal closeSignal = new MinuteSignal() { code = stockCode, volume = 0 - volumeNow, time = minuteData[0].time, tradingVarieties = "stock", price = MA1_array[count], minuteIndex = 0 }; openingOn = true; closingOn = false; volumeNow = 0; signal.Add(stockCode, closeSignal); //平仓下单 MinuteTransactionWithSlip.computeMinuteClosePositions(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: minuteData[0].time); signal.Clear(); } //将交易记录记录到历史 BasicAccount tempAccount = new BasicAccount(); tempAccount.time = myAccount.time; tempAccount.freeCash = myAccount.freeCash; tempAccount.margin = myAccount.margin; tempAccount.positionValue = myAccount.positionValue; tempAccount.totalAssets = myAccount.totalAssets; tempAccount.initialAssets = myAccount.initialAssets; accountHistory.Add(tempAccount); //账户信息更新 AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, minuteData[0].time, dataToday); } //策略绩效统计及输出 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: "EMA7_EMA50", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录持仓变化 //var positionStatus = OptionRecordUtil.Transfer(positions); //RecordUtil.recordToCsv(positionStatus, GetType().FullName, "positions", parameters: "EMA7_EMA50", performance: myStgStats.anualSharpe.ToString("N").Replace(".", "_")); //记录统计指标 var performanceList = new List <PerformanceStatisics>(); performanceList.Add(myStgStats); RecordUtil.recordToCsv(performanceList, GetType().FullName, "performance", parameters: "EMA7_EMA50", 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(); //初始化净值曲线类 PLChart plc = new PLChart(line, datestr); Application.Run(plc); plc.SaveZed("D:\\BTP\\Result\\BackTestingPlatform.Strategies.Stock.StockSample.MABreak\\aa.png"); return(0); }
/// <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; //记录历史账户信息 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", 3); //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); //计算需要指标 //(1)回看长度内的高低极值点(值) //(2)各级别高低拐点的位置(值) List <double> upReversionPoint = new List <double>(); List <double> downReversionPoint = new List <double>(); upReversionPoint = ComputeReversionPoint.findUpReversionPoint(data[targetVariety], NDays, lengthOfBackLooking); downReversionPoint = ComputeReversionPoint.findDownReversionPoint(data[targetVariety], NDays, lengthOfBackLooking); int indexOfNow = -1;//记录整个data的索引 ///回测循环 //回测循环--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++; DateTime now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(day), index); Dictionary <string, MinuteSignal> signal = new Dictionary <string, MinuteSignal>(); DateTime next = new DateTime(); //int indexOfNow = data[targetVariety].FindIndex(s => s.time == now); myAccount.time = now; double nowClose = dataToday[targetVariety][index].close; double nowUpReversionPoint = upReversionPoint[indexOfNow]; double nowDownReversionPoint = downReversionPoint[indexOfNow]; //实际操作从第一个回望期后开始 if (indexOfNow < lengthOfBackLooking - 1) { 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(); }
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; StraddlePair holdingStatus = new StraddlePair(); //统计历史波动率分位数,从回测期开始前一天,统计到最后一天 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.callCodeFront) { holdingStatus.strike = item.strike; holdingStatus.callPositionFront *= item.contractMultiplier / standardContractMultiplier; holdingStatus.putPositionFront *= item.contractMultiplier / standardContractMultiplier; holdingStatus.callPositionNext *= item.contractMultiplier / standardContractMultiplier; holdingStatus.putPositionNext *= 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; //获取当日期限结构,选取当月合约,若当日合约到日期小于等于3天,直接开仓下月合约 List <double> dateStructure = OptionUtilities.getDurationStructure(optionInfoList, today); double durationFront = dateStructure[0] <= 3 ? dateStructure[1] : dateStructure[0]; double durationNext = dateStructure[0] <= 3 ? dateStructure[2] : dateStructure[1]; StraddlePairCode myPair = getStraddlePairCode(optionInfoList, durationFront, durationNext, etfPrice, today); var callATM = OptionUtilities.getOptionByCode(optionInfoList, myPair.callCodeFront); var callPrice = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(callATM.optionCode, today); var putATM = OptionUtilities.getOptionByCode(optionInfoList, myPair.putCodeFront); var putPrice = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(putATM.optionCode, today); var callATMNext = OptionUtilities.getOptionByCode(optionInfoList, myPair.callCodeNext); var callPriceNext = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(callATMNext.optionCode, today); var putATMNext = OptionUtilities.getOptionByCode(optionInfoList, myPair.putCodeNext); var putPriceNext = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(putATMNext.optionCode, today); //整合当日分钟线数据 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()); dataToday.Add(callATMNext.optionCode, callPriceNext.Cast <KLine>().ToList()); dataToday.Add(putATMNext.optionCode, putPriceNext.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.callPositionFront != 0) { if (dataToday.ContainsKey(holdingStatus.callCodeFront) == false) { var callLastDayFront = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.callCodeFront, today); dataToday.Add(holdingStatus.callCodeFront, callLastDayFront.Cast <KLine>().ToList()); } if (dataToday.ContainsKey(holdingStatus.putCodeFront) == false) { var putLastDayFront = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.putCodeFront, today); dataToday.Add(holdingStatus.putCodeFront, putLastDayFront.Cast <KLine>().ToList()); } if (dataToday.ContainsKey(holdingStatus.callCodeNext) == false) { var callLastDayNext = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.callCodeNext, today); dataToday.Add(holdingStatus.callCodeNext, callLastDayNext.Cast <KLine>().ToList()); } if (dataToday.ContainsKey(holdingStatus.putCodeNext) == false) { var putLastDayNext = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.putCodeNext, today); dataToday.Add(holdingStatus.putCodeNext, putLastDayNext.Cast <KLine>().ToList()); } if (holdingStatus.callPositionFront * orignalSignal < 0) //仓位和信号相反,强制平仓 { Console.WriteLine("平仓!"); MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, openIndex, slipPoint); holdingStatus = new StraddlePair(); } if (DateUtils.GetSpanOfTradeDays(today, holdingStatus.endDate) <= 3) //有仓位无信号,判断是否移仓 { Console.WriteLine("平仓!"); MinuteCloseAllWithBar.CloseAllPosition(dataToday, ref positions, ref myAccount, now, openIndex, slipPoint); holdingStatus = new StraddlePair(); } } //指定开仓时间为开盘第10分钟。错开开平仓的时间。 openIndex = 10; now = TimeListUtility.IndexToMinuteDateTime(Kit.ToInt_yyyyMMdd(today), openIndex); if (holdingStatus.callPositionFront == 0 && orignalSignal != 0) //无仓位有信号,开仓 { if (orignalSignal == 1) //做多跨式期权 { MinuteSignal openSignalCallFront = new MinuteSignal() { code = callATM.optionCode, volume = optionVolume, time = now, tradingVarieties = "option", price = callPrice[openIndex].open, minuteIndex = openIndex }; MinuteSignal openSignalPutFront = new MinuteSignal() { code = putATM.optionCode, volume = optionVolume, time = now, tradingVarieties = "option", price = putPrice[openIndex].open, minuteIndex = openIndex }; MinuteSignal openSignalCallNext = new MinuteSignal() { code = callATMNext.optionCode, volume = -optionVolume, time = now, tradingVarieties = "option", price = callPriceNext[openIndex].open, minuteIndex = openIndex }; MinuteSignal openSignalPutNext = new MinuteSignal() { code = putATMNext.optionCode, volume = -optionVolume, time = now, tradingVarieties = "option", price = putPriceNext[openIndex].open, minuteIndex = openIndex }; Console.WriteLine("做多跨式期权!"); signal.Add(callATM.optionCode, openSignalCallFront); signal.Add(putATM.optionCode, openSignalPutFront); signal.Add(callATMNext.optionCode, openSignalCallNext); signal.Add(putATMNext.optionCode, openSignalPutNext); //变更持仓状态 holdingStatus = new StraddlePair { callCodeFront = callATM.optionCode, putCodeFront = putATM.optionCode, callCodeNext = callATMNext.optionCode, putCodeNext = putATMNext.optionCode, callPositionFront = optionVolume, putPositionFront = optionVolume, callPositionNext = -optionVolume, putPositionNext = -optionVolume, etfPrice_open = etfData[openIndex].open, straddlePairPrice_open = callPrice[openIndex].open + putPrice[openIndex].open - callPriceNext[openIndex].open - putPriceNext[openIndex].open, straddleOpenDate = today, endDate = callATM.endDate, strike = callATM.strike, endDateNext = callATMNext.endDate }; } 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 }; MinuteSignal openSignalCallNext = new MinuteSignal() { code = callATMNext.optionCode, volume = optionVolume, time = now, tradingVarieties = "option", price = callPriceNext[openIndex].open, minuteIndex = openIndex }; MinuteSignal openSignalPutNext = new MinuteSignal() { code = putATMNext.optionCode, volume = optionVolume, time = now, tradingVarieties = "option", price = putPriceNext[openIndex].open, minuteIndex = openIndex }; Console.WriteLine("做空跨式期权!"); signal.Add(callATM.optionCode, openSignalCall); signal.Add(putATM.optionCode, openSignalPut); signal.Add(callATMNext.optionCode, openSignalCallNext); signal.Add(putATMNext.optionCode, openSignalPutNext); //变更持仓状态 holdingStatus = new StraddlePair { callCodeFront = callATM.optionCode, putCodeFront = putATM.optionCode, callCodeNext = callATMNext.optionCode, putCodeNext = putATMNext.optionCode, callPositionFront = -optionVolume, putPositionFront = -optionVolume, callPositionNext = optionVolume, putPositionNext = optionVolume, etfPrice_open = etfData[openIndex].open, straddlePairPrice_open = -callPrice[openIndex].open - putPrice[openIndex].open + callPriceNext[openIndex].open + putPriceNext[openIndex].open, straddleOpenDate = today, endDate = callATM.endDate, strike = callATM.strike, endDateNext = callATMNext.endDate }; } 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.callPositionFront != 0) { if (dataToday.ContainsKey(holdingStatus.callCodeFront) == false) { var callLastDayFront = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.callCodeFront, today); dataToday.Add(holdingStatus.callCodeFront, callLastDayFront.Cast <KLine>().ToList()); } if (dataToday.ContainsKey(holdingStatus.putCodeFront) == false) { var putLastDayFront = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.putCodeFront, today); dataToday.Add(holdingStatus.putCodeFront, putLastDayFront.Cast <KLine>().ToList()); } if (dataToday.ContainsKey(holdingStatus.callCodeNext) == false) { var callLastDayNext = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.callCodeNext, today); dataToday.Add(holdingStatus.callCodeNext, callLastDayNext.Cast <KLine>().ToList()); } if (dataToday.ContainsKey(holdingStatus.putCodeNext) == false) { var putLastDayNext = Platforms.container.Resolve <OptionMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.putCodeNext, today); dataToday.Add(holdingStatus.putCodeNext, putLastDayNext.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); //holdingStatus.etfPosition += etfChangeVolume; // AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, thisTime, dataToday); } //更新当日属性信息 AccountUpdatingWithMinuteBar.computeAccount(ref myAccount, positions, thisTime, data: dataToday, nowIndex: thisIndex); //记录历史仓位信息 accountHistory.Add(new BasicAccount(myAccount.time, myAccount.totalAssets, myAccount.freeCash, myAccount.positionValue, myAccount.margin, myAccount.initialAssets)); //在控制台上数据每日持仓信息 if (holdingStatus.callPositionFront != 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.callPositionFront, dataToday[holdingStatus.callCodeFront][thisIndex].close, dataToday[holdingStatus.putCodeFront][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)); }