public void ConvertCandles(IBrokersCandlesService candlesService, IBroker broker, List <string> markets) { var candlesDirectory = Path.Combine(Path.GetDirectoryName(typeof(MainWindowViewModel).Assembly.Location), "Candles"); if (!Directory.Exists(candlesDirectory)) { Directory.CreateDirectory(candlesDirectory); } foreach (var market in markets) { if (File.Exists(GetCandlesPath(market, Timeframe.H2)) && File.Exists(GetCandlesPath(market, Timeframe.H4)) && File.Exists(GetCandlesPath(market, Timeframe.D1)) && File.Exists(GetCandlesPath(market, Timeframe.M5))) { continue; } var allM5Candles = candlesService.GetCandles(broker, market, Timeframe.M5, false, cacheData: false); var allH2Candles = candlesService.GetCandles(broker, market, Timeframe.H2, false, cacheData: false); var allH4Candles = candlesService.GetCandles(broker, market, Timeframe.H4, false, cacheData: false); var allD1Candles = candlesService.GetCandles(broker, market, Timeframe.D1, false, cacheData: false); if (allD1Candles.Count == 0 || allH2Candles.Count == 0 || allH4Candles.Count == 0 || allM5Candles.Count == 0) { continue; } var bytes = Compress(BrokersCandlesService.CandlesToBytes(allM5Candles)); var path = Path.Combine(candlesDirectory, $"{market.Replace("/", string.Empty)}_M5.dat"); File.WriteAllBytes(path, bytes); bytes = Compress(BrokersCandlesService.CandlesToBytes(allH2Candles)); path = Path.Combine(candlesDirectory, $"{market.Replace("/", string.Empty)}_H2.dat"); File.WriteAllBytes(path, bytes); bytes = Compress(BrokersCandlesService.CandlesToBytes(allH4Candles)); path = Path.Combine(candlesDirectory, $"{market.Replace("/", string.Empty)}_H4.dat"); File.WriteAllBytes(path, bytes); bytes = Compress(BrokersCandlesService.CandlesToBytes(allD1Candles)); path = Path.Combine(candlesDirectory, $"{market.Replace("/", string.Empty)}_D1.dat"); File.WriteAllBytes(path, bytes); GC.Collect(); } }
public static List <Candle> GetDerivedCandles(this IBrokersCandlesService candlesService, IBroker broker, string firstSymbol, string secondSymbol, Timeframe timeframe, bool updateCandles = false, DateTime?minOpenTimeUtc = null, DateTime?maxCloseTimeUtc = null, bool forceCreateDerived = false) { var pair = $"{firstSymbol}{secondSymbol}"; if (!forceCreateDerived) { var candles = candlesService.GetCandles(broker, pair, timeframe, false, minOpenTimeUtc: minOpenTimeUtc, maxCloseTimeUtc: maxCloseTimeUtc); if (candles != null && candles.Count > 0) { return(candles); } } var calculatedMarketCandles = new DerivedMarketCandles(broker, candlesService); return(calculatedMarketCandles.CreateCandlesSeries( firstSymbol, secondSymbol, timeframe, updateCandles, minOpenTimeUtc, maxCloseTimeUtc)); }
/// <summary> /// Uses a faster mechanism for finding candles. /// </summary> /// <param name="service"></param> /// <param name="market"></param> /// <param name="broker"></param> /// <param name="timeframe"></param> /// <param name="dateTime"></param> /// <param name="updateCandles"></param> /// <returns></returns> public static Candle?GetLastClosedCandle(this IBrokersCandlesService service, string market, IBroker broker, Timeframe timeframe, DateTime dateTime, bool updateCandles = false) { var candles = service.GetCandles(broker, market, timeframe, updateCandles); if (candles == null || candles.Count == 0) { return(null); } var maxItemsInRange = 20; if (candles.Count <= maxItemsInRange) { for (var i = candles.Count - 1; i >= 0; i--) { if (candles[i].CloseTimeTicks <= dateTime.Ticks) { return(candles[i]); } } return(null); } if (candles[candles.Count - 1].CloseTimeTicks <= dateTime.Ticks) { return(candles[candles.Count - 1]); } var range1Start = 0; var range2End = candles.Count - 1; var range1End = (range2End - range1Start) / 2; while (range2End - range1Start + 1 > maxItemsInRange) { var range2Start = range1End + 1; if (candles[range2Start].CloseTimeTicks > dateTime.Ticks) { range2End = range1End; range1End = range1Start + (range2End - range1Start) / 2; } else { range1Start = range1End + 1; range1End = range1Start + (range2End - range1Start) / 2; } } for (var i = range2End; i >= range1Start; i--) { if (candles[i].CloseTimeTicks <= dateTime.Ticks) { return(candles[i]); } } return(null); }
public decimal CalculateUSDTValue(IBroker broker, DateTime currentTime) { var value = 0M; foreach (var assetBalance in CurrentAssetBalances) { if (assetBalance.Value.Asset == "USDT") { value += assetBalance.Value.Balance; continue; } var candles = _candleService.GetCandles(broker, $"{assetBalance.Value.Asset}USDT", Timeframe.H1, false, maxCloseTimeUtc: currentTime); var candle = candles.Last(); var assetValue = assetBalance.Value.Balance * (decimal)candle.CloseBid; value += assetValue; } return(value); }
/// <summary> /// Uses a faster mechanism for finding candles. /// </summary> /// <param name="service"></param> /// <param name="market"></param> /// <param name="broker"></param> /// <param name="timeframe"></param> /// <param name="dateTime"></param> /// <param name="updateCandles"></param> /// <returns></returns> public static Candle?GetLastClosedCandle(this IBrokersCandlesService service, string market, IBroker broker, Timeframe timeframe, DateTime dateTime, bool updateCandles = false) { var candles = service.GetCandles(broker, market, timeframe, updateCandles); if (candles == null || candles.Count == 0) { return(null); } var index = candles.BinarySearchGetItem( i => candles[i].CloseTimeTicks, 0, dateTime.Ticks, BinarySearchMethod.PrevLowerValueOrValue); if (index != -1) { return(candles[index]); } return(null); }
public List <Trade> Run(StrategyBase strategy, Func <bool> getShouldStopFunc, DateTime?startTime = null, DateTime?endTime = null) { var logIntervalSeconds = 5; var candleTimeframes = strategy.Timeframes; var smallestNonM1Timeframe = candleTimeframes.First(); var candleTimeframesExcSmallest = candleTimeframes.Where(x => x != smallestNonM1Timeframe).ToList(); var m1Candles = _candleService.GetCandles(_broker, _market.Name, Timeframe.M1, false); var allCandles = GetCandles(candleTimeframes); var timeframeCandleIndexes = new TimeframeLookup <int>(); var currentCandles = new TimeframeLookup <List <Candle> >(); var trades = new TradeWithIndexingCollection(); var nextLogTime = DateTime.UtcNow.AddSeconds(logIntervalSeconds); var calls = 0; strategy.SetSimulationParameters(trades, currentCandles, _market); foreach (var tf in candleTimeframes) { currentCandles[tf] = new List <Candle>(10000); } var smallestNonM1TimeframeCount = allCandles[smallestNonM1Timeframe].Count; var m1CandleIndex = 0; Candle smallestNonM1Candle; var startTimeTicks = startTime != null ? (long?)startTime.Value.Ticks : null; var endTimeTicks = endTime != null ? (long?)endTime.Value.Ticks : null; strategy.SetInitialised(); // Ignore M1 candles for (var smallestNonM1TfIndex = 0; smallestNonM1TfIndex < smallestNonM1TimeframeCount; smallestNonM1TfIndex++) { if (getShouldStopFunc != null && getShouldStopFunc()) { return(null); } // Progress smallest non-M1 candle smallestNonM1Candle = allCandles[smallestNonM1Timeframe][smallestNonM1TfIndex]; currentCandles[smallestNonM1Timeframe].Add(smallestNonM1Candle); var newCandleTimeframes = new List <Timeframe> { smallestNonM1Timeframe }; if (DateTime.UtcNow > nextLogTime) { LogProgress(trades, smallestNonM1Candle.CloseTimeTicks); nextLogTime = DateTime.UtcNow.AddSeconds(logIntervalSeconds); } // Progress other timeframes foreach (var tf in candleTimeframesExcSmallest) { for (var i = timeframeCandleIndexes[tf]; i < allCandles[tf].Count; i++) { var c = allCandles[tf][i]; if (c.CloseTimeTicks <= smallestNonM1Candle.CloseTimeTicks) { currentCandles[tf].Add(c); newCandleTimeframes.Add(tf); timeframeCandleIndexes[tf] = i + 1; } else { break; } } } // Process M1 candles if any trades are open or and orders if ((trades.AnyOpen || trades.AnyOrders) && m1CandleIndex < m1Candles.Count) { // Include M1 candles var nextIndex = m1Candles.BinarySearchGetItem( i => m1Candles[i].CloseTimeTicks, m1CandleIndex, smallestNonM1TfIndex > 0 ? allCandles[smallestNonM1Timeframe][smallestNonM1TfIndex - 1].CloseTimeTicks : smallestNonM1Candle.OpenTimeTicks, BinarySearchMethod.NextHigherValue); if (nextIndex != -1 && m1Candles[nextIndex].CloseTimeTicks <= m1Candles[m1CandleIndex].CloseTimeTicks) { throw new ApplicationException("M1 candles are not running in order"); } if (nextIndex != -1 && m1Candles[nextIndex].CloseTimeTicks <= smallestNonM1Candle.CloseTimeTicks) { m1CandleIndex = nextIndex; for (var i = m1CandleIndex; i < m1Candles.Count; i++) { var m1 = m1Candles[i]; if (m1.CloseTimeTicks > smallestNonM1Candle.CloseTimeTicks) { break; } m1CandleIndex = i; if (!trades.AnyOpen && !trades.AnyOrders) { break; } if (trades.AnyOrders) { FillOrders(trades, m1); } if (trades.AnyOpen) { TryCloseOpenTrades(trades, m1); calls++; } } } } // Process new completed candles in strategy try { strategy.UpdateIndicators(newCandleTimeframes); strategy.NewTrades.Clear(); if (startTimeTicks == null || (smallestNonM1Candle.CloseTimeTicks >= startTimeTicks && smallestNonM1Candle.CloseTimeTicks <= endTimeTicks)) { strategy.ProcessCandles(newCandleTimeframes); } } catch (Exception ex) { Log.Error("Error processing new candles", ex); return(null); } RemoveInvalidTrades(strategy.NewTrades, smallestNonM1Candle.CloseBid, smallestNonM1Candle.CloseAsk); // Add new trades foreach (var t in strategy.NewTrades) { if (t.CloseDateTime != null) { trades.AddClosedTrade(t); } else if (t.EntryDateTime == null && t.OrderDateTime != null) { trades.AddOrderTrade(t); } else if (t.EntryDateTime != null) { trades.AddOpenTrade(t); } } } LogProgress(trades, allCandles[smallestNonM1Timeframe][allCandles[smallestNonM1Timeframe].Count - 1].CloseTimeTicks); var ret = trades.AllTrades.Select(x => x.Trade).ToList(); foreach (var t in ret) { TradeCalculator.UpdateInitialStopPips(t); TradeCalculator.UpdateInitialLimitPips(t); TradeCalculator.UpdateRMultiple(t); } return(ret); }
private List <Candle> GetBTCCandles(string symbol, bool updateCandles, DateTime?minOpenTimeUtc = null, DateTime?maxCloseTimeUtc = null) { var candles = _candlesService.GetCandles(_broker, $"{symbol}BTC", Timeframe.M5, updateCandles, minOpenTimeUtc, maxCloseTimeUtc); if (candles != null && candles.Count > 0) { return(candles); } var usdtCandles = _candlesService.GetCandles(_broker, $"{symbol}USDT", Timeframe.M5, updateCandles, minOpenTimeUtc, maxCloseTimeUtc); if (usdtCandles != null && usdtCandles.Count > 0) { var btcUsdtCandles = _candlesService.GetCandles(_broker, "BTCUSDT", Timeframe.M5, updateCandles, minOpenTimeUtc, maxCloseTimeUtc); candles = new List <Candle>(); int btcCandleIndex = 0; foreach (var c in usdtCandles) { btcCandleIndex = btcUsdtCandles.BinarySearchGetItem(i => btcUsdtCandles[i].CloseTimeTicks, btcCandleIndex, c.CloseTimeTicks, BinarySearchMethod.PrevLowerValueOrValue); var btcPrice = btcUsdtCandles[btcCandleIndex].OpenBid; candles.Add(new Candle() { OpenBid = c.OpenBid / btcPrice, CloseBid = c.CloseBid / btcPrice, HighBid = c.HighBid / btcPrice, LowBid = c.LowBid / btcPrice, OpenTimeTicks = c.OpenTimeTicks, CloseTimeTicks = c.CloseTimeTicks }); } } else { var bnbCandles = _candlesService.GetCandles(_broker, $"{symbol}BNB", Timeframe.M5, updateCandles, minOpenTimeUtc, maxCloseTimeUtc); if (bnbCandles != null && bnbCandles.Count > 0) { var bnbUsdtCandles = _candlesService.GetCandles(_broker, "BNBUSDT", Timeframe.M5, updateCandles, minOpenTimeUtc, maxCloseTimeUtc); candles = new List <Candle>(); int bnbCandleIndex = 0; foreach (var c in bnbCandles) { bnbCandleIndex = bnbUsdtCandles.BinarySearchGetItem(i => bnbUsdtCandles[i].CloseTimeTicks, bnbCandleIndex, c.CloseTimeTicks, BinarySearchMethod.PrevLowerValueOrValue); if (bnbCandleIndex != -1) { var btcPrice = bnbUsdtCandles[bnbCandleIndex].OpenBid; candles.Add(new Candle() { OpenBid = c.OpenBid / btcPrice, CloseBid = c.CloseBid / btcPrice, HighBid = c.HighBid / btcPrice, LowBid = c.LowBid / btcPrice, OpenTimeTicks = c.OpenTimeTicks, CloseTimeTicks = c.CloseTimeTicks }); } } } } return(candles); }
public static List <Candle> GetCandlesUptoSpecificTime(this IBrokersCandlesService brokerCandles, IBroker broker, string market, Timeframe timeframe, bool updateCandles, DateTime?startUtc, DateTime?endUtc, Timeframe smallestTimeframeForPartialCandle = Timeframe.M1) { var allLargeChartCandles = brokerCandles.GetCandles(broker, market, timeframe, updateCandles, cacheData: false, minOpenTimeUtc: startUtc, maxCloseTimeUtc: endUtc); var smallestTimeframeCandles = brokerCandles.GetCandles(broker, market, smallestTimeframeForPartialCandle, updateCandles, cacheData: false, maxCloseTimeUtc: endUtc); var largeChartCandles = new List <Candle>(); var endTicks = endUtc?.Ticks ?? -1; var endTimeTicks = endUtc?.Ticks; // Add complete candle for (var i = 0; i < allLargeChartCandles.Count; i++) { var currentCandle = allLargeChartCandles[i]; if (endTimeTicks == null || currentCandle.CloseTimeTicks <= endTimeTicks) { largeChartCandles.Add(currentCandle); } } // Add incomplete candle var latestCandleTimeTicks = largeChartCandles[largeChartCandles.Count - 1].CloseTimeTicks; float?openBid = null, closeBid = null, highBid = null, lowBid = null; float?openAsk = null, closeAsk = null, highAsk = null, lowAsk = null; long? openTimeTicks = null, closeTimeTicks = null; foreach (var smallestTimeframeCandle in smallestTimeframeCandles) { if (smallestTimeframeCandle.OpenTimeTicks >= latestCandleTimeTicks && (smallestTimeframeCandle.CloseTimeTicks <= endTicks || endTicks == -1)) { if (openTimeTicks == null) { openTimeTicks = smallestTimeframeCandle.OpenTimeTicks; } if (openBid == null || smallestTimeframeCandle.OpenBid < openBid) { openBid = smallestTimeframeCandle.OpenBid; } if (highBid == null || smallestTimeframeCandle.HighBid > highBid) { highBid = smallestTimeframeCandle.HighBid; } if (lowBid == null || smallestTimeframeCandle.LowBid < lowBid) { lowBid = smallestTimeframeCandle.LowBid; } closeBid = smallestTimeframeCandle.CloseBid; if (openAsk == null || smallestTimeframeCandle.OpenAsk < openAsk) { openAsk = smallestTimeframeCandle.OpenAsk; } if (highAsk == null || smallestTimeframeCandle.HighAsk > highAsk) { highAsk = smallestTimeframeCandle.HighAsk; } if (lowAsk == null || smallestTimeframeCandle.LowAsk < lowAsk) { lowAsk = smallestTimeframeCandle.LowAsk; } closeAsk = smallestTimeframeCandle.CloseAsk; closeTimeTicks = smallestTimeframeCandle.CloseTimeTicks; } if (smallestTimeframeCandle.CloseTime() > endUtc) { break; } } if (openBid != null) { largeChartCandles.Add(new Candle { OpenBid = openBid.Value, CloseBid = closeBid.Value, HighBid = highBid.Value, LowBid = lowBid.Value, OpenAsk = openAsk.Value, CloseAsk = closeAsk.Value, HighAsk = highAsk.Value, LowAsk = lowAsk.Value, CloseTimeTicks = closeTimeTicks.Value, OpenTimeTicks = openTimeTicks.Value, IsComplete = 0 }); } return(largeChartCandles); }
public static void UpdateRMultiple(Trade trade) { if (trade.RiskAmount != null && trade.RiskAmount.Value != 0M && trade.Profit != null) { trade.RMultiple = trade.Profit / trade.RiskAmount; } else if (trade.EntryPrice != null && trade.EntryDateTime != null && trade.ClosePrice != null && trade.StopPrices.Count > 0) { // Get stop price at entry DatePrice entryStop = null; foreach (var stop in trade.StopPrices) { if (entryStop == null || stop.Date <= trade.EntryDateTime.Value) { entryStop = stop; } else { break; } } if (entryStop?.Price != null) { var oneR = Math.Abs(trade.EntryPrice.Value - entryStop.Price.Value); if (trade.TradeDirection == TradeDirection.Long) { trade.RMultiple = oneR != 0 ? (decimal?)(trade.ClosePrice.Value - trade.EntryPrice.Value) / oneR : null; } else if (oneR != 0) { trade.RMultiple = trade.EntryPrice.Value != trade.ClosePrice.Value ? (trade.EntryPrice.Value - trade.ClosePrice.Value) / oneR : 0; } else { trade.RMultiple = null; } } } else if (trade.EntryPrice != null && trade.EntryDateTime != null && trade.ClosePrice == null && trade.InitialStop != null && trade.CalculateOptions.HasFlag(CalculateOptions.IncludeOpenTradesInRMultipleCalculation)) { var stopPrice = trade.InitialStop.Value; var risk = Math.Abs(stopPrice - trade.EntryPrice.Value); var currentCandle = _candlesService.GetCandles(_brokersService.GetBroker(trade.Broker), trade.Market, Timeframe.D1, false, cacheData: false).Last(); var currentClose = trade.TradeDirection == TradeDirection.Long ? (decimal)currentCandle.CloseBid : (decimal)currentCandle.CloseAsk; // Get stop price at entry DatePrice entryStop = null; foreach (var stop in trade.StopPrices) { if (entryStop == null || stop.Date <= trade.EntryDateTime.Value) { entryStop = stop; } else { break; } } if (entryStop?.Price != null) { var oneR = Math.Abs(trade.EntryPrice.Value - entryStop.Price.Value); if (trade.TradeDirection == TradeDirection.Long) { trade.RMultiple = (currentClose - trade.EntryPrice.Value) / oneR; } else { trade.RMultiple = (trade.EntryPrice.Value - currentClose) / oneR; } } } else { trade.RMultiple = null; } }
private void RunLive(string selectedStrategyFilename) { var trades = new TradeWithIndexingCollection(); var strategyLookup = new Dictionary <string, StrategyBase>(); var candlesLookup = new Dictionary <string, TimeframeLookup <List <Candle> > >(); var accountSaveIntervalSeconds = 60; var accountLastSaveTime = DateTime.UtcNow; Log.Info("Running live"); // Get strategy type and markets var strategyType = CompileStrategyAndGetStrategyMarkets(selectedStrategyFilename, out var markets, out var timeframes); if (strategyType == null) { return; } // Update broker account Log.Info("Updating broker account"); _brokerAccount.UpdateBrokerAccount(_fxcm, _candlesService, _marketDetailsService, _tradeDetailsAutoCalculatorService, UpdateOption.ForceUpdate); // Get candles Log.Info("Getting candles"); foreach (var m in markets) { candlesLookup[m] = new TimeframeLookup <List <Candle> >(); foreach (var t in timeframes) { candlesLookup[m].Add(t, _candlesService.GetCandles(_fxcm, m, t, true, forceUpdate: true, cacheData: true)); } } // Setup locks foreach (var market in markets) { _marketLock[market] = new object(); } // Create strategies Log.Info("Setting up strategies"); foreach (var market in markets) { var strategy = (StrategyBase)Activator.CreateInstance(strategyType); var currentCandles = new TimeframeLookup <List <Candle> >(); strategy.SetSimulationParameters(trades, currentCandles, _marketDetailsService.GetMarketDetails("FXCM", market)); strategyLookup.Add(market, strategy); // Setup candles for strategy foreach (var t in timeframes) { currentCandles.Add(t, candlesLookup[market][t].Where(c => c.IsComplete == 1).ToList()); } } // Get live prices steams var priceMonitor = new MonitorLivePrices(_fxcm, p => ProcessNewPrice(markets, timeframes, p, candlesLookup)); try { var checkFxcmConnectedIntervalSeconds = 60 * 5; var nextFxcmConnectedCheckTime = DateTime.UtcNow.AddSeconds(checkFxcmConnectedIntervalSeconds); Log.Info("Running main processing loop"); while (true) { if (accountLastSaveTime < DateTime.UtcNow.AddSeconds(-accountSaveIntervalSeconds)) { lock (_brokerAccount) { Log.Debug("Saving broker account"); _brokerAccount.SaveAccount( DataDirectoryService.GetMainDirectoryWithApplicationName("TradeLog")); } accountLastSaveTime = DateTime.UtcNow; } // Re-connect if connect is lost if (DateTime.UtcNow >= nextFxcmConnectedCheckTime) { nextFxcmConnectedCheckTime = DateTime.UtcNow.AddSeconds(checkFxcmConnectedIntervalSeconds); if (_fxcm.Status == ConnectStatus.Disconnected) { Log.Warn("FXCM has disconnected - reconnecting"); try { priceMonitor?.Dispose(); } catch (Exception ex) { Log.Error("Unable to dispose price monitor", ex); } priceMonitor = null; _fxcm.Connect(); if (_fxcm.Status == ConnectStatus.Connected) { Log.Warn($"FXCM has reconnected"); priceMonitor = new MonitorLivePrices(_fxcm, p => ProcessNewPrice(markets, timeframes, p, candlesLookup)); } else { Log.Warn($"FXCM hasn't re-connected - new status is: {_fxcm.Status}"); } } } foreach (var strategy in strategyLookup.Values) { var newTimeframeCandles = new List <Timeframe>(); // Check if there is any new complete candles foreach (var t in strategy.Timeframes) { lock (candlesLookup[strategy.Market.Name][t]) { if (strategy.Candles[t].Count != candlesLookup[strategy.Market.Name][t].Count(c => c.IsComplete == 1)) { newTimeframeCandles.Add(t); strategy.Candles[t].Clear(); strategy.Candles[t].AddRange(candlesLookup[strategy.Market.Name][t] .Where(c => c.IsComplete == 1).ToList()); } } } if (newTimeframeCandles.Any()) // TODO reduce times this is called and include save { // Update broker account lock (_brokerAccount) { Log.Debug("Updating and saving broker account"); _brokerAccount.UpdateBrokerAccount(_fxcm, _candlesService, _marketDetailsService, _tradeDetailsAutoCalculatorService, UpdateOption.ForceUpdate); } var s = strategy; Task.Run(() => { if (Monitor.TryEnter(_marketLock[s.Market.Name])) { try { Log.Info($"Found new candles for market: {s.Market.Name}"); // Update indicators and do trades maintenance s.UpdateIndicators(newTimeframeCandles); s.NewTrades.Clear(); s.Trades.MoveTrades(); var beforeStopLossLookup = s.Trades.OpenTrades.ToDictionary(x => x.Trade.Id, x => x.Trade.StopPrice); // Process strategy s.ProcessCandles(newTimeframeCandles); // Create any new trades CreateNewFXCMTradesAndUpdateAccount(s); if (trades.OpenTrades.Count() > 5) { Log.Error("Too many trades"); } // Update any stops UpdateFXCMOpenTradesStops(s, beforeStopLossLookup); } finally { Monitor.Exit(_marketLock[s.Market.Name]); } } }); } } Thread.Sleep(100); } } finally { priceMonitor.Dispose(); } }