using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Windows.Input; using MarketData; using MarketData.MarketDataModel; using MarketData.Helper; using MarketData.DataAccess; using MarketData.Utils; using TradeBlotter.DataAccess; using TradeBlotter.Command; using TradeBlotter.Model; namespace TradeBlotter.ViewModels { public class TradeEntryViewModel : WorkspaceViewModel { private BlotterTradeModel trade = null; private StopLimit stopLimit; private readonly TradeRepository tradeRepository; private RelayCommand saveCommand; private String statusText=""; public TradeEntryViewModel(BlotterTradeModel trade,TradeRepository tradeRepository) { base.DisplayName="TradeEntry"; this.trade=trade; this.tradeRepository=tradeRepository; if(StopLimitDA.HasStopLimit(trade.Symbol,trade.Shares)) stopLimit=StopLimitDA.GetStopLimit(trade.Symbol,trade.Shares); PropertyChanged+=OnTradeEntryViewModelPropertyChanged; UpdateProperties(); } public override SaveParameters GetSaveParameters() { return null; } public override void SetSaveParameters(SaveParameters saveParameters) { } public override bool CanPersist() { return false; } public void SetActiveTrade(BlotterTradeModel trade) { this.trade = trade; UpdateProperties(); } private void UpdateProperties() { base.OnPropertyChanged("Symbol"); base.OnPropertyChanged("Price"); base.OnPropertyChanged("CompanyName"); base.OnPropertyChanged("TradeDate"); base.OnPropertyChanged("SelectedTradeType"); base.OnPropertyChanged("SelectedAccount"); base.OnPropertyChanged("SelectedStatus"); base.OnPropertyChanged("TradeId"); base.OnPropertyChanged("Shares"); base.OnPropertyChanged("Exposure"); base.OnPropertyChanged("AvailableCash"); } private void OnTradeEntryViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs) { if (eventArgs.PropertyName.Equals("Symbol")) { if(null!=trade && null!=trade.Symbol) { if(-1==trade.TradeId) // only suggest the price for a new trade (i.e.) a trade that has not yet been assigned a tradeid { Price price=PricingDA.GetPrice(trade.Symbol,trade.TradeDate); if(null==price) price=PricingDA.GetPrice(trade.Symbol); if(null!=price)trade.Price=price.Close; base.OnPropertyChanged("Price"); } String companyName = PricingDA.GetNameForSymbol(trade.Symbol); if (null != companyName) { trade.CompanyName = companyName; base.OnPropertyChanged("CompanyName"); } } } else if (eventArgs.PropertyName.Equals("TradeDate")) { base.OnPropertyChanged("Price"); } else if (eventArgs.PropertyName.Equals("SelectedAccount") || eventArgs.PropertyName.Equals("Shares") || eventArgs.PropertyName.Equals("Price") || eventArgs.PropertyName.Equals("SellPrice")) { base.OnPropertyChanged("AvailableCash"); } } public String TradeId { get { return -1==trade.TradeId?Constants.CONST_DASHES:trade.TradeId.ToString(); } set { ;} } public List TradeTypeOptions { get { return new List() { "Buy", "Sell" }; } } // Buy/Sell public String SelectedTradeType { get { if (null == trade.BuySell) return null; else if (trade.BuySell.Equals("S")) return "Sell"; else if (trade.BuySell.Equals("B")) return "Buy"; else return null; } set { if(String.IsNullOrEmpty(value)) return; trade.BuySell = value.Equals("Buy") ? "B" : "S"; if (trade.BuySell.Equals("B")) { trade.Status = "OPEN"; trade.SellPrice = double.NaN; trade.SellDate = Utility.Epoch; base.OnPropertyChanged("SellPrice"); base.OnPropertyChanged("SellDate"); } else { trade.Status = "CLOSED"; } base.OnPropertyChanged("SelectedStatus"); base.OnPropertyChanged("SelectedTradeType"); } } // ***************************************** A C C O U N T *************************** public List AccountOptions { get { return PortfolioDA.GetAccounts(); } } public String SelectedAccount { get { return null==trade.Account||trade.Account.Equals("") ? null : trade.Account; } set { trade.Account = value; base.OnPropertyChanged("SelectedAccount"); } } //************************************************************************************ public List TradeStatusOptions { get {return new List(){"OPEN","CLOSED"}; } } public String SelectedStatus { get { return null==trade.Status||trade.Status.Equals("") ? null : trade.Status; } } public DateTime TradeDate { get { return trade.TradeDate; } set { trade.TradeDate = value; Price price = null; if (!Utility.IsEpoch(trade.TradeDate) && !trade.TradeDate.Date.Equals(DateTime.Now.Date)) { price = PricingDA.GetPrice(trade.Symbol, trade.TradeDate); } else { price = MarketDataHelper.GetLatestPrice(trade.Symbol); if (null == price) price = PricingDA.GetPrice(trade.Symbol); } if (null != price) { trade.Price = price.Close; base.OnPropertyChanged("TradeDate"); } } } public String Symbol { get { return trade.Symbol; } set { trade.Symbol = value; trade.Symbol = trade.Symbol.ToUpper(); base.OnPropertyChanged("Symbol"); } } public double Shares { get { return trade.Shares; } set { trade.Shares = value; base.OnPropertyChanged("Shares"); base.OnPropertyChanged("Exposure"); } } public String CompanyName { get { return trade.CompanyName; } set { trade.CompanyName = value; base.OnPropertyChanged("CompanyName"); } } public String Exposure { get { return Utility.FormatCurrency(trade.Exposure); } set { ;} } public String Price { get { return Utility.FormatPrice(trade.Price); } set { trade.Price = Utility.ParseCurrency(value); base.OnPropertyChanged("Price"); base.OnPropertyChanged("Exposure"); } } public String StopLimit { get { if(null==stopLimit)return Constants.NA; return Utility.FormatCurrency(stopLimit.StopPrice,2); } set { if(String.IsNullOrEmpty(value))stopLimit=null; else { if(null==stopLimit) { stopLimit=new StopLimit(); stopLimit.Symbol=trade.Symbol; stopLimit.Shares=trade.Shares; stopLimit.StopType=StopLimitConstants.STOP_QUOTE; } stopLimit.StopPrice=Utility.ParseCurrency(value); } base.OnPropertyChanged("StopLimit"); } } public String SellPrice { get { return Utility.FormatPrice(trade.SellPrice); } set { if (!trade.Status.Equals("CLOSED")) return; trade.SellPrice = Utility.ParseCurrency(value); base.OnPropertyChanged("SellPrice"); } } public DateTime? SellDate { get { if (Utility.IsEpoch(trade.SellDate)) return null; return trade.SellDate; } set { trade.SellDate = value.Value; base.OnPropertyChanged("SellDate"); } } public String Commission { get { return Utility.FormatCurrency(trade.Commission); } set { trade.Commission = Utility.ParseCurrency(value); } } public String Status { get { return statusText; } set { if(!String.IsNullOrEmpty(value)) { statusText = value; base.OnPropertyChanged("Status"); } } } public String AvailableCash { get { double availableCash = CashDA.GetBalance(); if(null!=SelectedAccount)availableCash=CashDA.GetBalance(SelectedAccount); if (null == trade.BuySell) return Utility.FormatCurrency(availableCash); if(trade.BuySell.Equals("S") && double.IsNaN(trade.SellPrice) && double.IsNaN(trade.Shares))return Utility.FormatCurrency(availableCash); if(trade.BuySell.Equals("B") && double.IsNaN(trade.Price) && double.IsNaN(trade.Shares)) return Utility.FormatCurrency(availableCash); if(trade.BuySell.Equals("S")) availableCash += trade.SellPrice * trade.Shares; if(trade.BuySell.Equals("B")) availableCash -= trade.Price * trade.Shares; if (!double.IsNaN(trade.Commission)) availableCash -= trade.Commission; return Utility.FormatCurrency(availableCash); } } // ******************************************************************************************************* // ****************************************** S A V E C O M M A N D ************************************* // ******************************************************************************************************* public ICommand SaveCommand { get { if(saveCommand == null) { saveCommand = new RelayCommand(param => this.Save(),param => this.CanSave); } return saveCommand; } } public void Save() { if (!tradeRepository.ContainsTrade(trade)) { tradeRepository.AddTrade(trade); WatchListDA.AddToWatchList(trade.Symbol); base.OnPropertyChanged("TradeId"); base.OnPropertyChanged("DisplayName"); statusText = "Save completed."; } else { tradeRepository.UpdateTrade(trade); statusText = "Update completed."; } UpdateStopLimit(); base.OnPropertyChanged("Status"); } /// /// There is a general problem with StopLimits at this level because the StopLimits table is keyed on Symbol /// What this means is that we cannot hold two positions for the same security with different stop limits. /// It also means that when we close one of two positions where one or both of those positions has a stop limit /// Then we are not quite sure which stop limit to update. /// The solution would be to key the StopLimit table on the trade.Id as thid would allow us to have a per position stop limit /// In the interim we do our best here to associate the trade with a given stop limit by matching it to the number of shares. /// private void UpdateStopLimit() { if(trade.IsClosed) { if(null!=stopLimit && stopLimit.Shares.Equals(trade.Shares)) { StopLimitDA.DeleteStopLimit(trade.Symbol,trade.Shares); } } else { if(null!=stopLimit) { StopLimitDA.InsertUpdateStopLimit(stopLimit); } } } public bool CanSave { get { try { DateGenerator dateGenerator=new DateGenerator(); if (null == trade.TradeDate || Utility.IsEpoch(trade.TradeDate)) { statusText=String.Format("Invalid Trade Date."); return false; } if (null == trade.BuySell || !(trade.BuySell.ToUpper().Equals("B") || trade.BuySell.ToUpper().Equals("S"))) { statusText=String.Format("BuySell must contain either BUY or SELL."); return false; } if (null == trade.Status || (!trade.Status.Equals("OPEN") && !trade.Status.Equals("CLOSED"))) { statusText=String.Format("Status must contain either OPEN or CLOSED."); return false; } if(null!=trade.Status&&(trade.Status.Equals("CLOSED")||trade.BuySell.ToUpper().Equals("S"))&&double.IsNaN(trade.SellPrice)) { statusText="Close position requires a valid selling price."; return false; } if(null!=trade.Status&&(trade.Status.Equals("CLOSED") || trade.BuySell.ToUpper().Equals("S")) && Utility.IsEpoch(trade.SellDate)) { statusText="Close position requires a valid sell date."; return false; } if(null!=trade.Status&&(trade.Status.Equals("OPEN")||trade.BuySell.ToUpper().Equals("B"))&&!Utility.IsEpoch(trade.SellDate)) { statusText="An open position cannot contain a sell date."; return false; } if(null==trade.Symbol) { statusText="A trade entry must contain a valid symbol."; return false; } if (0 == trade.Shares) { statusText="A trade entry must contain a number of shares."; return false; } if (null == trade.Account || "".Equals(trade.Account)) { statusText="A trade entry must contain a valid account."; return false; } if (0 == trade.Price) { statusText="A trade entry must contain a valid price that is greater than zero."; return false; } if(!Utility.IsEpoch(trade.TradeDate) && !dateGenerator.IsMarketOpen(trade.TradeDate)) { statusText=String.Format("Trade date must be a valid market date. {0} is a holiday or weekend. ",Utility.DateTimeToStringMMSDDSYYYY(trade.TradeDate)); return false; } if(!Utility.IsEpoch(trade.SellDate)&&!dateGenerator.IsMarketOpen(trade.SellDate)) { statusText=String.Format("Sell date must be a valid market date. {0} is a holiday or weekend. ",Utility.DateTimeToStringMMSDDSYYYY(trade.SellDate)); return false; } statusText="Form is validated."; return true; } catch(Exception exception) { statusText=exception.ToString(); return false; } finally { base.OnPropertyChanged("Status"); } } } } }