/// <summary> /// Редактирование 'опасных' полей сделок /// </summary> /// <param name="strId">Уникальные идентификаторы редактируемых сделок</param> /// <param name="currentState">Статус редактируемых сделок</param> /// <param name="newTicker">Новое значение инструмента</param> /// <param name="newSide">Новое значение типа сделки</param> /// <param name="newVolume">Новое значение объёма сделки</param> /// <param name="newEnterPrice">Новое значение цены входа</param> /// <param name="newExitPrice">Новое значение цены выхода</param> public bool EditDangerDeal(string strId, PositionState currentState, string newTicker, int?newSide, int?newVolume, float?newEnterPrice, float?newExitPrice) { var id = strId.ToIntArrayUniform(); using (var ctx = DatabaseContext.Instance.Make()) { var selOrders = new List <MarketOrder>(); // ReSharper disable LoopCanBeConvertedToQuery if (currentState == PositionState.Closed) { foreach (var order in ctx.POSITION_CLOSED.Where(x => id.Contains(x.ID))) { selOrders.Add(LinqToEntity.DecorateOrder(order)); } } else { foreach (var order in ctx.POSITION.Where(x => id.Contains(x.ID))) { selOrders.Add(LinqToEntity.DecorateOrder(order)); } } // ReSharper restore LoopCanBeConvertedToQuery // Группируем все сделки по счётам var selOrderGroupByAccount = selOrders.GroupBy(x => x.AccountID); // Идём по счетам в группе foreach (var selOrderGroup in selOrderGroupByAccount) { var acc = accountRepository.GetAccount(selOrderGroup.Key); try { // Идём по сделкам в счёте foreach (var order in selOrderGroup) { decimal?sl = null; if (order.StopLoss.HasValue) { sl = Convert.ToDecimal(order.StopLoss); } decimal?tp = null; if (order.TakeProfit.HasValue) { tp = Convert.ToDecimal(order.TakeProfit); } var timeEnter = order.TimeEnter; var timeExit = order.TimeExit; var price = newEnterPrice.HasValue ? newEnterPrice : order.PriceEnter; var priceExit = newExitPrice.HasValue ? newExitPrice : order.PriceExit; var volume = newVolume.HasValue ? newVolume : order.Volume; var symbol = !string.IsNullOrEmpty(newTicker) && newTicker.ToLower() != "null" ? newTicker : order.Symbol; var side = newSide.HasValue ? newSide : order.Side; var state = order.State; #region MarketOrder ordClosed = null; if (state == PositionState.Closed) { // посчитать профит по сделке ordClosed = order.MakeCopy(); ordClosed.State = PositionState.Closed; ordClosed.PriceExit = priceExit.Value; ordClosed.TimeExit = timeExit.Value; string errorStr = null; if (!priceExit.HasValue || !DealProfitCalculator.CalculateOrderProfit(ordClosed, acc.Currency, priceExit.Value, out errorStr)) { if (!string.IsNullOrEmpty(errorStr)) { Logger.Error("Сделка " + order.ID + " не будет закрыта : " + errorStr); } else { Logger.Error("Сделка " + order.ID + ". не будет закрыта : не указана priceExit."); } continue; } } try { //Если сделка из 'открытых' if (state != PositionState.Closed) { // поправить открытую позицию var pos = ctx.POSITION.First(p => p.ID == order.ID); pos.PriceEnter = Convert.ToDecimal(price.Value); pos.TimeEnter = timeEnter; pos.Stoploss = sl; pos.Takeprofit = tp; pos.Volume = volume.Value; pos.Symbol = symbol; pos.Side = side.Value; pos.State = (int)state; } else { #region поправить закрытую позу и скорректировать результат var pos = ctx.POSITION_CLOSED.First(p => p.ID == order.ID); pos.PriceEnter = Convert.ToDecimal(price.Value); pos.TimeEnter = timeEnter; pos.Stoploss = sl; pos.Takeprofit = tp; pos.Volume = volume.Value; pos.Symbol = symbol; pos.Side = side.Value; pos.PriceExit = Convert.ToDecimal(priceExit.Value); pos.TimeExit = timeExit.Value; pos.ResultDepo = (decimal)ordClosed.ResultDepo; pos.ResultBase = (decimal)ordClosed.ResultBase; pos.ResultPoints = (decimal)ordClosed.ResultPoints; #endregion // поправить проводку и баланс UpdateBalanceChange(ctx, ordClosed, false); } } catch (Exception ex) { Logger.Error("Ошибка при редактировании сделки " + order.ID, ex); } #endregion Logger.Info("Сделка " + order.ID + " отредактирована удачно."); } // коммит ctx.SaveChanges(); } catch (Exception ex) { Logger.Error("EditDangerDeal", ex); return(false); } if (currentState == PositionState.Closed) { ReCalculateAccountBalance(ctx, acc.ID); } } return(true); } }
/// <summary> /// Закрытие сделок /// </summary> /// <param name="strId">Уникальные идентификаторы закрываемых сделок, перечисленные через запятую</param> /// <param name="timeExit">Время выхода</param> /// <param name="exitReason">Symbol, Side (в виде Ask и Bid), Price</param> /// <param name="lstPrice">Причина закрытия сделки, указанная пользователем</param> public List <string> ClosingPositions(string strId, DateTime timeExit, PositionExitReason exitReason, List <Tuple <string, int, float> > lstPrice) { Logger.Info("Начинаем закрывать сделки " + strId); var result = new List <string>(); var id = strId.ToIntArrayUniform(); try { using (var ctx = DatabaseContext.Instance.Make()) { // Вытаскиваем все открытые сделки, которые нужно закрыть var selOrders = new List <MarketOrder>(); // ReSharper disable LoopCanBeConvertedToQuery foreach (var order in ctx.POSITION.Where(x => id.Contains(x.ID))) { selOrders.Add(LinqToEntity.DecorateOrder(order)); } // ReSharper restore LoopCanBeConvertedToQuery if (selOrders.Count != id.Length) { Logger.Error("ClosingPositions() - в таблице 'POSITION' не найдены некоторые или все сделки с идентификаторами " + strId); } // Группируем все сделки по счётам var selOrderGroupByAccount = selOrders.GroupBy(x => x.AccountID); // Перебираем все счета foreach (var orderGroup in selOrderGroupByAccount) { // Список удачно закрытых сделок в текущем счёте var successClosedPositions = new List <string>(); var acc = accountRepository.GetAccount(orderGroup.Key); if (acc == null) { Logger.Error("ClosingPositions() - не удалось получить счёт " + orderGroup.Key); continue; } Logger.Info("Начинаем закрывать сделки в счёте " + orderGroup.Key); // Перебираем все сделки в текущем счёте foreach (var order in orderGroup) { #region //Ищем цену выхода, указанную пользователем, для сделок с таким тикером и направлением var priceExitTuple = lstPrice.FirstOrDefault(x => x.Item1 == order.Symbol && x.Item2 == order.Side); if (priceExitTuple == null) { Logger.Error(string.Format("ClosingPositions() - не найдена цена выхода, указанная пользователем, для сделок счёта {0} с тикером {1} и направлением {2}", order.ID, order.Symbol, order.Side)); continue; } var closedOrder = order.MakeCopy(); closedOrder.State = PositionState.Closed; closedOrder.TimeExit = timeExit; closedOrder.PriceExit = priceExitTuple.Item3; closedOrder.ExitReason = exitReason; // посчитать прибыль string errorStr; if (!DealProfitCalculator.CalculateOrderProfit(closedOrder, acc.Currency, priceExitTuple.Item3, out errorStr)) { if (!string.IsNullOrEmpty(errorStr)) { Logger.Error("Сделка " + closedOrder.ID + " не будет закрыта - не удалось пересчитать прибыль : " + errorStr); } continue; } var balance = new BALANCE_CHANGE { AccountID = order.AccountID, ChangeType = closedOrder.ResultBase > 0 ? (int)BalanceChangeType.Profit : (int)BalanceChangeType.Loss, Description = string.Format("результат сделки #{0}", order.ID), Amount = (decimal)Math.Abs(closedOrder.ResultDepo), ValueDate = closedOrder.TimeExit.Value }; // убрать сделку из числа открытых, добавить закрытую и добавить проводку по счету try { // убрать var pos = ctx.POSITION.FirstOrDefault(p => p.ID == order.ID); ctx.POSITION.Remove(pos); Logger.Info("запись о сделке " + order.ID + " удалена из таблици POSITION"); ctx.POSITION_CLOSED.Add(LinqToEntity.UndecorateClosedPosition(closedOrder)); Logger.Info("запись о сделке " + order.ID + " добавленав таблицу POSITION_CLOSED"); // добавить проводку по счету ctx.BALANCE_CHANGE.Add(balance); var acBase = ctx.ACCOUNT.FirstOrDefault(ac => ac.ID == order.AccountID); if (acBase == null) { Logger.Error("ClosingPositions() - не удалось найти счёт " + order.AccountID + " в таблице 'ACCOUNT', что бы добавить проводку"); continue; } acBase.Balance += (decimal)closedOrder.ResultDepo; Logger.Info("Баланс счёта " + order.AccountID + " изменён на величину " + (decimal)closedOrder.ResultDepo); } catch (Exception ex) { Logger.Error("ClosingPositions() - Ошибка при попытке убрать сделку из числа открытых, добавить закрытую и добавить проводку по счету", ex); continue; } #endregion successClosedPositions.Add(order.ID.ToString()); Logger.Error("Сделка " + order.ID + " отредактирована удачно"); } ReCalculateAccountBalance(ctx, acc.ID); Logger.Info("Начинаем сохранять в базу данных изменения по счёту " + orderGroup.Key); ctx.SaveChanges(); result.AddRange(successClosedPositions); } if (result.Count == 0) { Logger.Info("Не удалось закрыть ни одной из указанных сделок"); } else { Logger.Info("Изменения сохранены. Из указанных сделок " + strId + " закрыты следующие: " + string.Join(", ", result)); } } } catch (Exception ex) { Logger.Error("ClosingPositions() - возникла ошибка при попытке сохранить изменения в базу данных. Не удалось закрыть сделки " + strId, ex); } return(result); }