public int BarsSave(Bars bars) { int ret = 0; DateTime start = DateTime.Now; Storage db = StorageFactory.Instance.CreateStorage(); db.Open(this.Abspath, pagePoolSize); BarsPerst barsPerst = db.Root as BarsPerst; barsPerst = (BarsPerst) db.CreateClass(typeof(BarsPerst)); barsPerst.Symbol = bars.Symbol; barsPerst.SecurityName = bars.SecurityName; barsPerst.ScaleInterval = bars.ScaleInterval; /// If number of element in block is 100, time series period is 1 day, then /// value of maxBlockTimeInterval can be set as 100*(24*60*60*10000000L)*2 //long maxBlockTimeInterval = (number of elements in block)*(tick interval)*2; TICKS_PER_SECOND = bars.ScaleInterval.TimeSpanInSeconds; barsPerst.BarsStored = db.CreateTimeSeries<BarPerst>(N_ELEMS_PER_BLOCK, N_ELEMS_PER_BLOCK*TICKS_PER_SECOND*2); foreach (Bar bar in bars.Values) { BarPerst barPerst = new BarPerst(bar); barsPerst.BarsStored.Add(barPerst); } ret = barsPerst.BarsStored.Count; db.Root = barsPerst; db.Commit(); db.Close(); string msg = "Elapsed time for storing " + ret + " bars: " + (DateTime.Now - start); return ret; }
public ChartControl() { this.ChartSettings = new ChartSettings(); // was a component, used at InitializeComponent() (to draw SampleBars) this.ScriptExecutorObjects = new ScriptExecutorObjects(); InitializeComponent(); //if (this.ChartSettings == null) this.AutoScroll = false; //this.HScroll = true; this.hScrollBar.SmallChange = this.ChartSettings.ScrollNBarsPerOneKeyPress; panelsFolding = new List<PanelNamedFolding>(); panelsFolding.Add(this.panelPrice); panelsFolding.Add(this.panelVolume); this.panelPrice.Initialize(this); this.panelVolume.Initialize(this); if (base.DesignMode == false) return; //this.chartRenderer.Initialize(this); BarScaleInterval chartShouldntCare = new BarScaleInterval(BarScale.Minute, 5); //REFLECTION_FAILS_FOR_DESIGNER BarsBasic.GenerateRandom(chartShouldntCare) //this.Initialize(BarsBasic.GenerateRandom(chartShouldntCare)); Bars generated = new Bars("RANDOM", chartShouldntCare, "test-ChartControl-DesignMode"); generated.GenerateAppend(); this.Initialize(generated); }
public Bars CloneNoBars(string reasonToExist = null, BarScaleInterval scaleIntervalConvertingTo = null) { if (scaleIntervalConvertingTo == null) scaleIntervalConvertingTo = this.ScaleInterval; if (string.IsNullOrEmpty(reasonToExist)) reasonToExist = "InitializedFrom(" + this.ReasonToExist + ")"; Bars ret = new Bars(this.Symbol, scaleIntervalConvertingTo, reasonToExist); ret.SymbolHumanReadable = this.SymbolHumanReadable; ret.MarketInfo = this.MarketInfo; ret.SymbolInfo = this.SymbolInfo; ret.DataSource = this.DataSource; return ret; }
public BacktestDataSource(Bars bars) { base.Name = "BacktestDataSource"; // base.DataSourceManager = bars.DataSource.DataSourceManager; base.MarketInfo = bars.MarketInfo; base.ScaleInterval = bars.ScaleInterval; base.Symbols.Add(bars.Symbol); base.StreamingProvider = new BacktestStreamingProvider(bars.Symbol); base.StreamingProvider.InitializeFromDataSource(this); base.BrokerProvider = new BacktestBrokerProvider(); //base.BrokerProvider.Initialize(this, base.StreamingProvider, null, base.StatusReporter); }
public Bars BarsRead() { Bars ret = null; DateTime start = DateTime.Now; Storage db = StorageFactory.Instance.CreateStorage(); db.Open(this.Abspath, pagePoolSize); BarsPerst barsPerst = db.Root as BarsPerst; if (barsPerst == null) return ret; ret = new Bars(barsPerst.Symbol, barsPerst.ScaleInterval, this.Abspath); foreach (BarPerst barPerst in barsPerst.BarsStored) { ret.BarCreateAppend(new DateTime(barPerst.Time), barPerst.Open, barPerst.High, barPerst.Low, barPerst.Close, barPerst.Volume); } db.Close(); string msg = "Elapsed time for reading " + ret.Count + " bars: " + (DateTime.Now - start); return ret; }
public Bars BarsLoadThreadSafe(DateTime startDate, DateTime endDate, int maxBars) { Bars barsAll = this.BarsLoadAllThreadSafe(); //Assembler.PopupException("Loaded [ " + bars.Count + "] bars; symbol[" + this.Symbol + "] scaleInterval[" + this.BarsFolder.ScaleInterval + "]"); if (startDate == DateTime.MinValue && endDate == DateTime.MaxValue && maxBars == 0) return barsAll; string start = (startDate == DateTime.MinValue) ? "MIN" : startDate.ToString("dd-MMM-yyyy"); string end = (endDate == DateTime.MaxValue) ? "MAX" : endDate.ToString("dd-MMM-yyyy"); Bars bars = new Bars(barsAll.Symbol, barsAll.ScaleInterval, barsAll.ReasonToExist + " [" + start + "..." + end + "]max[" + maxBars + "]"); for (int i=0; i<barsAll.Count; i++) { if (maxBars > 0 && i >= maxBars) break; Bar barAdding = barsAll[i]; bool skipThisBar = false; if (startDate > DateTime.MinValue && barAdding.DateTimeOpen < startDate) skipThisBar = true; if (endDate < DateTime.MaxValue && barAdding.DateTimeOpen > endDate) skipThisBar = true; if (skipThisBar) continue; bars.BarAppendBindStatic(barAdding.CloneDetached()); } return bars; }
public void Initialize(Bars barsNotNull) { this.barEventsDetach(); this.Bars = barsNotNull; if (this.BarsNotEmpty) { this.barEventsAttach(); this.SyncHorizontalScrollToBarsCount(); //this.hScrollBar.ValueCurrent = this.hScrollBar.Maximum; // I just sync'ed this.hScrollBar.Maximum = this.Bars.Count - 1 // after I reduced BarRange{500bars => 100bars} in MainForm, don't set this.hScrollBar.Value here, I'll invoke ScrollToLastBarRight() upstack if (this.ChartSettings.ScrollPositionAtBarIndex >= this.hScrollBar.Minimum && this.ChartSettings.ScrollPositionAtBarIndex <= this.hScrollBar.Maximum) { // I'm here 1) at ChartControl startup; 2) after I changed BarRange in MainForm this.hScrollBar.Value = this.ChartSettings.ScrollPositionAtBarIndex; } else { string msg = "HSCROLL_POSITION_VALUE_OUT_OF_RANGE; fix deserialization upstack"; } foreach (PanelNamedFolding panelFolding in this.panelsFolding) { // at least PanelPrice and PanelVolume panelFolding.InitializeWithNonEmptyBars(this); } } this.InvalidateAllPanelsFolding(); }
public Bars BarsLoadAll() { string msig = " BarsLoadAll(this.Abspath=[" + this.Abspath + "]): "; Bars bars = null; DateTime dateTime = DateTime.Now; FileStream fileStream = null; try { fileStream = File.Open(this.Abspath, FileMode.Open, FileAccess.Read, FileShare.Read); BinaryReader binaryReader = new BinaryReader(fileStream); double version = binaryReader.ReadDouble(); //Assembler.PopupException("LoadBars[" + this.Relpath + "]: version[" + version + "]"); string symbol = binaryReader.ReadString(); string symbolHumanReadable = binaryReader.ReadString(); BarScale barScale = (BarScale)binaryReader.ReadInt32(); int barInterval = binaryReader.ReadInt32(); BarScaleInterval scaleInterval = new BarScaleInterval(barScale, barInterval); //string shortFnameIneedMorePathParts = Path.GetFileName(this.Abspath); //string shortFname = this.Abspath.Substring(this.Abspath.IndexOf("" + Path.DirectorySeparatorChar + "Data" + Path.DirectorySeparatorChar + "") + 6); string shortFname = this.Relpath; bars = new Bars(symbol, scaleInterval, shortFname); int barsStored = binaryReader.ReadInt32(); //int securityType = binaryReader.ReadInt32(); //bars.SymbolInfo.SecurityType = (SecurityType)securityType; for (int barsRead = 0; barsRead<barsStored; barsRead++) { DateTime dateTimeOpen = new DateTime(binaryReader.ReadInt64()); double open = binaryReader.ReadDouble(); double high = binaryReader.ReadDouble(); double low = binaryReader.ReadDouble(); double close = binaryReader.ReadDouble(); double volume = binaryReader.ReadDouble(); Bar barAdded = bars.BarCreateAppendBindStatic(dateTimeOpen, open, high, low, close, volume); } } catch (EndOfStreamException ex) { Assembler.PopupException(ex.Message + msig, ex); } finally { if (fileStream != null) fileStream.Close(); } return bars; }
public void SetBars(Bars barsClicked) { if (barsClicked == null) { string msg = "don't feed Bars=null into the foodchain!"; throw new Exception(msg); } if (this.Backtester.IsBacktestingNow) { this.Backtester.AbortRunningBacktestWaitAborted("CLICKED_ON_OTHER_BARS_WHILE_BACKTESTING"); } this.Bars = barsClicked; }
internal void BacktestContextRestore() { this.Bars = this.preBacktestBars; //this.DataSource = this.preDataSource; this.preBacktestBars = null; // will help ignore this.IsStreaming saving IsStreaming state to json this.IsStreaming = preBacktestIsStreaming; }
internal void BacktestContextInitialize(Bars bars) { this.preBacktestBars = this.Bars; // this.preBacktestBars != null will help ignore this.IsStreaming saving IsStreaming state to json this.preDataSource = this.DataSource; this.preBacktestIsStreaming = this.IsStreaming; this.Bars = bars; //this.DataSource = bars.DataSource; this.IsStreaming = true; //this.Strategy.ScriptBase.Initialize(this); }
public double OrderCommissionCalculate(Direction direction, MarketLimitStop marketLimitStop, double price, double shares, Bars bars) { double ret = 0; if (this.Strategy.ScriptContextCurrent.ApplyCommission && this.CommissionCalculator != null) { ret = this.CommissionCalculator.CalculateCommission(direction, marketLimitStop, price, shares, bars); } return ret; }
int BarsSave(Bars bars) { int barsSaved = 0; FileStream fileStream = null; try { fileStream = File.Create(this.Abspath); BinaryWriter binaryWriter = new BinaryWriter(fileStream); binaryWriter.Write(this.version); binaryWriter.Write(bars.Symbol); binaryWriter.Write(bars.SymbolHumanReadable); binaryWriter.Write((int)bars.ScaleInterval.Scale); binaryWriter.Write(bars.ScaleInterval.Interval); binaryWriter.Write(bars.Count); for (int i = 0; i < bars.Count; i++) { Bar bar = bars[i]; binaryWriter.Write(bar.DateTimeOpen.Ticks); binaryWriter.Write(bar.Open); binaryWriter.Write(bar.High); binaryWriter.Write(bar.Low); binaryWriter.Write(bar.Close); binaryWriter.Write(bar.Volume); barsSaved++; } } catch (Exception ex) { string msg = "Error while Saving bars[" + this + "] into [" + this.Abspath + "]"; Assembler.PopupException(msg, ex); } finally { if (fileStream != null) fileStream.Close(); } return barsSaved; }
public void SetParentForBackwardUpdate(Bars parentBars, int parentBarsIndex) { if (this.ParentBars == parentBars) { string msg = "TYRING_AVOID_BUGS: same ParentBars as I have already;" + " this.ParentBars==parentBars[" + parentBars + "]"; throw new Exception(msg); } if (this.ParentBarsIndex == parentBarsIndex) { string msg = "TYRING_AVOID_BUGS: same ParentBarsIndex[" + this.ParentBarsIndex + "] as I have already;" + " this.ParentBars==parentBars[" + parentBars + "]"; throw new Exception(msg); } if (this.ParentBars != null) { if (this.ParentBars.Symbol != parentBars.Symbol) { string msg1 = "here is the problem for a streaming bar to carry another symbol!"; throw new Exception(msg1); } string msg = "this.ParentBars!=null => this Bar is already assigned to Bars;" + " use Bar.CloneDetached() if you add this Bar to another BarSeries" + " otherwise reciprocity will be uneven" + " and strategies relying on quote.ParentBar.ParentBarsIndex will be messed up"; throw new Exception(msg); } if (this.ParentBarsIndex != -1) { string msg = "this.ParentBarsIndex!=-1 => this Bar is already assigned to Bars;" + " use Bar.CloneDetached() if you add this Bar to another BarSeries" + " otherwise reciprocity will be uneven" + " and strategies relying on quote.ParentBar.ParentBarsIndex will be messed up"; throw new Exception(msg); } this.ParentBars = parentBars; this.ParentBarsIndex = parentBarsIndex; }
public virtual Bars RequestDataFromRepository(string symbol) { Bars ret; symbol = symbol.ToUpper(); //BarsFolder perstFolder = new BarsFolder(this.BarsFolder.RootFolder, this.DataSource.ScaleInterval, true, "dts"); //RepositoryBarsPerst barsPerst = new RepositoryBarsPerst(perstFolder, symbol, false); //ret = barsPerst.BarsRead(); //if (ret == null) { RepositoryBarsFile barsFile = this.BarsRepository.DataFileForSymbol(symbol); ret = barsFile.BarsLoadAllThreadSafe(); //} if (ret == null) ret = new Bars(symbol, this.ScaleInterval, "FILE_NOT_FOUND " + this.GetType().Name); return ret; }
public bool IsProxyFor(Bars bars, DataSeriesProxyableFromBars dataSeriesProxyableFromBars) { return(barsBeingProxied == bars && dataSeriesBeingExposed == dataSeriesProxyableFromBars); }
public void InitializeStreamingOHLCVfromStreamingProvider(Bars chartBars) { SymbolScaleDistributionChannel distributionChannel = this.DataDistributor .GetDistributionChannelFor(chartBars.Symbol, chartBars.ScaleInterval); Bar streamingBar = distributionChannel.StreamingBarFactoryUnattached.StreamingBarUnattached; if (streamingBar == null) { string msg = "STREAMING_NEVER_STARTED BarFactory.StreamingBar=null for distributionChannel[" + distributionChannel + "]"; Assembler.PopupException(msg); throw new Exception(msg); } if (streamingBar.DateTimeOpen == DateTime.MinValue) { string msg = "STREAMING_NEVER_STARTED streamingBar.DateTimeOpen=MinValue [" + streamingBar + "] for distributionChannel[" + distributionChannel + "]"; Assembler.PopupException(msg); throw new Exception(msg); } if (streamingBar.DateTimeOpen != chartBars.BarStaticLast.DateTimeNextBarOpenUnconditional) { if (streamingBar.DateTimeOpen == chartBars.BarStaticLast.DateTimeOpen) { string msg = "STREAMINGBAR_OVERWROTE_LASTBAR streamingBar.DateTimeOpen[" + streamingBar.DateTimeOpen + "] == this.LastStaticBar.DateTimeOpen[" + chartBars.BarStaticLast.DateTimeOpen + "] " + chartBars; //log.Error(msg); } else { string msg = "STREAMINGBAR_OUTDATED streamingBar.DateTimeOpen[" + streamingBar.DateTimeOpen + "] != chartBars.LastStaticBar.DateTimeNextBarOpenUnconditional[" + chartBars.BarStaticLast.DateTimeNextBarOpenUnconditional + "] " + chartBars; //log.Error(msg); } } chartBars.OverrideStreamingDOHLCVwith(streamingBar); Assembler.PopupException("StreamingOHLCV Overwritten: Bars.StreamingBar[" + chartBars.BarStreamingCloneReadonly + "] taken from streamingBar[" + streamingBar + "]"); }
public int BarsSaveThreadSafe(Bars bars) { if (bars.Count == 0) return 0; int barsSaved = -1; lock (this) { barsSaved = BarsSave(bars); //Assembler.PopupException("Saved [ " + bars.Count + "] bars; symbol[" + bars.Symbol + "] scaleInterval[" + bars.ScaleInterval + "]"); } return barsSaved; }
public int BarAppend(Bar barLastFormed) { Bars allBars = this.BarsLoadAllThreadSafe(); if (allBars == null) { allBars = new Bars(barLastFormed.Symbol, barLastFormed.ScaleInterval, "DUMMY: LoadBars()=null"); } //allBars.DumpPartialInitFromStreamingBar(bar); // this happens on a very first quote - this.pushBarToConsumers(StreamingBarFactory.LastBarFormed.Clone()); if (allBars.BarStaticLast.DateTimeOpen == barLastFormed.DateTimeOpen) return 0; // not really needed to clone to save it in a file, but we became strict to eliminate other bugs barLastFormed = barLastFormed.CloneDetached(); // SetParentForBackwardUpdateAutoindex used within Bar only() //barLastFormed.SetParentForBackwardUpdateAutoindex(allBars); if (allBars.BarStaticLast.DateTimeOpen == barLastFormed.DateTimeOpen) { return 0; } allBars.BarAppendBindStatic(barLastFormed); int barsSaved = this.BarsSaveThreadSafe(allBars); return barsSaved; }
// bool handlingVolume = indicator.Series.TypeSafeIsProxyFor(this.Executor.Bars, DataSeriesProxyableFromBars.Volume) public virtual bool TypeSafeIsProxyFor(Bars bars, DataSeriesProxyableFromBars barField) { if (this is DataSeriesProxyBars == false) return false; DataSeriesProxyBars proxy = this as DataSeriesProxyBars; if (proxy.IsProxyFor(bars, barField)) return true; return false; }
public bool IsProxyFor(Bars bars, DataSeriesProxyableFromBars dataSeriesProxyableFromBars) { return barsBeingProxied == bars && dataSeriesBeingExposed == dataSeriesProxyableFromBars; }
public int BarsSave(Bars bars) { RepositoryBarsFile barsFile = this.BarsRepository.DataFileForSymbol(bars.Symbol, false); int barsSaved = barsFile.BarsSaveThreadSafe(bars); string msg = "Saved [ " + barsSaved + "] bars; static[" + this.Name + "]"; //BarsFolder perstFolder = new BarsFolder(this.BarsFolder.RootFolder, bars.ScaleInterval, true, "dts"); //RepositoryBarsPerst barsPerst = new RepositoryBarsPerst(perstFolder, bars.Symbol, false); //int barsSavedPerst = barsPerst.BarsSave(bars); //string msgPerst = "Saved [ " + barsSavedPerst + "] bars; static[" + this.Name + "]"; return barsSaved; }
public override double CalculateCommission(Direction direction, MarketLimitStop marketLimitStop, double orderPrice, double shares, Bars bars) { return 0; }
protected Position(Bars bars, PositionLongShort positionLongShort, string strategyID, double basisPrice, double shares) : this() { this.Bars = bars; this.PositionLongShort = positionLongShort; this.StrategyID = strategyID; this.LastQuoteForMarketOrStopLimitImplicitPrice = basisPrice; this.Shares = shares; }
public double AlignAlertPriceToPriceLevel(Bars bars, double orderPrice, bool buyOrShort, PositionLongShort positionLongShort0, MarketLimitStop marketLimitStop0) { if (this.Strategy.ScriptContextCurrent.NoDecimalRoundingForLimitStopPrice) return orderPrice; if (bars == null) bars = this.Bars; if (bars.SymbolInfo.PriceLevelSizeForBonds == 0.0) { string text = "1"; if (this.Strategy.ScriptContextCurrent.PriceLevelSizeForBonds > 0) { text = text.PadRight(this.Strategy.ScriptContextCurrent.PriceLevelSizeForBonds + 1, '0'); bars.SymbolInfo.PriceLevelSizeForBonds = 1.0 / (double)Convert.ToInt32(text); } return orderPrice; } orderPrice = bars.SymbolInfo.RoundAlertPriceToPriceLevel(orderPrice, buyOrShort, positionLongShort0, marketLimitStop0); return orderPrice; }
void step4createBarsDisplayChart() { string status = " Symbol[" + this.symbolDetectedInCsv + "]"; BarScaleInterval csvScaleInterval = this.scaleIntervalDetectedInCsv; if (csvScaleInterval == null) { this.scaleIntervalMinimalScanned = this.findMinimalInterval(this.csvParsedByFormat); status += " ScaleIntervalMinFound[" + csvScaleInterval + "]"; } else { status += " ScaleIntervalFromColumn[" + csvScaleInterval + "]"; } string statusOld = this.grpPreviewParsedByFormat.Text; int posToAppend = statusOld.IndexOf('|'); //if (posToAppend < 0) posToAppend = 0; string prefix = statusOld.Substring(0, posToAppend+1); // no exception thrown even if posToAppend=-1 (not found) this.grpPreviewParsedByFormat.Text = prefix + status; this.BarsParsed = new Bars(this.symbolDetectedInCsv, csvScaleInterval, this.dataSnapshot.FileSelectedAbsname); //BarsUnscaled barsParsed = new BarsUnscaled(symbol, this.dataSnapshot.FileSelectedAbsname); foreach (CsvBar csvBar in this.csvParsedByFormat) { this.BarsParsed.BarCreateAppendBindStatic(csvBar.DateTime, csvBar.Open, csvBar.High, csvBar.Low, csvBar.Close, csvBar.Volume); } //status += " BarsParsed.Count[" + this.BarsParsed.Count + "]"; status += " Bars[" + this.BarsParsed.Count + "]"; this.grpPreviewParsedByFormat.Text = prefix + status; this.rangeBarDateTime1.Enabled = true; this.rangeBarDateTime1.Initialize(this.BarsParsed); this.syncImportButton(); }
public BarsEventArgs(Bars bars) { this.Bars = bars; }
public StreamingBarFactory(Bars bars) { Bars = bars; StreamingBarUnattached = this.Bars.; IntraBarSerno = 0; }
public Alert(Bar bar, double qty, double priceScript, string signalName, Direction direction, MarketLimitStop marketLimitStop, OrderSpreadSide orderSpreadSide, Strategy strategy) : this() { if (direction == Direction.Unknown) { string msg = "ALERT_CTOR_DIRECTION_MUST_NOT_BE_UNKNOWN: when creating an Alert, direction parameter can't be null"; throw new Exception(msg); } if (bar == null) { string msg = "ALERT_CTOR_BAR_MUST_NOT_BE_NULL: when creating an Alert, bar parameter can't be null"; throw new Exception(msg); } if (bar.ParentBars == null) { string msg = "ALERT_CTOR_PARENT_BARS_MUST_NOT_BE_NULL: when creating an Alert, bar.ParentBars can't be null"; throw new Exception(msg); } this.Bars = bar.ParentBars; this.PlacedBar = bar; this.PlacedBarIndex = bar.ParentBarsIndex; this.Symbol = bar.Symbol; this.BarsScaleInterval = this.Bars.ScaleInterval; if (this.Bars.SymbolInfo != null) { SymbolInfo symbolInfo = this.Bars.SymbolInfo; this.SymbolClass = (string.IsNullOrEmpty(symbolInfo.SymbolClass) == false) ? symbolInfo.SymbolClass : "UNKNOWN_CLASS"; this.MarketOrderAs = symbolInfo.MarketOrderAs; } this.AccountNumber = "UNKNOWN_ACCOUNT"; if (this.DataSource.BrokerProvider != null && this.DataSource.BrokerProvider.AccountAutoPropagate != null && string.IsNullOrEmpty(this.Bars.DataSource.BrokerProvider.AccountAutoPropagate.AccountNumber) != false) { this.AccountNumber = this.Bars.DataSource.BrokerProvider.AccountAutoPropagate.AccountNumber; } this.Qty = qty; this.PriceScript = priceScript; this.SignalName = signalName; this.Direction = direction; this.MarketLimitStop = marketLimitStop; this.OrderSpreadSide = orderSpreadSide; this.Strategy = strategy; if (this.Strategy != null) { this.StrategyID = this.Strategy.Guid; this.StrategyName = this.Strategy.Name; } if (this.Strategy.Script != null) { string msg = "Looks like a manual Order submitted from the Chart"; return; } }
public DataSeriesProxyBars(Bars bars, DataSeriesProxyableFromBars dataSeriesBeingExposed) { base.ScaleInterval = bars.ScaleInterval; this.barsBeingProxied = bars; this.dataSeriesBeingExposed = dataSeriesBeingExposed; base.Description = this.dataSeriesBeingExposed + " for " + this.ToString(); }
public static Bars GenerateRandom(BarScaleInterval scaleInt, int howManyBars = 10, string symbol = "SAMPLE", string reasonToExist = "test-ChartControl-DesignMode") { Bars ret = new Bars(symbol, scaleInt, reasonToExist); ret.GenerateAppend(howManyBars); return ret; }