public static void computeAccount(ref BasicAccount myAccount, SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions, DateTime now, int nowIndex, Dictionary <string, List <KLine> > data) { myAccount.time = now; //若position为null,直接跳过 if (positions.Count == 0) { return; } //提取初始资产 double initialCapital = myAccount.initialAssets; Dictionary <string, PositionsWithDetail> nowPosition = new Dictionary <string, PositionsWithDetail>(); nowPosition = positions[positions.Keys.Last()]; //初始化保证金,可用现金 double totalMargin = 0; double totalCashFlow = 0; double totalPositionValue = 0; double totalAssets = 0; //当前时间对应data中timeList 的序号 int index = nowIndex; if (index < 0) { log.Warn("Signal时间出错,请查验"); return; } foreach (var item in nowPosition) { PositionsWithDetail position0 = item.Value; if (position0.volume != 0) { double price = data[position0.code][index].close; totalPositionValue += price * position0.volume; } if (position0.volume < 0) //计算保证金 { if (position0.tradingVarieties == "option") //按每分钟收盘价来近似期权的保证金 { totalMargin += (OptionMargin.ComputeMaintenanceMargin(data["510050.SH"][index].close, data[position0.code][index].close, optionInfoList[position0.code].strike, optionInfoList[position0.code].optionType, Math.Abs(position0.volume))); } else if (position0.tradingVarieties == "stock") //股票卖空按照一半保证金计算 { totalMargin += 0.5 * data[position0.code][index].close * Math.Abs(position0.volume); } else if (position0.tradingVarieties == "futures") { totalMargin += FuturesMargin.ComputeOpenMargin(data[position0.code][index].close, 0.4, position0.volume); } } totalCashFlow += position0.totalCashFlow; } myAccount.totalAssets = initialCapital + totalCashFlow + totalPositionValue; myAccount.freeCash = initialCapital + totalCashFlow - totalMargin; myAccount.margin = totalMargin; myAccount.positionValue = totalPositionValue; }
//根据signal进行成交判断 public static DateTime computeMinutePositions2(Dictionary <string, MinuteSignal> signal, Dictionary <string, List <KLine> > data, ref SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions, ref BasicAccount myAccount, DateTime now, double slipPoint = 0.003) { //若signal为空或无信号,返回下一时刻时间 if (signal == null || signal.Count == 0) { return(now.AddMinutes(1)); } //否则在信号价格上,朝不利方向加一个滑点成交 Dictionary <string, PositionsWithDetail> positionShot = new Dictionary <string, PositionsWithDetail>(); Dictionary <string, PositionsWithDetail> positionLast = (positions.Count == 0 ? null : positions[positions.Keys.Last()]); //合约乘数初始化 int contractTimes = 100; //成交价初始化,在当前模式下为信号价格朝不利方向加一个滑点 double transactionPrice = 0; //成交数量初始化,此方法下等于信号值(全部成交) double transactionVolume = 0; //当前成交成本初始化 double nowTransactionCost = 0; //当前后续费初始化 double nowBrokerFeeRatio = 0; //若上一时刻持仓不为空,上刻持仓先赋给此刻持仓,再根据信号调仓 if (positionLast != null) { positionShot = new Dictionary <string, PositionsWithDetail>(positionLast); } foreach (var signal0 in signal.Values) { //当前信号委托数量不为0,需进行下单操作 if (signal0.volume != 0) { //委托时间 now = (signal0.time > now) ? signal0.time : now; //合约乘数 if (signal0.tradingVarieties == "stock") { contractTimes = 100; } else if (signal0.tradingVarieties == "option") { contractTimes = 10000; } //当前临时头寸 PositionsWithDetail position0 = new PositionsWithDetail(); //多空持仓初始化 position0.LongPosition = new PositionDetail(); position0.ShortPosition = new PositionDetail(); //当前信号多空标志, int longShortFlag = (signal0.volume > 0) ? 1 : -1; //当前信号证券代码 position0.code = signal0.code; //将当前证券持仓情况赋给 // position0 = positionShot[position0.code]; //当前成交价,信号价格加滑点---注:此模型下信号价格即为现价 transactionPrice = signal0.price * (1 + slipPoint * longShortFlag); //当前可成交量,若成交价因滑点而改变,成交量也会因此改变 // transactionVolume = Math.Truncate((signal0.volume * signal0.price) / transactionPrice / contractTimes) * contractTimes; transactionVolume = signal0.volume; //当前成交成本(交易费+佣金) nowTransactionCost = 0; //获取当前品种手续费 nowBrokerFeeRatio = brokerFeeRatio[signal0.tradingVarieties]; //------------------------------------------------------------------- //验资,检查当前剩余资金是否足够执行信号 //计算当前信号占用资金 double nowSignalCapitalOccupy = longShortFlag == 1 ? transactionPrice * transactionVolume : CalculateOnesMarginForMinute.calculateOnesMargin(signal0.code, transactionVolume, now, ref data); //若资金不足,则跳过当前信号(*需要记录) /* * if (nowSignalCapitalOccupy > myAccount.freeCash) * continue; */ //当前证券已有持仓 if (positionLast != null && positionLast.ContainsKey(position0.code)) { //将当前证券持仓情况赋给临时持仓变量 position0 = positionShot[position0.code]; //当前为多头持仓 if (position0.volume > 0) { //若signal为long,则多头头寸增加 if (longShortFlag > 0) { //多头头寸更新 position0.LongPosition.averagePrice = (position0.LongPosition.averagePrice * position0.LongPosition.volume + transactionPrice * transactionVolume) / (position0.LongPosition.volume + transactionVolume); position0.LongPosition.volume = position0.LongPosition.volume + transactionVolume; position0.LongPosition.totalCost = position0.LongPosition.averagePrice * position0.LongPosition.volume; } //若signal为short,先平多,若不足则开空(注:short则成交量为负) else { // 若信号空头量小于等于持仓多头,则多头持仓减小 if ((position0.LongPosition.volume + transactionVolume) >= 0) { //多头头寸更新 //position价格不变,方便记录真实持仓成本 position0.LongPosition.volume = position0.LongPosition.volume + transactionVolume; position0.LongPosition.totalCost = position0.LongPosition.averagePrice * position0.LongPosition.volume; } else if ((position0.LongPosition.volume + transactionVolume) < 0) { //多头头寸更新,平多头 position0.LongPosition.averagePrice = 0; position0.LongPosition.volume = 0; position0.LongPosition.totalCost = 0; transactionVolume = position0.LongPosition.volume + transactionVolume; //空头头寸更新,开空头 position0.ShortPosition.averagePrice = transactionPrice; position0.ShortPosition.volume = transactionVolume; position0.ShortPosition.totalCost = position0.ShortPosition.averagePrice * position0.ShortPosition.volume; } } } //当前为空头持仓 positon0.volume < 0 else { //若signal为short,则空头头寸增加 if (longShortFlag < 0) { //空头头寸更新 position0.ShortPosition.averagePrice = (position0.ShortPosition.averagePrice * position0.ShortPosition.volume + transactionPrice * transactionVolume) / (position0.ShortPosition.volume + transactionVolume); position0.ShortPosition.volume = position0.ShortPosition.volume + transactionVolume; position0.ShortPosition.totalCost = position0.ShortPosition.averagePrice * position0.ShortPosition.volume; } //若signal为long,先平空,若不足则开多(注:short则成交量为负) else { // 若信号多头量小于等于持仓空头,则空头持仓减小 if ((position0.ShortPosition.volume + transactionVolume) <= 0) { //空头头寸更新 //position价格不变,方便记录真实持仓成本 position0.ShortPosition.volume = position0.ShortPosition.volume + transactionVolume; position0.ShortPosition.totalCost = position0.ShortPosition.averagePrice * position0.ShortPosition.volume; } else if ((position0.ShortPosition.volume + transactionVolume) > 0) { //空头头寸更新,平空头 position0.ShortPosition.averagePrice = 0; position0.ShortPosition.volume = 0; position0.ShortPosition.totalCost = 0; transactionVolume += position0.ShortPosition.volume; //多头头寸更新,开多头 position0.LongPosition.averagePrice = transactionPrice; position0.LongPosition.volume = transactionVolume; position0.LongPosition.totalCost = position0.LongPosition.averagePrice * position0.LongPosition.volume; } } } } //当前无证券持仓 else { //若为多头开仓,更新多头头寸 if (longShortFlag > 0) { position0.LongPosition.averagePrice = transactionPrice; position0.LongPosition.volume = transactionVolume; position0.LongPosition.totalCost = position0.LongPosition.averagePrice * position0.LongPosition.volume; } //若为空头开仓,更新空头头寸 else { position0.ShortPosition.averagePrice = transactionPrice; position0.ShortPosition.volume = transactionVolume; position0.ShortPosition.totalCost = position0.ShortPosition.averagePrice * position0.ShortPosition.volume; } } //持仓汇总信息记录 //当前时间 position0.time = now; //当前品种 position0.tradingVarieties = signal0.tradingVarieties; //当前价 position0.currentPrice = signal0.price; //当前持仓量 position0.volume = position0.LongPosition.volume + position0.ShortPosition.volume; //当前权益(实时) position0.totalAmt = position0.currentPrice * position0.volume; //手续费 //手续费计算,期权 if (signal0.tradingVarieties.Equals("option")) { //若为short信号,开空的部分手续费为0 //若信号为short,且调整持仓后交易总量大于等于空头持仓量,说明是先平多再开空,只收取平多部分手续费 if (longShortFlag < 0 && Math.Abs(transactionVolume) >= Math.Abs(position0.ShortPosition.volume)) { //平多合约张数 * 手续费 nowTransactionCost = Math.Abs((transactionVolume - position0.ShortPosition.volume) / contractTimes * nowBrokerFeeRatio); } //若信号为short,且调整持仓后交易总量小于空头持仓量,说明是继续开空,无手续费 else if (longShortFlag < 0 && Math.Abs(transactionVolume) < Math.Abs(position0.ShortPosition.volume)) { nowTransactionCost = 0; } //若信号为long,正常收取手续费 else { //合约张数 * 手续费 nowTransactionCost = Math.Abs(transactionVolume / contractTimes * nowBrokerFeeRatio); } } else if (signal0.tradingVarieties.Equals("stock")) { //若信号为short,正常收取手续费 if (longShortFlag < 0) { //成交金额 * 手续费率 nowTransactionCost = Math.Abs(transactionPrice * transactionVolume * nowBrokerFeeRatio); } //若信号为long,无手续费 else { nowTransactionCost = 0; } } //实际中不同期货品种手续费差异大,有的按手有的按成交额比率,有的单边有的双边,此处简单处理按单边 else if (signal0.tradingVarieties.Equals("futures")) { //若信号为short,正常收取手续费 if (longShortFlag < 0) { //成交金额 * 手续费率 nowTransactionCost = Math.Abs(transactionPrice * transactionVolume * nowBrokerFeeRatio); } //若信号为long,无手续费 else { nowTransactionCost = 0; } } //总手续费、持仓成本更新 //手续费,持续累加 position0.transactionCost += nowTransactionCost; //当前品种总现金流,包含历史现金流,若未持仓该品种,则记录持仓盈亏,若有持仓,则为历史现金流 + 当前现金流。该指标用于计算freeCash // position0.totalCashFlow += (position0.volume > 0 ? -position0.LongPosition.totalCost : -position0.ShortPosition.totalCost) - nowTransactionCost; position0.totalCashFlow += -transactionPrice * transactionVolume - nowTransactionCost; //交易记录添加 position0.record = new List <TransactionRecord>(); position0.record.Add(new TransactionRecord { time = now, volume = transactionVolume, price = transactionPrice }); //存储当前持仓信息 if (positionShot.ContainsKey(position0.code)) { positionShot[position0.code] = position0; } else { positionShot.Add(signal0.code, position0); } //账户信息更新 //根据当前交易记录和持仓情况更新账户 if (positions.Count != 0) { AccountUpdatingForMinute.computeAccountUpdating(ref myAccount, positions, now, data); } } } positions.Add(now, positionShot); return(now.AddMinutes(1)); }
/// <summary> /// /// </summary> /// <param name="signal">交易信号(正向/反向)</param> /// <param name="data">KLine格式的交易数据</param> /// <param name="positions">头寸信息</param> /// <param name="myAccount">账户信息</param> /// <param name="now">交易日的时间信息</param> /// <param name="nowIndex">当前索引值(不知道什么意思)</param> /// <param name="slipPoint">滑点</param> /// <returns></returns> public static Dictionary <string, ExecutionReport> ComputePosition(Dictionary <string, MinuteSignal> signal, Dictionary <string, List <KLine> > data, ref SortedDictionary <DateTime, Dictionary <string, PositionsWithDetail> > positions, ref BasicAccount myAccount, DateTime now, int nowIndex, double slipPoint = 0.00) { //初始化记录成交回报的变量 Dictionary <string, ExecutionReport> tradingFeedback = new Dictionary <string, ExecutionReport>(); //初始化上一次头寸记录时间 DateTime lastTime = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0); //如果signal无信号,无法成交,直接返回空的成交回报。 if (signal == null || signal.Count == 0) { return(tradingFeedback); } if (positions.Count != 0) { lastTime = positions.Keys.Last(); } //新建头寸变量,作为接受新仓位的容器 Dictionary <string, PositionsWithDetail> positionShot = new Dictionary <string, PositionsWithDetail>(); //如果持仓最后状态时间大于signal信号的时间,无成交,直接返回空的成交回报。 if (lastTime > now) { return(tradingFeedback); } //如果两者时间相等,则把总仓位变化数组positions中的最后一项,添加进新仓位的容器positionShot中 if (now == lastTime) { positionShot = positions[positions.Keys.Last()]; } //如果交易信号时间在最后一次持仓变化时间点之后,则需要重新把最后持仓的仓位变化信息手工copy一份; //然后添加进新仓位的容器positionShot中。 else if (positions.Count > 0) //如果持仓大于0 { foreach (var item in positions[positions.Keys.Last()]) //循环 持仓最后状态时间的持仓数据 { //这里必须手动copy一份,不可以直接传引用。因为最后持仓变化节点的仓位信息是不应该被改变的; //如果直接传引用,交易信号时间点仓位变化会同时改变最后持仓变化节点的仓位信息。 PositionsWithDetail position0 = new PositionsWithDetail().myClone(item.Value);//复制一份新的 positionShot.Add(position0.code, position0); } } //获取前一步的头寸信息,如果没有寸头就设为null Dictionary <string, PositionsWithDetail> positionLast = (positions.Count == 0 ? null : positions[positions.Keys.Last()]); //对信号进行遍历 foreach (var signal0 in signal.Values) { //整理成交信号,剔除不合理信号。 //①信号触发时间必须在positionLast的记录时间之后,在当前时间now之前。 //②信号必须有合理的交易数量。 //③信号必须有对应的数据。 if (signal0.time != now) //【???】不是说必须要在当前时间now之前么 { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.status = "交易时间错误,无效信号"; tradingFeedback.Add(signal0.code, report0); continue; } if (signal0.volume == 0 || signal0.price == 0) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.status = "交易数据错误,无效信号"; tradingFeedback.Add(signal0.code, report0); continue; } if (data.ContainsKey(signal0.code) == false) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.status = "无法找到行情数据,无效信号"; tradingFeedback.Add(signal0.code, report0); continue; } //根据K线来判断成交数量,有可能完全成交,也有可能部分成交 //开多头时,如果价格大于最低价,完全成交,否者不成交 //开空头时,如果价格小于最高价,完全成交,否者不成交 //找出对应的K线 KLine KLineData = data[signal0.code][nowIndex]; //确定滑点 double slip = Math.Max(slipPoint, signal0.bidAskSpread); //开多头时,如果价格大于最低价,完全成交,否者不成交 if (signal0.volume > 0 && signal0.price >= KLineData.low) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.volume = signal0.volume; report0.price = signal0.price + slip; report0.status = "完全成交"; tradingFeedback.Add(signal0.code, report0); } if (signal0.volume > 0 && signal0.price < KLineData.low) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.price = signal0.price + slip; report0.status = "无法成交"; tradingFeedback.Add(signal0.code, report0); continue; } //开空头时,如果价格小于最高价,完全成交,否者不成交 if (signal0.volume < 0 && signal0.price <= KLineData.high) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.price = signal0.price - slip; report0.volume = signal0.volume; report0.status = "完全成交"; tradingFeedback.Add(signal0.code, report0); } if (signal0.volume < 0 && signal0.price > KLineData.high) { ExecutionReport report0 = new ExecutionReport(); report0.code = signal0.code; report0.time = signal0.time; report0.price = signal0.price - slip; report0.status = "无法成交"; tradingFeedback.Add(signal0.code, report0); continue; } ///【解释:以下部分 position,positionShot和position0的关系】 /// position:是传入的参数, /// //接下来处理能够成交的signal0,信号下单的时间只能是lastTime或者now。 PositionsWithDetail position0 = new PositionsWithDetail(); //查询当前持仓数量 double nowHoldingVolume; //当前证券已有持仓 if (positionShot.Count > 0 && positionShot.ContainsKey(signal0.code)) { //将当前证券持仓情况赋值给临时持仓变量 position0 = positionShot[signal0.code]; nowHoldingVolume = position0.volume; } else //若历史无持仓 { //当前信号证券代码 position0.code = signal0.code; //多空头寸初始化 position0.LongPosition = new PositionDetail(); position0.ShortPosition = new PositionDetail(); position0.record = new List <TransactionRecord>(); nowHoldingVolume = 0; positionShot.Add(position0.code, position0); } //持仓和开仓方向一致 if (nowHoldingVolume * signal0.volume >= 0) { //开多仓 if (signal0.volume > 0) { //重新计算仓位和价格 double volume = signal0.volume + position0.LongPosition.volume; double cost = (signal0.price + slip) * signal0.volume + position0.LongPosition.volume * position0.LongPosition.averagePrice; double averagePrice = cost / volume; position0.LongPosition = new PositionDetail { volume = volume, totalCost = cost, averagePrice = averagePrice }; } else //开空仓 { //重新计算仓位和价格 double volume = signal0.volume + position0.ShortPosition.volume; double cost = (signal0.price - slip) * signal0.volume + position0.ShortPosition.volume * position0.ShortPosition.averagePrice; double averagePrice = cost / volume; position0.ShortPosition = new PositionDetail { volume = volume, totalCost = cost, averagePrice = averagePrice }; } } else //持仓和开仓方向不一致 { if (nowHoldingVolume > 0) //原先持有多头头寸,现开空仓 { //计算总头寸,分类讨论 double volume = signal0.volume + position0.LongPosition.volume; if (volume > 0) { position0.LongPosition.volume = volume; position0.LongPosition.totalCost = position0.LongPosition.volume * position0.LongPosition.averagePrice; } else if (volume < 0) { position0.ShortPosition.volume = volume; position0.ShortPosition.totalCost = volume * (signal0.price - slip); position0.ShortPosition.averagePrice = (signal0.price - slip); position0.LongPosition = new PositionDetail(); } else { position0.LongPosition = new PositionDetail(); } } else //原先持有空头头寸,现开多仓 { //计算总头寸,分类讨论 double volume = signal0.volume + position0.ShortPosition.volume; if (volume < 0) { position0.ShortPosition.volume = volume; position0.ShortPosition.totalCost = position0.ShortPosition.volume * position0.ShortPosition.averagePrice; } else if (volume > 0) { position0.LongPosition.volume = volume; position0.LongPosition.averagePrice = (signal0.price + slip); position0.LongPosition.totalCost = (signal0.price - slip) * volume; position0.ShortPosition = new PositionDetail(); } else { position0.ShortPosition = new PositionDetail(); } } } //更新其他信息 position0.record.Add(new TransactionRecord { time = now, volume = signal0.volume, price = signal0.price + slip * (signal0.volume > 0?1:-1), code = position0.code }); position0.totalCashFlow = position0.totalCashFlow - (signal0.price + (signal0.volume > 0 ? 1 : -1) * slip) * signal0.volume; position0.transactionCost = position0.transactionCost + Math.Abs(slip * signal0.volume); position0.volume = position0.volume + signal0.volume; position0.time = now; position0.code = signal0.code; position0.currentPrice = KLineData.close; position0.tradingVarieties = signal0.tradingVarieties; //总资产=权益现值+现金盈亏 position0.totalAmt = position0.totalCashFlow + position0.volume * position0.currentPrice; } if (now > lastTime) { positions.Add(now, positionShot); } //更新持仓的头寸信息 if (positions.Count != 0) { AccountUpdatingWithMinuteBar.computeAccount(ref myAccount, positions, now, nowIndex, data); } return(tradingFeedback); }