/// <summary> /// 读取文件 /// </summary> /// <param name="filename"></param> /// <returns></returns> public static TradeRecords Load(String filename) { String[] lines = System.IO.File.ReadAllLines(filename); if (lines == null || lines.Length <= 0) { return(new TradeRecords()); } TradeRecords records = new TradeRecords(); foreach (String line in lines) { TradeBout bout = TradeBout.Parse(line); if (bout != null) { records.bouts.Add(bout); } } return(records); }
/// <summary> /// 判断指定的回合是否要在d这天卖出 /// </summary> /// <param name="code"></param> /// <param name="bout"></param> /// <param name="d"></param> /// <param name="strategyParam"></param> /// <param name="backtestParam"></param> /// <returns>是否进入择机卖出</returns> public abstract bool DoSell(String code, TradeBout bout, DateTime d, Properties strategyParam, BacktestParameter backtestParam, out String reason);
/// <summary> /// 执行卖出操作 /// </summary> /// <param name="tradeRecord"></param> /// <param name="strategyParam"></param> /// <param name="backtestParam"></param> public virtual void Execute(TradeRecords tradeRecord, Properties strategyParam, BacktestParameter backtestParam) { #region 1初始化行情库 if (tradeRecord == null) { return; } IndicatorRepository repository = (IndicatorRepository)backtestParam.Get <Object>("repository"); if (repository == null) { return; } #endregion #region 2 取得策略参数 int p_maxbuynum = strategyParam.Get <int>("maxbuynum", 0); double p_maxprofilt = strategyParam.Get <double>("maxprofilt"); int p_maxholddays = strategyParam.Get <int>("maxholddays"); double p_stoploss = strategyParam.Get <double>("stoploss"); int p_choosedays = strategyParam.Get <int>("choosedays"); double p_chooseprofilt = strategyParam.Get <double>("chooseprofilt"); double p_addholdprofilt = strategyParam.Get <double>("addholdprofilt"); double p_addholdamount = strategyParam.Get <double>("addholdamount"); #endregion String code = tradeRecord.Code; List <TradeBout> bouts = tradeRecord.Bouts; #region 3 遍历每一个买入回合 for (int i = 0; i < bouts.Count; i++) { #region 3.1 取得该回合的行情数据 TradeBout bout = bouts[i]; TimeSerialsDataSet ds = repository[bout.Code]; if (ds == null) { continue; } if (bout.Completed) { continue; //跳过已完成的 } KLine kline = ds.DayKLine; if (kline == null) { continue; } bool chooseToSell = false;//择机卖出状态,是指持仓价值较低 int bIndex = kline.IndexOf(bout.BuyInfo.TradeDate); if (bIndex < 0) { continue; } KLineItem klineItemDay = kline[bIndex]; DateTime d = klineItemDay.Date; #endregion #region 3.2 如果超过了最大持仓数限制,该回合跳过 if (p_maxbuynum > 0) { //计算当前回合的买入日期这天有多少持仓 int count = 0; bouts.ForEach(x => { if (x.Completed && bout.BuyInfo.TradeDate.Date >= x.BuyInfo.TradeDate.Date && bout.BuyInfo.TradeDate.Date < x.SellInfo.TradeDate.Date) { count++; } }); if (count > p_maxbuynum) { continue; } } #endregion #region 3.3 寻找卖点 String reason = ""; for (int index = bIndex + 1; index < kline.Count; index++) { klineItemDay = kline[index]; d = klineItemDay.Date; #region A 计算以当日最高价和收盘价卖出的盈利 double diff = klineItemDay.HIGH - bout.BuyInfo.TradePrice; double percentHigh = diff / bout.BuyInfo.TradePrice; diff = klineItemDay.CLOSE - bout.BuyInfo.TradePrice; double percentClose = diff / bout.BuyInfo.TradePrice; #endregion #region B 盈利超过预定 if (p_maxprofilt > 0 && percentHigh >= p_maxprofilt) //盈利超过预定 { double price = bout.BuyInfo.TradePrice * (1 + p_maxprofilt); int amount = bout.BuyInfo.Amount; bout.RecordTrade(2, klineItemDay.Date, TradeDirection.Sell, price, amount, backtestParam.Volumecommission, backtestParam.Stampduty, "盈利>=" + p_maxprofilt.ToString("F2")); break; } #endregion #region C 择机卖出状态 if (chooseToSell) { for (int t = 0; t < p_choosedays; t++) { index += t; if (index >= kline.Count) { break; } double percent = (kline[index].HIGH - bout.BuyInfo.TradePrice) / bout.BuyInfo.TradePrice; if (percent >= p_chooseprofilt) { double price = bout.BuyInfo.TradePrice * (1 + p_chooseprofilt); int amount = bout.BuyInfo.Amount; bout.RecordTrade(2, kline[index].Date, TradeDirection.Sell, price, amount, backtestParam.Volumecommission, backtestParam.Stampduty, (reason == "" ? "" : reason + ",并") + "在第" + (t + 1).ToString() + "天择机卖出"); break; } } if (!bout.Completed) { if (index >= kline.Count) { return; } double price = kline[index].CLOSE; int amount = bout.BuyInfo.Amount; bout.RecordTrade(2, kline[index].Date, TradeDirection.Sell, price, amount, backtestParam.Volumecommission, backtestParam.Stampduty, (reason == "" ? "" : reason + ",并") + "择机强制卖出"); } break; } #endregion #region D 持仓超过n天进入到择机卖出状态 if (p_maxholddays != 0 && CalendarUtils.WorkDayCount(bout.BuyInfo.TradeDate, klineItemDay.Date) >= p_maxholddays) { reason = "持仓超过" + p_maxholddays.ToString() + "天"; chooseToSell = true; continue; } #endregion #region E 达到止损线,进入到择机卖出状态 double loss = (klineItemDay.LOW - bout.BuyInfo.TradePrice) / bout.BuyInfo.TradePrice; if (p_stoploss > 0 && loss < 0 && loss < -1 * p_stoploss) { reason = "达到止损" + p_stoploss.ToString("F2"); bout.RecordTrade(2, d, TradeDirection.Sell, bout.BuyInfo.TradePrice * (1 - p_stoploss), bout.BuyInfo.Amount, backtestParam.Volumecommission, backtestParam.Stampduty, reason); break; } #endregion #region F调用子类的算法来寻找卖点 if (!bout.Completed) { chooseToSell = DoSell(code, bout, d, strategyParam, backtestParam, out reason); } if (bout.Completed) { break; } #endregion #region 判断是否加仓或者减仓 if (p_addholdprofilt > 0 && p_addholdamount > 0) { if (percentClose > p_addholdprofilt) { int addamount = (int)(bout.BuyInfo.Amount * p_addholdamount); TradeInfo tradeInfo = new TradeInfo(); tradeInfo.Code = bout.Code; tradeInfo.Amount = addamount; tradeInfo.Direction = TradeDirection.Buy; tradeInfo.Reason = "加仓"; tradeInfo.Fee = backtestParam.Volumecommission; tradeInfo.Stamps = backtestParam.Stampduty; tradeInfo.TradeDate = klineItemDay.Date; tradeInfo.TradePrice = klineItemDay.CLOSE; bout.BuyInfo.TradePrice = (bout.BuyInfo.Amount * bout.BuyInfo.TradePrice + addamount * klineItemDay.CLOSE) / (bout.BuyInfo.Amount + addamount); bout.BuyInfo.Amount += addamount; } } #endregion } #endregion //寻找卖点结束 } #endregion //遍历每一个买入回合结束 }
/// <summary> /// 执行回测 /// </summary> /// <param name="props"></param> /// <returns></returns> public virtual TotalStat doTestByDate(List <TradeRecords> tradeRecoreds) { List <TradeBout> bouts = new List <TradeBout>(); tradeRecoreds.ForEach(x => bouts.AddRange(x.Bouts)); double marketValueMin = backtestParam.Initfunds; //日最低市值 double marketValueMax = backtestParam.Initfunds; //日最高市值 double lastmarketValueMax = backtestParam.Initfunds; //上一个日最高市值 DateTime lastmarketValueMaxDate = backtestParam.BeginDate; double curFund = backtestParam.Initfunds; //当前资金 TotalStat stat = new TotalStat(); List <DateDetailRecord> records = new List <DateDetailRecord>(); //日详细记录 List <TradeBout> holdTrades = new List <TradeBout>(); //日持仓回合 List <int> holdDays = new List <int>(); //持仓日期 List <String> codes = new List <string>(); //交易的股票代码 List <int> buyCounts = new List <int>(); //每天买入的回合数 IndicatorRepository repository = (IndicatorRepository)backtestParam.Get <Object>("repository"); String reason = ""; //遍历每一天 for (DateTime d = backtestParam.BeginDate; d <= backtestParam.EndDate; d = d.AddDays(1)) { //跳过非工作日 //if (!CalendarUtils.IsWorkDay(d)) // continue; //生成空的当日记录 DateDetailRecord record = new DateDetailRecord(); record.date = d; //找到当日的买入回合、卖出回合 bouts.ForEach(y => { if (y.BuyInfo.TradeDate.Date == d.Date) { record.buyBouts.Add(y); } else if (y.SellInfo.TradeDate.Date == d.Date) { record.sellBouts.Add(y); } }); //当日没有发生买卖操作,也没有持仓,跳过 if (record.buyBouts.Count <= 0 && record.sellBouts.Count <= 0 && holdTrades.Count <= 0) { continue; } //将buyTrades按照优先规则排序,待实现 //计算当日买入的花销,如果超过了资金允许买入的量,则删除一部分 record.willBuyCount = record.buyBouts.Count; for (int i = 0; i < record.buyBouts.Count; i++) { if (record.buyBouts[i].BuyInfo.TradeCost > curFund)//资金不够 { bouts.Remove(record.buyBouts[i]); record.buyBouts.RemoveAt(i--); } else if (isForbidBuy(d, record.buyBouts[i].Code, out reason))//如果策略实现禁止买入 { bouts.Remove(record.buyBouts[i]); record.buyBouts.RemoveAt(i--); } else { curFund -= record.buyBouts[i].BuyInfo.TradeCost; //买入 holdTrades.Add(record.buyBouts[i]); //买入后变成持仓 } } record.buyCount = record.buyBouts.Count; if (stat.MaxTradeCountPerDay < record.buyBouts.Count) { stat.MaxTradeCountPerDay = record.buyBouts.Count; } buyCounts.Add(record.buyBouts.Count); //判断持仓中的股票是否被禁止持仓 for (int i = 0; i < holdTrades.Count; i++) { TradeBout info = holdTrades[i]; if (!isForbidHold(d, info.Code, out reason)) { continue; } info.SellInfo.Reason = reason + "(" + info.SellInfo.Reason + ")"; record.sellBouts.Add(info); holdTrades.RemoveAt(i--); } //卖出收入放回资金 for (int i = 0; i < record.sellBouts.Count; i++) { if (!codes.Contains(record.sellBouts[i].Code))//记录交易的股票 { codes.Add(record.sellBouts[i].Code); } stat.BoutNum += 1; //回合数加1 if (record.sellBouts[i].Win) //胜数加1 { stat.WinNum += 1; } holdDays.Add(record.sellBouts[i].PositionDays); //记录持仓日期 curFund += record.sellBouts[i].SellInfo.TradeCost; //回收资金 holdTrades.Remove(record.sellBouts[i]); //从持仓中拿掉 } record.SellCount = record.sellBouts.Count; //计算市值 record.holdCount = holdTrades.Count; if (holdTrades.Count <= 0)//如果没有持仓,市值就是资金量 { marketValueMax = marketValueMin = curFund; records.Add(record); } else//如果有持仓,则计算市值=资金量+持仓当日市值 { double min = 0, max = 0; foreach (TradeBout info in holdTrades) { TimeSerialsDataSet ds = repository[info.Code]; KLine kline = ds.DayKLine; KLineItem klineitem = kline.GetNearest(d, true, -1); if (klineitem == null)//有一个回合找不到当日K线数据,则当日市值不再计算 { min = max = 0; this.log.Warn("日期" + d.ToString("yyyyMMdd") + "中有回合缺少当日和历史K线:" + info.Code); break; } min += info.BuyInfo.Amount * klineitem.LOW; max += info.BuyInfo.Amount * klineitem.HIGH; record.holdBouts.Add(info.Code + "," + info.BuyInfo.Amount + "," + info.BuyInfo.TradePrice.ToString("F2") + "," + klineitem.CLOSE); } if (min != 0) { marketValueMin = curFund + min; } if (max != 0) { marketValueMax = curFund + max; } if (min != 0 && max != 0) { records.Add(record); } } //记录资金和市值数据 record.curFund = curFund; record.marketValueMin = marketValueMin; record.marketValueMax = marketValueMax; if (marketValueMin < backtestParam.Initfunds) { record.retracement = (backtestParam.Initfunds - marketValueMin) / backtestParam.Initfunds; } if (stat.MaxInitRetracementRate < record.retracement) { stat.MaxInitRetracementRate = record.retracement; stat.MaxInitRetracementDate = d; } if (marketValueMax > lastmarketValueMax) { lastmarketValueMax = marketValueMax; lastmarketValueMaxDate = d; record.retracement = 0; } else { record.retracement = (lastmarketValueMax - marketValueMax) / lastmarketValueMax; stat.MaxRetracementRate = record.retracement; stat.MaxRetracementDate = d; } } //清除没有卖出的回合 for (int i = 0; i < holdTrades.Count; i++) { curFund += holdTrades[i].BuyInfo.TradeCost; bouts.Remove(holdTrades[i]); } holdTrades.Clear(); marketValueMin = marketValueMax = curFund; if (records.Count > 0) { DateDetailRecord extends = new DateDetailRecord(); extends.date = records[records.Count - 1].date.AddDays(1); extends.curFund = curFund; extends.marketValueMax = marketValueMax; extends.marketValueMin = marketValueMin; records.Add(extends); } //结果统计 stat.Records = records; stat.AverageTradeCountPerDay = buyCounts.Count <= 0?0:buyCounts.Average(); stat.AvgHoldDays = holdDays.Count <= 0?0:(int)holdDays.Average(); stat.Count = codes.Count; stat.MaxHoldDays = holdDays.Count <= 0 ? 0 : holdDays.Max(); stat.TotalFund = curFund; stat.TotalProfilt = (curFund - backtestParam.Initfunds) / backtestParam.Initfunds; stat.WinRate = stat.WinNum * 1.0 / stat.BoutNum; return(stat); }