From: SourceForge.net <no...@so...> - 2007-12-29 15:39:13
|
Feature Requests item #1709964, was opened at 2007-04-30 02:04 Message generated for change (Comment added) made by nobody You can respond by visiting: https://sourceforge.net/tracker/?func=detail&atid=821571&aid=1709964&group_id=161868 Please note that this message will contain a full copy of the comment thread, including the initial issue submission, for this request, not just the latest update. Category: None Group: None Status: Open Priority: 5 Private: No Submitted By: Nobody/Anonymous (nobody) Assigned to: Nobody/Anonymous (nobody) Summary: non trading days in TradingDateTimeAxis Initial Comment: TradingDateTimeAxis is actually removing only saturdays and sundays. I'd like non trading days (apart form weekends) to be removed as I get trend lines errors. ---------------------------------------------------------------------- Comment By: Nobody/Anonymous (nobody) Date: 2007-12-29 07:39 Message: Logged In: NO I found another bug in the last correction posted here: the ticks labels were being created even for no tradding days. This little problem was corrected, just change the method "OnTradingDays" code of the class "TradingDateTimeAxis", for this: public bool OnTradingDays(double coord) { long ticks = (long)coord; long whole_days = ticks / TimeSpan.TicksPerDay; long days_in_last_week = whole_days % 7; bool retorno=true; if (!(days_in_last_week < 5)) { retorno = false; } else { DateTime aux = new DateTime(ticks); foreach (DateTime dt in exceptionalNonTradingDays) { if (aux == dt) { retorno = false; break; } } } return retorno; } Att. André Novais afn...@gm... ---------------------------------------------------------------------- Comment By: Nobody/Anonymous (nobody) Date: 2007-05-11 14:33 Message: Logged In: NO Hi, I've just modified TradingTimeAxis in order to allow non trading days to be removed from the axis... best, Davide. /* * NPlot - A charting library for .NET * * TradingDateTimeAxis.cs * Copyright (C) 2003-2006 Matt Howlett and others. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of NPlot nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Drawing; using System.Collections; namespace NPlot { /// <summary> /// Provides a DateTime axis that removes non-trading days. /// </summary> public class TradingDateTimeAxis : DateTimeAxis { /// <summary> /// a list of non trading days /// </summary> private ArrayList exceptionalNonTradingDays; /// <summary> /// add a day to the list of non trading days /// </summary> /// <param name="dayToAdd">non trading day to add</param> public void AddNonTradingDay(DateTime dayToAdd) { if (!exceptionalNonTradingDays.Contains(dayToAdd)) { exceptionalNonTradingDays.Add(dayToAdd); } } // we keep shadow "virtual" copies of WorldMin/Max for speed // which are already remapped, so it is essential that changes // to WorldMin/Max are captured here /// <summary> /// The axis world min value. /// </summary> public override double WorldMin { get { return base.WorldMin; } set { base.WorldMin = value; virtualWorldMin_ = SparseWorldRemap(value); } } private double virtualWorldMin_ = double.NaN; /// <summary> /// The axis world max value. /// </summary> public override double WorldMax { get { return base.WorldMax; } set { base.WorldMax = value; virtualWorldMax_ = SparseWorldRemap(value); } } private double virtualWorldMax_ = double.NaN; /// <summary> /// Optional time at which trading begins. /// All data points earlied than that (same day) will be collapsed. /// </summary> public virtual TimeSpan StartTradingTime { get { return new TimeSpan(startTradingTime_); } set { startTradingTime_ = value.Ticks; tradingTimeSpan_ = endTradingTime_ - startTradingTime_; } } private long startTradingTime_; /// <summary> /// Optional time at which trading ends. /// All data points later than that (same day) will be collapsed. /// </summary> public virtual TimeSpan EndTradingTime { get { return new TimeSpan(endTradingTime_); } set { endTradingTime_ = value.Ticks; tradingTimeSpan_ = endTradingTime_ - startTradingTime_; } } private long endTradingTime_; private long tradingTimeSpan_; /// <summary> /// Get whether or not this axis is linear. /// </summary> public override bool IsLinear { get { return false; } } /// <summary> /// Constructor /// </summary> public TradingDateTimeAxis() : base() { exceptionalNonTradingDays = new ArrayList(); Init(); } /// <summary> /// Constructor /// </summary> public TradingDateTimeAxis(ArrayList nonTradingDays) : base() { exceptionalNonTradingDays = nonTradingDays; Init(); } /// <summary> /// Copy Constructor /// </summary> /// <param name="a">construct a TradingDateTimeAxis based on this provided axis.</param> public TradingDateTimeAxis(Axis a) : base(a) { exceptionalNonTradingDays = new ArrayList(); Init(); if (a is TradingDateTimeAxis) DoClone((TradingDateTimeAxis)a, this); else if (a is DateTimeAxis) DoClone((DateTimeAxis)a, this); else { DoClone(a, this); this.NumberFormat = null; } } /// <summary> /// Helper function for constructors. /// </summary> private void Init() { startTradingTime_ = 0; endTradingTime_ = TimeSpan.TicksPerDay; tradingTimeSpan_ = endTradingTime_ - startTradingTime_; virtualWorldMin_ = SparseWorldRemap(WorldMin); virtualWorldMax_ = SparseWorldRemap(WorldMax); } /// <summary> /// Deep copy of DateTimeAxis. /// </summary> /// <returns>A copy of the DateTimeAxis Class.</returns> public override object Clone() { TradingDateTimeAxis a = new TradingDateTimeAxis(); // ensure that this isn't being called on a derived type. If it is, then oh no! if (this.GetType() != a.GetType()) { throw new NPlotException( "Clone not defined in derived type. Help!" ); } DoClone( this, a ); return a; } /// <summary> /// Helper method for Clone. /// </summary> /// <param name="a">The cloned target object.</param> /// <param name="b">The cloned source object.</param> protected static void DoClone(TradingDateTimeAxis b, TradingDateTimeAxis a) { DateTimeAxis.DoClone(b, a); a.startTradingTime_ = b.startTradingTime_; a.endTradingTime_ = b.endTradingTime_; a.tradingTimeSpan_ = b.tradingTimeSpan_; a.WorldMin = b.WorldMin; a.WorldMax = b.WorldMax; a.exceptionalNonTradingDays = b.exceptionalNonTradingDays; } /// <summary> /// World to physical coordinate transform. /// </summary> /// <param name="coord">The coordinate value to transform.</param> /// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param> /// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param> /// <param name="clip">if false, then physical value may extend outside worldMin / worldMax. If true, the physical value returned will be clipped to physicalMin or physicalMax if it lies outside this range.</param> /// <returns>The transformed coordinates.</returns> /// <remarks>Not sure how much time is spent in this often called function. If it's lots, then /// worth optimizing (there is scope to do so).</remarks> public override PointF WorldToPhysical( double coord, PointF physicalMin, PointF physicalMax, bool clip) { // (1) account for reversed axis. Could be tricky and move // this out, but would be a little messy. PointF _physicalMin; PointF _physicalMax; if (this.Reversed) { _physicalMin = physicalMax; _physicalMax = physicalMin; } else { _physicalMin = physicalMin; _physicalMax = physicalMax; } // (2) if want clipped value, return extrema if outside range. if (clip) { if (WorldMin < WorldMax) { if (coord > WorldMax) { return _physicalMax; } if (coord < WorldMin) { return _physicalMin; } } else { if (coord < WorldMax) { return _physicalMax; } if (coord > WorldMin) { return _physicalMin; } } } // (3) we are inside range or don't want to clip. coord = SparseWorldRemap(coord); double range = virtualWorldMax_ - virtualWorldMin_; double prop = (double)((coord - virtualWorldMin_) / range); //double range = WorldMax - WorldMin; //double prop = (double)((coord - WorldMin) / range); //if (range1 != range) // range1 = range; // Force clipping at bounding box largeClip times that of real bounding box // anyway. This is effectively at infinity. const double largeClip = 100.0; if (prop > largeClip && clip) prop = largeClip; if (prop < -largeClip && clip) prop = -largeClip; if (range == 0) { if (coord >= virtualWorldMin_) prop = largeClip; if (coord < virtualWorldMin_) prop = -largeClip; } // calculate the physical coordinate. PointF offset = new PointF( (float)(prop * (_physicalMax.X - _physicalMin.X)), (float)(prop * (_physicalMax.Y - _physicalMin.Y))); return new PointF(_physicalMin.X + offset.X, _physicalMin.Y + offset.Y); } /// <summary> /// Transforms a physical coordinate to an axis world /// coordinate given the physical extremites of the axis. /// </summary> /// <param name="p">the point to convert</param> /// <param name="physicalMin">the physical minimum extremity of the axis</param> /// <param name="physicalMax">the physical maximum extremity of the axis</param> /// <param name="clip">whether or not to clip the world value to lie in the range of the axis if it is outside.</param> /// <returns></returns> public override double PhysicalToWorld( PointF p, PointF physicalMin, PointF physicalMax, bool clip) { // (1) account for reversed axis. Could be tricky and move // this out, but would be a little messy. PointF _physicalMin; PointF _physicalMax; if (this.Reversed) { _physicalMin = physicalMax; _physicalMax = physicalMin; } else { _physicalMin = physicalMin; _physicalMax = physicalMax; } // normalised axis dir vector float axis_X = _physicalMax.X - _physicalMin.X; float axis_Y = _physicalMax.Y - _physicalMin.Y; float len = (float)Math.Sqrt(axis_X * axis_X + axis_Y * axis_Y); axis_X /= len; axis_Y /= len; // point relative to axis physical minimum. PointF posRel = new PointF(p.X - _physicalMin.X, p.Y - _physicalMin.Y); // dist of point projection on axis, normalised. float prop = (axis_X * posRel.X + axis_Y * posRel.Y) / len; //double world = prop * (WorldMax - WorldMin) + WorldMin; double world = prop * (virtualWorldMax_ - virtualWorldMin_) + virtualWorldMin_; world = ReverseSparseWorldRemap(world); // if want clipped value, return extrema if outside range. if (clip) { world = Math.Max(world, WorldMin); world = Math.Min(world, WorldMax); } return world; } /// <summary> /// Remap a world coordinate into a "virtual" world, where non-trading dates and times are collapsed. /// </summary> /// <remarks> /// This code works under asumption that there are exactly 24*60*60 seconds in a day /// This is strictly speaking not correct but apparently .NET 2.0 does not count leap seconds. /// Luckilly, Ticks == 0 =~= 0001-01-01T00:00 =~= Monday /// First tried a version fully on floating point arithmetic, /// but failed hopelessly due to rounding errors. /// </remarks> /// <param name="coord">world coordinate to transform.</param> /// <returns>equivalent virtual world coordinate.</returns> protected double SparseWorldRemap(double coord) { long ticks = (long)coord; /* * removing non trading days.. * davide morelli * */ int numberOfDaysToRemove = 0; foreach (DateTime day in exceptionalNonTradingDays) { if (day.Ticks < ticks) numberOfDaysToRemove++; } long whole_days = ticks / TimeSpan.TicksPerDay; long ticks_in_last_day = ticks % TimeSpan.TicksPerDay; long full_weeks = whole_days / 7; long days_in_last_week = whole_days % 7; if (days_in_last_week >= 5) { days_in_last_week = 5; ticks_in_last_day = 0; } if (ticks_in_last_day < startTradingTime_) ticks_in_last_day = startTradingTime_; else if (ticks_in_last_day > endTradingTime_) ticks_in_last_day = endTradingTime_; ticks_in_last_day -= startTradingTime_; // removing non trading days... long whole_working_days = (full_weeks * 5 + days_in_last_week) - numberOfDaysToRemove; long working_ticks = whole_working_days * tradingTimeSpan_; long new_ticks = working_ticks + ticks_in_last_day; return (double)new_ticks; } /// <summary> /// Remaps a "virtual" world coordinates back to true world coordinates. /// </summary> /// <param name="coord">virtual world coordinate to transform.</param> /// <returns>equivalent world coordinate.</returns> protected double ReverseSparseWorldRemap(double coord) { long ticks = (long)coord; //ticks += startTradingTime_; /* * removing non trading days.. * davide morelli * */ int numberOfDaysToRemove = 0; foreach (DateTime day in exceptionalNonTradingDays) { if (SparseWorldRemap(day.Ticks) < ticks) numberOfDaysToRemove++; } ticks += numberOfDaysToRemove * tradingTimeSpan_; long ticks_in_last_day = ticks % tradingTimeSpan_; ticks /= tradingTimeSpan_; long full_weeks = ticks / 5; long week_part = ticks % 5; long day_ticks = (full_weeks * 7 + week_part) * TimeSpan.TicksPerDay; return (double)(day_ticks + ticks_in_last_day + startTradingTime_); } /// <summary> /// Adds a delta amount to the given world coordinate in such a way that /// all "sparse gaps" are skipped. In other words, the returned value is /// in delta distance from the given in the "virtual" world. /// </summary> /// <param name="coord">world coordinate to shift.</param> /// <param name="delta">shif amount in "virtual" units.</param> /// <returns></returns> public double SparseWorldAdd(double coord, double delta) { return ReverseSparseWorldRemap(SparseWorldRemap(coord) + delta); } /// <summary> /// World extent in virtual (sparse) units. /// </summary> public double SparseWorldLength { get { return SparseWorldRemap(WorldMax) - SparseWorldRemap(WorldMin); } } /// <summary> /// Check whether the given coordinate falls within defined trading hours. /// </summary> /// <param name="coord">world coordinate in ticks to check.</param> /// <returns>true if in trading hours, false if in non-trading gap.</returns> public bool WithinTradingHours(double coord) { long ticks = (long)coord; long whole_days = ticks / TimeSpan.TicksPerDay; long ticks_in_last_day = ticks % TimeSpan.TicksPerDay; long days_in_last_week = whole_days % 7; if (days_in_last_week >= 5) return false; if (ticks_in_last_day < startTradingTime_) return false; if (ticks_in_last_day >= endTradingTime_) return false; return true; } /// <summary> /// Check whether the given coordinate falls on trading days. /// </summary> /// <param name="coord">world coordinate in ticks to check.</param> /// <returns>true if on Mon - Fri.</returns> public bool OnTradingDays(double coord) { long ticks = (long)coord; long whole_days = ticks / TimeSpan.TicksPerDay; long days_in_last_week = whole_days % 7; return (days_in_last_week < 5); } /// <summary> /// Determines the positions of all Large and Small ticks. /// </summary> /// <remarks> /// The method WorldTickPositions_FirstPass() from the base works just fine, except that it /// does not account for non-trading gaps in time, therefore, when less than two days are visible /// an own algorithm is used (to show intraday time). Otherwise the base class implementation is used /// but the output is corrected to remove ticks on non-trading days (Sat, Sun). /// </remarks> /// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param> /// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param> /// <param name="largeTickPositions">ArrayList containing the positions of the large ticks.</param> /// <param name="smallTickPositions">null</param> internal override void WorldTickPositions_FirstPass( Point physicalMin, Point physicalMax, out ArrayList largeTickPositions, out ArrayList smallTickPositions ) { if (LargeTickStep != TimeSpan.Zero || SparseWorldLength > 2.0 * (double)tradingTimeSpan_) // utilise base class { ArrayList largeTickPositions_FirstPass; base.WorldTickPositions_FirstPass(physicalMin, physicalMax, out largeTickPositions_FirstPass, out smallTickPositions); if (largeTickPositions_FirstPass.Count < 2) { // leave it alone, whatever that single tick may be (better something than nothing...) largeTickPositions = largeTickPositions_FirstPass; } else if ((double)largeTickPositions_FirstPass[1] - (double)largeTickPositions_FirstPass[0] > 27.0 * (double)TimeSpan.TicksPerDay) { // For distances between ticks in months or longer, just accept all ticks largeTickPositions = largeTickPositions_FirstPass; } else { // for daily ticks, ignore non-trading hours but obey (skip) non-trading days largeTickPositions = new ArrayList(); foreach (object tick in largeTickPositions_FirstPass) { if (OnTradingDays((double)tick)) largeTickPositions.Add(tick); } } } else // intraday ticks, own algorithm { smallTickPositions = null; largeTickPositions = new ArrayList(); TimeSpan timeLength = new TimeSpan((long)SparseWorldLength); DateTime worldMinDate = new DateTime( (long)this.WorldMin ); DateTime worldMaxDate = new DateTime( (long)this.WorldMax ); DateTime currentTickDate; long skip; // in time ticks // The following if-else flow establishes currentTickDate to the beginning of series // and skip to the optimal distance between ticks // if less than 10 minutes, then large ticks on second spacings. if ( timeLength < new TimeSpan(0,0,10,0,0) ) { this.LargeTickLabelType_ = LargeTickLabelType.hourMinuteSeconds; int secondsSkip; if (timeLength < new TimeSpan( 0,0,0,10,0 ) ) secondsSkip = 1; else if ( timeLength < new TimeSpan(0,0,0,20,0) ) secondsSkip = 2; else if ( timeLength < new TimeSpan(0,0,0,50,0) ) secondsSkip = 5; else if ( timeLength < new TimeSpan(0,0,2,30,0) ) secondsSkip = 15; else secondsSkip = 30; int second = worldMinDate.Second; second -= second % secondsSkip; currentTickDate = new DateTime( worldMinDate.Year, worldMinDate.Month, worldMinDate.Day, worldMinDate.Hour, worldMinDate.Minute, second,0 ); skip = secondsSkip * TimeSpan.TicksPerSecond; } // Less than 2 hours, then large ticks on minute spacings. else if ( timeLength < new TimeSpan(0,2,0,0,0) ) { this.LargeTickLabelType_ = LargeTickLabelType.hourMinute; int minuteSkip; if ( timeLength < new TimeSpan(0,0,10,0,0) ) minuteSkip = 1; else if ( timeLength < new TimeSpan(0,0,20,0,0) ) minuteSkip = 2; else if ( timeLength < new TimeSpan(0,0,50,0,0) ) minuteSkip = 5; else if ( timeLength < new TimeSpan(0,2,30,0,0) ) minuteSkip = 15; else //( timeLength < new TimeSpan( 0,5,0,0,0) ) minuteSkip = 30; int minute = worldMinDate.Minute; minute -= minute % minuteSkip; currentTickDate = new DateTime( worldMinDate.Year, worldMinDate.Month, worldMinDate.Day, worldMinDate.Hour, minute,0,0 ); skip = minuteSkip * TimeSpan.TicksPerMinute; } // Else large ticks on hour spacings. else { this.LargeTickLabelType_ = LargeTickLabelType.hourMinute; int hourSkip; if (timeLength < new TimeSpan(0, 10, 0, 0, 0)) hourSkip = 1; else if (timeLength < new TimeSpan(0, 20, 0, 0, 0)) hourSkip = 2; else hourSkip = 6; int hour = worldMinDate.Hour; hour -= hour % hourSkip; currentTickDate = new DateTime( worldMinDate.Year, worldMinDate.Month, worldMinDate.Day, hour, 0, 0, 0); skip = hourSkip * TimeSpan.TicksPerHour; } // place ticks while (currentTickDate < worldMaxDate) { double world = (double)currentTickDate.Ticks; if (!WithinTradingHours(world)) { // add gap boundary instead world = ReverseSparseWorldRemap(SparseWorldRemap(world)); // moves forward long gap = (long)world; gap -= gap % skip; currentTickDate = new DateTime(gap); } if (world >= this.WorldMin && world <= this.WorldMax) { largeTickPositions.Add(world); } currentTickDate = currentTickDate.AddTicks(skip); } } } /// <summary> /// Get an appropriate label name, given the DateTime of a label /// </summary> /// <param name="tickDate">the DateTime to get the label name for</param> /// <returns>A label name appropriate to the supplied DateTime.</returns> protected override string LargeTickLabel(DateTime tickDate) { string label; if ( this.NumberFormat == null && (LargeTickLabelType_ == LargeTickLabelType.hourMinute || LargeTickLabelType_ == LargeTickLabelType.hourMinuteSeconds) && tickDate.TimeOfDay == StartTradingTime) { // in such case always show the day date label = (tickDate.Day).ToString(); label += " "; label += tickDate.ToString("MMM"); } else { label = base.LargeTickLabel(tickDate); } return label; } } } ---------------------------------------------------------------------- You can respond by visiting: https://sourceforge.net/tracker/?func=detail&atid=821571&aid=1709964&group_id=161868 |