public void Convert(ConvertDataType viewType) { List <int> keys15; keys15 = new List <int>(origpricedata.Keys); pricedata = new Dictionary <int, PriceCandle>(); DateTime dt; PriceCandle lastprice15 = new PriceCandle(); ConvertData cdata = new ConvertData(viewType); for (int n = 0; n < keys15.Count; n++) { if (n == 486) { } dt = Helper.UnixToDateTime(keys15[n].ToString()); cdata.prevclose = lastprice15.close; lastprice15 = origpricedata[keys15[n]]; cdata.SetCurBar(dt, lastprice15); cdata.ProcessBar(pricedata); } cdata.prevclose = lastprice15.close; cdata.AddPrice(pricedata); }
void PrepareChart() { maxprice = 0; minprice = double.MaxValue; Dictionary <int, PriceCandle> pricedata = data.GetData(); List <int> keys = data.GetKeys(); for (int n = starttick; n <= lasttick; n++) { int key1 = keys[n]; // int date1 = key1; PriceCandle price1 = pricedata[key1]; if (price1.open > maxprice) { maxprice = price1.open; } if (price1.high > maxprice) { maxprice = price1.high; } if (price1.low > maxprice) { maxprice = price1.low; } if (price1.close > maxprice) { maxprice = price1.close; } if (price1.open < minprice) { minprice = price1.open; } if (price1.high < minprice) { minprice = price1.high; } if (price1.low < minprice) { minprice = price1.low; } if (price1.close < minprice) { minprice = price1.close; } } yzoomcoeff = (float)(maxprice - minprice) / drawh; data.NormalizeData(minprice, yzoomcoeff); }
public void CreatePrice() { created = true; switch (datatype) { case ConvertDataType.BAR_15: lastperiod = HourPeriodNumber(dt, 15); lastdate = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, lastperiod * 15, 0); break; case ConvertDataType.BAR_HOUR: lastperiod = dt.Hour; lastdate = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0); break; case ConvertDataType.BAR_DAY: lastperiod = dt.Day; lastdate = new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0); break; case ConvertDataType.BAR_WEEK: lastperiod = Helper.WeekNumber(dt); lastdate = Helper.WeekDate(dt.Year, lastperiod).AddDays(-1); /* * DateTime d1 = new DateTime(2017, 1, 1).AddDays(34 * 7); * DateTime d2 = new DateTime(2017, 1, 1).AddDays(52 * 7); * * lastperiod = Helper.WeekNumber(new DateTime(2017, 1, 15)); * DateTime d3 = new DateTime(2017, 1, 1).AddDays(lastperiod * 7-1); */ break; case ConvertDataType.BAR_MONTH: lastperiod = dt.Month; lastdate = new DateTime(dt.Year, dt.Month, 1, 0, 0, 0); break; } lastprice = new PriceCandle(); lastprice.open = lastprice15.open; lastprice.high = lastprice15.high; lastprice.low = lastprice15.low; lastprice.volume = lastprice15.volume; lastprice.date = lastprice15.date; }
public void NormalizeData(double minprice, double yzoomcoeff) { normpricedata = new Dictionary <int, PriceCandle>(); foreach (int key in keys) { PriceCandle p = pricedata[key]; PriceCandle pnew = p.Copy(); pnew.open = (pnew.open - minprice) / yzoomcoeff; pnew.high = (pnew.high - minprice) / yzoomcoeff; pnew.low = (pnew.low - minprice) / yzoomcoeff; pnew.close = (pnew.close - minprice) / yzoomcoeff; // pnew.date = key; normpricedata.Add(key, pnew); } }
public void Convert1(bool fromday) { List <int> keys15; keys15 = new List <int>(origpricedata.Keys); min15pricedata = new Dictionary <int, PriceCandle>(); hourpricedata = new Dictionary <int, PriceCandle>(); if (!fromday) { daypricedata = new Dictionary <int, PriceCandle>(); } weekpricedata = new Dictionary <int, PriceCandle>(); monthpricedata = new Dictionary <int, PriceCandle>(); DateTime dt; PriceCandle lastprice15 = new PriceCandle(); ConvertData min15data = new ConvertData(ConvertDataType.BAR_15); ConvertData hourdata = new ConvertData(ConvertDataType.BAR_HOUR); ConvertData daydata = new ConvertData(ConvertDataType.BAR_DAY); ConvertData weekdata = new ConvertData(ConvertDataType.BAR_WEEK); ConvertData monthdata = new ConvertData(ConvertDataType.BAR_MONTH); for (int n = 0; n < keys15.Count; n++) { if (n == 486) { } dt = Helper.UnixToDateTime(keys15[n].ToString()); // if (dt.Month == 9 && dt.Day == 15) min15data.prevclose = lastprice15.close; hourdata.prevclose = lastprice15.close; daydata.prevclose = lastprice15.close; weekdata.prevclose = lastprice15.close; monthdata.prevclose = lastprice15.close; lastprice15 = origpricedata[keys15[n]]; if (!fromday) { min15data.SetCurBar(dt, lastprice15); min15data.ProcessBar(min15pricedata); hourdata.SetCurBar(dt, lastprice15); hourdata.ProcessBar(hourpricedata); daydata.SetCurBar(dt, lastprice15); daydata.ProcessBar(daypricedata); } weekdata.SetCurBar(dt, lastprice15); weekdata.ProcessBar(weekpricedata); monthdata.SetCurBar(dt, lastprice15); monthdata.ProcessBar(monthpricedata); } if (!fromday) { min15data.prevclose = lastprice15.close; hourdata.prevclose = lastprice15.close; daydata.prevclose = lastprice15.close; min15data.AddPrice(min15pricedata); hourdata.AddPrice(hourpricedata); daydata.AddPrice(daypricedata); } weekdata.prevclose = lastprice15.close; monthdata.prevclose = lastprice15.close; weekdata.AddPrice(weekpricedata); monthdata.AddPrice(monthpricedata); }
public bool UpdatePrice(double lastPrice) { bool barAdded = false; PriceCandle last = pricedata.Last().Value; int lastPriceDateUnix = pricedata.Keys.Max(); DateTime lastPriceDate = Helper.UnixToDateTime(lastPriceDateUnix); int unixPeriod = 0; switch (curViewType) { case ConvertDataType.BAR_5: unixPeriod = 15 * 60; break; case ConvertDataType.BAR_15: unixPeriod = 15 * 60; break; case ConvertDataType.BAR_HOUR: unixPeriod = 60 * 60; break; case ConvertDataType.BAR_DAY: unixPeriod = 24 * 60 * 60; break; case ConvertDataType.BAR_WEEK: unixPeriod = 7 * 24 * 60 * 60; break; case ConvertDataType.BAR_MONTH: unixPeriod = DateTime.DaysInMonth(lastPriceDate.Year, lastPriceDate.Month) * 24 * 60 * 60; break; } int curDateUnix = Helper.ToUnixTimeStamp(DateTime.UtcNow); if (curDateUnix >= lastPriceDateUnix + unixPeriod) { //create new bar PriceCandle newbar = new PriceCandle(); newbar.close = lastPrice; newbar.open = last.close; newbar.high = Math.Max(last.close, lastPrice); newbar.low = Math.Min(last.close, lastPrice); newbar.volume = 666; newbar.date = lastPriceDateUnix + unixPeriod; pricedata.Add(newbar.date, newbar); barAdded = true; } // else { //update last bar // int rn = Helper.GetRandomExact(0, 1); // if (rn==0) // { double newClose = lastPrice;// + 100; last.close = newClose; if (last.high < newClose) { last.high = newClose; } if (last.low > newClose) { last.low = newClose; } } keys = new List <int>(pricedata.Keys); return(barAdded); }
void DrawMainChart() { // price rect int dx = 3; PointF p = new PointF(-dx, -dx); PointF size = new PointF(draww + dx * 2, drawh + dx * 2); AreaDrawRectangle(Pens.RosyBrown, Brushes.Red, p, size, false); // right price levels int plevelcount = 7; double startplevel = minprice; double endplevel = maxprice; double priceadd = (endplevel - startplevel) / plevelcount; double curplevel = startplevel; for (int n = 0; n <= plevelcount; n++) { p = new PointF(draww + 10, (float)NormalizePrice(curplevel) + style.strLabelFont.Size / 2); string pricestr = Helper.PriceToString(curplevel); AreaDrawString(style.strLabelBrush, style.strLabelFont, p, pricestr); curplevel += priceadd; } // down data labels int datesize = 90; float cursize = 0; float pos = draww - barsizex; Dictionary <int, PriceCandle> pricedata = data.GetNormalizeData(); List <int> keys = data.GetKeys(); for (int n = lasttick; n > starttick; n--) { cursize += barsizex; if (cursize >= datesize) { int date1 = keys[n]; p.X = pos; p.Y = -style.strLabelFont.Size; DateTime unixtime = Helper.UnixToDateTime(date1.ToString()).ToLocalTime(); AreaDrawString(style.strLabelBrush, style.strLabelFont, p, unixtime.ToString("dd.MM.yyyy")); cursize = 0; } pos -= barsizex; } // mouse selection if (mousedrawx != -1) { PointF p1 = new PointF(0, mousedrawy); PointF p2 = new PointF(draww, mousedrawy); float[] dashValues = { 5, 8 }; Pen dashPen = new Pen(style.SelLine.Color); dashPen.DashPattern = dashValues; AreaDrawLine(dashPen, p1, p2); float sy = mousedrawy - style.strLabelSelFont.Size; p = new PointF(draww + 10, sy); size = new PointF(RightOffset, style.strLabelSelFont.Size * 2f); AreaDrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, true); AreaDrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, false); p = new PointF(draww + 10, sy + style.strLabelSelFont.Size * 2); string pricestr = Helper.PriceToString(DeNormalizePrice(mousedrawy)); AreaDrawString(style.strLabelSelBrush, style.strLabelSelFont, p, pricestr); // vertical p1 = new PointF(mousedrawx, 0); p2 = new PointF(mousedrawx, drawh); float[] dashValuesV = { 4, 11 }; dashPen = new Pen(style.SelLine.Color); dashPen.DashPattern = dashValuesV; AreaDrawLine(dashPen, p1, p2); if (selectedPriceKey != -1) { PriceCandle price = pricedata[selectedPriceKey]; DateTime unixtime = Helper.UnixToDateTime(price.date.ToString()); string strtime = unixtime.ToLocalTime().ToString("dd.MM.yyyy HH:mm"); float strwidth = Helper.StringSize(g, style.strLabelSelFont, strtime).Width; p = new PointF(mousedrawx - strwidth / 2 - 3, 0); size = new PointF(strwidth + 3 * 2, style.strLabelSelFont.Size * 2f); DrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, true); DrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, false); p.X = mousedrawx - strwidth / 2; p.Y = -style.strLabelSelFont.Size; AreaDrawString(style.strLabelSelBrush, style.strLabelSelFont, p, strtime); //Date+V+OHLC Dictionary <int, PriceCandle> curdata = data.GetData(); PriceCandle priceReal = curdata[selectedPriceKey]; p.X = draww / 12; p.Y = drawh - style.strLabelFont.Size / 5; // string string strDescr = string.Format("{0} VOL: {1}", strtime, ((int)priceReal.volume).ToString()); AreaDrawString(style.strLabelBrush, style.strLabelFont, p, strDescr); p.X = draww / 12; p.Y = drawh - style.strLabelFont.Size - 8; strDescr = string.Format("O: {0} H: {1} L: {2} C: {3}", Helper.PriceToString(priceReal.open), Helper.PriceToString(priceReal.high), Helper.PriceToString(priceReal.low), Helper.PriceToString(priceReal.close)); AreaDrawString(style.strLabelBrush, style.strLabelFont, p, strDescr); } } // buttons /* * DrawButton(buttonMonth); * DrawButton(buttonWeek); * DrawButton(buttonDay); * DrawButton(buttonHour); * DrawButton(buttonMin15); * DrawButton(buttonMin5); */ DrawButton(buttonZoomPlus); DrawButton(buttonZoomMinus); DrawString(style.strLabelBrush, style.strLabelFont, new PointF(draww + 10, areaymax - style.strLabelSelFont.Size / 2), chartCaption); }
public void SetCurBar(DateTime dt_, PriceCandle lastprice15_) { lastprice15 = lastprice15_; dt = dt_; }
void DrawPrice() { Dictionary <int, PriceCandle> pricedata = data.GetNormalizeData(); List <int> keys = data.GetKeys(); float pos = draww; selectedPriceKey = -1; for (int n = lasttick; n > starttick; n--) { Pen pen = style.barUp; Brush brush = style.barUpBrush; int key1 = keys[n - 1]; int date1 = key1; PriceCandle price1 = pricedata[key1]; int key2 = keys[n]; int date2 = key2; PriceCandle price2 = pricedata[key2]; bool fill = false; if (price2.close < price2.open) { pen = style.barDown; brush = style.barDownBrush; fill = true; } float barspace = 0; if (mousedrawx != -1) { if (mousedrawx >= pos - barsizex && mousedrawx < pos) { pen = new Pen(Color.Yellow); brush = new SolidBrush(Color.Yellow); selectedPriceKey = key2; } } if (barsizex > 2) { barspace = 2; float realbarsize = barsizex - barspace; PointF p = new PointF(pos - barsizex, (float)Math.Min(price2.close, price2.open)); PointF size = new PointF(realbarsize, (float)Math.Abs(price2.close - price2.open)); AreaDrawRectangle(pen, brush, p, size, fill); PointF p1 = new PointF(pos - barsizex / 2 - barspace / 2, (float)price2.high); PointF p2 = new PointF(pos - barsizex / 2 - barspace / 2, (float)Math.Max(price2.close, price2.open)); AreaDrawLine(pen, p1, p2); p1 = new PointF(pos - barsizex / 2 - barspace / 2, (float)price2.low); p2 = new PointF(pos - barsizex / 2 - barspace / 2, (float)Math.Min(price2.close, price2.open)); if (p1.X == p2.X && p1.Y == p2.Y) { p2.X += 1; } AreaDrawLine(pen, p1, p2); /* * if(price2.high==price2.low) * { * p1 = new PointF(pos - barsizex / 2 - barspace / 2-1, (float)price2.close); * p2 = new PointF(pos - barsizex / 2 - barspace / 2+1, (float)price2.close); * AreaDrawLine(pen, p1, p2); * } */ } else { PointF p1 = new PointF(pos - barsizex / 2, (float)price2.low); PointF p2 = new PointF(pos - barsizex / 2, (float)price2.high); if (p1.X == p2.X && p1.Y == p2.Y) { p2.Y += 1; } AreaDrawLine(pen, p1, p2); } pos -= barsizex; } }
void DrawMainChart() { // price rect int dx = 3; PointF p = new PointF(-dx, -dx); PointF size = new PointF(draw.draww + dx * 2, draw.drawh + dx * 2); draw.AreaDrawRectangle(Pens.RosyBrown, Brushes.Red, p, size, false); // draw right price levels int plevelcount = 7; double startplevel = minprice; double endplevel = maxprice; double priceadd = (endplevel - startplevel) / plevelcount; double curplevel = startplevel; for (int n = 0; n <= plevelcount; n++) { p = new PointF(draw.draww + 10, (float)NormalizePrice(curplevel) + style.strLabelFont.Size / 2); string pricestr = Helper.PriceToString(curplevel); draw.AreaDrawString(style.strLabelBrush, style.strLabelFont, p, pricestr); curplevel += priceadd; } // draw left volume levels if (drawVolume) { float voldrawh = GetVolumeDrawH(); p = new PointF(draw.LeftOffset, (float)voldrawh + style.strLabelFont.Size / 2); string volstr = Helper.PriceToStringFinance(maxvol); draw.AreaDrawString(style.strLabelBrush, style.strLabelFont, p, volstr); p = new PointF(draw.LeftOffset, (float)voldrawh * (2f / 3f) + style.strLabelFont.Size / 2); volstr = Helper.PriceToStringFinance(maxvol * (2f / 3f)); draw.AreaDrawString(style.strLabelBrush, style.strLabelFont, p, volstr); p = new PointF(draw.LeftOffset, (float)voldrawh * (1f / 3f) + style.strLabelFont.Size / 2); volstr = Helper.PriceToStringFinance(maxvol * (1f / 3f)); draw.AreaDrawString(style.strLabelBrush, style.strLabelFont, p, volstr); p = new PointF(draw.LeftOffset, (float)voldrawh * (1f / 6f) + style.strLabelFont.Size / 2); volstr = Helper.PriceToStringFinance(maxvol * (1f / 6f)); draw.AreaDrawString(style.strLabelBrush, style.strLabelFont, p, volstr); } // down data labels int datesize = 90; float cursize = 0; float pos = draw.draww - barsizex; Dictionary <int, PriceCandle> pricedata = data.GetNormalizeData(); List <int> keys = data.GetKeys(); for (int n = lasttick; n > starttick; n--) { cursize += barsizex; if (cursize >= datesize) { int date1 = keys[n]; p.X = pos; p.Y = -style.strLabelFont.Size; DateTime unixtime = Helper.UnixToDateTime(date1.ToString()).ToLocalTime(); draw.AreaDrawString(style.strLabelBrush, style.strLabelFont, p, unixtime.ToString("dd.MM.yyyy")); cursize = 0; } pos -= barsizex; } Dictionary <int, PriceCandle> curdata = data.GetData(); PriceCandle lastpriceReal = curdata.Last().Value;// [curdata.Count - 1]; PriceCandle lastprice = pricedata.Last().Value; // rectangle with last price float ry = (float)lastprice.close - style.strLastPriceFont.Size; p = new PointF(draw.draww + 10, ry); size = new PointF(draw.RightOffset, style.strLastPriceFont.Size * 2f); draw.AreaDrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, true); draw.AreaDrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, false); p = new PointF(draw.draww + 10, ry + style.strLastPriceFont.Size * 2); string pricestrreal = Helper.PriceToString(lastpriceReal.close); draw.AreaDrawString(style.strLastPriceBrush, style.strLastPriceFont, p, pricestrreal); // mouse selection if (mousedrawx != -1) { PointF p1 = new PointF(0, mousedrawy); PointF p2 = new PointF(draw.draww, mousedrawy); float[] dashValues = { 5, 8 }; Pen dashPen = new Pen(style.SelLine.Color); dashPen.DashPattern = dashValues; draw.AreaDrawLine(dashPen, p1, p2); // rectangle with selected price float sy = mousedrawy - style.strLabelSelFont.Size; p = new PointF(draw.draww + 10, sy); size = new PointF(draw.RightOffset, style.strLabelSelFont.Size * 2f); draw.AreaDrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, true); draw.AreaDrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, false); p = new PointF(draw.draww + 10, sy + style.strLabelSelFont.Size * 2); string pricestr = Helper.PriceToString(DeNormalizePrice(mousedrawy)); draw.AreaDrawString(style.strLabelSelBrush, style.strLabelSelFont, p, pricestr); // vertical p1 = new PointF(mousedrawx, 0); p2 = new PointF(mousedrawx, draw.drawh); float[] dashValuesV = { 4, 11 }; dashPen = new Pen(style.SelLine.Color); dashPen.DashPattern = dashValuesV; draw.AreaDrawLine(dashPen, p1, p2); if (selectedPriceKey != -1) { PriceCandle priceReal = curdata[selectedPriceKey]; PriceCandle price = pricedata[selectedPriceKey]; DateTime unixtime = Helper.UnixToDateTime(price.date.ToString()); string strtime = unixtime.ToLocalTime().ToString("dd.MM.yyyy HH:mm"); float strwidth = Helper.StringSize(style.strLabelSelFont, strtime).Width; p = new PointF(mousedrawx - strwidth / 2 - 3, 0); size = new PointF(strwidth + 13 * 2, style.strLabelSelFont.Size * 2f); draw.DrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, true); draw.DrawRectangle(Pens.RosyBrown, Brushes.Black, p, size, false); p.X = mousedrawx - strwidth / 2; p.Y = -style.strLabelSelFont.Size; draw.AreaDrawString(style.strLabelSelBrush, style.strLabelSelFont, p, strtime); //Date+V+OHLC p.X = draw.draww / 12; p.Y = draw.drawh - style.strLabelFont.Size / 5; // string string strDescr = string.Format("{0} VOL: {1}", strtime, ((int)priceReal.volume).ToString()); draw.AreaDrawString(style.strLabelBrush, style.strLabelFont, p, strDescr); p.X = draw.draww / 12; p.Y = draw.drawh - style.strLabelFont.Size - 8; strDescr = string.Format("O: {0} H: {1} L: {2} C: {3}", Helper.PriceToString(priceReal.open), Helper.PriceToString(priceReal.high), Helper.PriceToString(priceReal.low), Helper.PriceToString(priceReal.close)); draw.AreaDrawString(style.strLabelBrush, style.strLabelFont, p, strDescr); } } draw.DrawString(style.strLabelBrush, style.strLabelFont, new PointF(draw.draww + 10, draw.areaymax - style.strLabelSelFont.Size / 5), chartCaption); }
void DrawPrice() { Dictionary <int, PriceCandle> pricedata = data.GetNormalizeData(); List <int> keys = data.GetKeys(); float pos = draw.draww; selectedPriceKey = -1; Dictionary <int, PriceCandle> realData = data.GetData(); PriceCandle lastPrice = realData.Last().Value; //[keys[lasttick]]; string strLastPrice = Helper.PriceToString(lastPrice.close); draw.DrawString(style.strLastPriceBrush, style.strLastPriceFont, new PointF(draw.draww - 80, draw.areaymax), strLastPrice); for (int n = lasttick; n > starttick; n--) { Pen pen = style.barUp; Brush brush = style.barUpBrush; Pen volpen = style.volbarUp; Brush volbrush = style.volbarUpBrush; int key1 = keys[n - 1]; int date1 = key1; PriceCandle price1 = pricedata[key1]; int key2 = keys[n]; int date2 = key2; PriceCandle price2 = pricedata[key2]; bool fill = false; if (price2.close < price2.open) { pen = style.barDown; brush = style.barDownBrush; volpen = style.volbarDown; volbrush = style.volbarDownBrush; fill = true; } float barspace = 0; if (mousedrawx != -1) { if (mousedrawx >= pos - barsizex && mousedrawx < pos) { pen = new Pen(Color.Yellow); brush = new SolidBrush(Color.Yellow); selectedPriceKey = key2; } } if (barsizex > 2) { barspace = 2; float realbarsize = barsizex - barspace; PointF p; PointF size; // volume if (drawVolume) { p = new PointF(pos - barsizex - 1, (float)0); size = new PointF(realbarsize + 1, (float)price2.volume); draw.AreaDrawRectangle(volpen, volbrush, p, size, true); } // price p = new PointF(pos - barsizex, (float)Math.Min(price2.close, price2.open)); size = new PointF(realbarsize, (float)Math.Abs(price2.close - price2.open)); draw.AreaDrawRectangle(pen, brush, p, size, fill); PointF p1 = new PointF(pos - barsizex / 2 - barspace / 2, (float)price2.high); PointF p2 = new PointF(pos - barsizex / 2 - barspace / 2, (float)Math.Max(price2.close, price2.open)); draw.AreaDrawLine(pen, p1, p2); p1 = new PointF(pos - barsizex / 2 - barspace / 2, (float)price2.low); p2 = new PointF(pos - barsizex / 2 - barspace / 2, (float)Math.Min(price2.close, price2.open)); if (p1.X == p2.X && p1.Y == p2.Y) { p2.X += 1; } draw.AreaDrawLine(pen, p1, p2); } else { PointF p1; PointF p2; // volume if (drawVolume) { p1 = new PointF(pos - barsizex / 2, (float)0); p2 = new PointF(pos - barsizex / 2, (float)price2.volume); if (p1.X == p2.X && p1.Y == p2.Y) { p2.Y += 1; } draw.AreaDrawLine(volpen, p1, p2); } // price p1 = new PointF(pos - barsizex / 2, (float)price2.low); p2 = new PointF(pos - barsizex / 2, (float)price2.high); if (p1.X == p2.X && p1.Y == p2.Y) { p2.Y += 1; } draw.AreaDrawLine(pen, p1, p2); } pos -= barsizex; } }