/// <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() { 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(); 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, 252); //统计隐含波动率 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 fractile70Yesterday = fractile[i - 1][7]; //昨日历史波动率70分位数 double fractile30Yesterday = fractile[i - 1][3]; //昨日历史波动率30分位数 double volYesterday = etfVol[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]< fractile30Yesterday) //{ // //买入跨式期权 // orignalSignal = 1; //} //else if (optionVol[i-1]-volYesterday>epsilon[i-1]) //{ // //卖出跨式期权 // orignalSignal = -1; //} orignalSignal = 0; if (volYesterday >= fractile70Yesterday) { //卖出跨式期权 orignalSignal = -1; } else if (optionVol[i - 1] < fractile30Yesterday) { //买入跨式期权 orignalSignal = 1; } else if (optionVol[i - 1] - volYesterday > 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); //开仓进行对冲 string IHCode = OptionUtilities.getCorrespondingIHCode(OptionUtilities.getSpecifiedOption(optionInfoList, holdingStatus.endDate, "认购", holdingStatus.strike)[0], Kit.ToInt_yyyyMMdd(today)); var IHToday = Platforms.container.Resolve <FuturesMinuteRepository>().fetchFromLocalCsvOrWindAndSave(IHCode, today); if (dataToday.ContainsKey(IHCode) == false) { dataToday.Add(IHCode, IHToday.Cast <KLine>().ToList()); } double cashDelta = computeOptionCashDelta(positions.Last().Value, holdingStatus, today, dataToday, openIndex); //double increment = Math.Round(-cashDelta / 1000) - holdingStatus.IHPosition; //if (Math.Abs(increment) >= 0) //{ // MinuteSignal IHSignal = new MinuteSignal() { code = IHCode, volume = increment, time = now, tradingVarieties = "futures", price = IHToday[openIndex].close, minuteIndex = openIndex }; // signal = new Dictionary<string, MinuteSignal>(); // signal.Add(IHCode, IHSignal); // holdingStatus.IHCode = IHCode; // holdingStatus.IHPosition += increment; // 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()); } } if (holdingStatus.callPosition != 0) { //获取对应的IH的数据 string IHCode = OptionUtilities.getCorrespondingIHCode(OptionUtilities.getSpecifiedOption(optionInfoList, holdingStatus.endDate, "认购", holdingStatus.strike)[0], Kit.ToInt_yyyyMMdd(today)); var IHToday = Platforms.container.Resolve <FuturesMinuteRepository>().fetchFromLocalCsvOrWindAndSave(IHCode, today); if (dataToday.ContainsKey(IHCode) == false) { dataToday.Add(IHCode, IHToday.Cast <KLine>().ToList()); } if (IHCode != holdingStatus.IHCode && holdingStatus.IHPosition != 0) //期权对应的期货合约发生变化,那么进行移仓操作 { var IHLast = Platforms.container.Resolve <FuturesMinuteRepository>().fetchFromLocalCsvOrWindAndSave(holdingStatus.IHCode, today); if (dataToday.ContainsKey(holdingStatus.IHCode) == false) { dataToday.Add(holdingStatus.IHCode, IHLast.Cast <KLine>().ToList()); } MinuteSignal IHCloseSignal = new MinuteSignal() { code = holdingStatus.IHCode, volume = -holdingStatus.IHPosition, time = thisTime, tradingVarieties = "futures", price = IHLast[thisIndex].close, minuteIndex = thisIndex }; signal = new Dictionary <string, MinuteSignal>(); signal.Add(holdingStatus.IHCode, IHCloseSignal); holdingStatus.IHCode = ""; holdingStatus.IHPosition = 0; MinuteTransactionWithSlip.computeMinuteOpenPositions(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: thisTime, capitalVerification: false); AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, thisTime, dataToday); } //计算期权持仓的delta值 double cashDelta = computeOptionCashDelta(positions.Last().Value, holdingStatus, today, dataToday, thisIndex); //收盘前对冲 if (cashDelta != 0) { Console.WriteLine("time; {0}, delta: {1}", today, cashDelta); //delta = 0; } //double increment = Math.Round(-cashDelta / 1000) - holdingStatus.IHPosition; //if (Math.Abs(increment)>0) //{ // MinuteSignal IHSignal = new MinuteSignal() { code = IHCode, volume = increment, time = thisTime, tradingVarieties = "futures", price = IHToday[thisIndex].close, minuteIndex = thisIndex }; // signal = new Dictionary<string, MinuteSignal>(); // signal.Add(IHCode, IHSignal); // holdingStatus.IHCode = IHCode; // holdingStatus.IHPosition += increment; // MinuteTransactionWithSlip.computeMinuteOpenPositions(signal, dataToday, ref positions, ref myAccount, slipPoint: slipPoint, now: thisTime, capitalVerification: false); //} } //更新当日属性信息 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)); }