Padding time series data? - c#

I am receiving large amounts of time series data in a list timeSeries.Records. The object has a date time property of (10 minute intervals) TimeStamp and a data value property Data
The time series may have gaps which I need to pad with the appropriate date time stamp and a value of double.NaN. This will allow me to display the data correctly within a chart in the UI.
I have written the following code which works but is incredibly slow! I suspect this is because I am newing up new objects & returning them in the while loop. I assume this can be optimised significantly and maybe is the wrong approach completely but not sure where to start...
Here's the code which appends the data to the chart:
foreach (TimeSeriesRecord record in this.FillTimeSeriesGaps(timeSeries))
{
dataSeries.Append(record.TimeStamp, record.Data);
}
Filling the gaps:
private IEnumerable<TimeSeriesRecord> FillTimeSeriesGaps(ITimeSeriesProvider timeSeries)
{
// Get the min & max records by date time
TimeSeriesRecord minRecord = timeSeries.Records.OrderBy(r => r.TimeStamp).FirstOrDefault();
TimeSeriesRecord maxRecord = timeSeries.Records.OrderByDescending(r => r.TimeStamp).FirstOrDefault();
// 10 sec time interval
TimeSpan seriesIntervalTime = new TimeSpan(0, 10, 0);
DateTime workingDateTime = minRecord.TimeStamp;
while (minRecord.TimeStamp <= maxRecord.TimeStamp)
{
if (timeSeries.Records.All(r => r.TimeStamp != workingDateTime))
{
yield return new TimeSeriesRecord() {TimeStamp = workingDateTime, Data = double.NaN};
}
else
{
yield return new TimeSeriesRecord()
{
TimeStamp = workingDateTime,
Data = (from r in timeSeries.Records
where r.TimeStamp == workingDateTime
select r.Data).First()
};
workingDateTime = workingDateTime.Add(seriesIntervalTime);
}
}
}

For anyone who is interested this is how I sped things up:
private static IEnumerable<TimeSeriesRecord> FillTimeSeriesGaps(ITimeSeriesProvider timeSeries)
{
TimeSpan seriesIntervalTime = new TimeSpan(0, 10, 0);
DateTime previousDateTime = DateTime.MinValue;
foreach (GenericTimeSeriesRecord record in timeSeries.Records)
{
if (previousDateTime == DateTime.MinValue)
{
yield return record;
previousDateTime = record.TimeStamp;
continue;
}
if (previousDateTime + seriesIntervalTime == record.TimeStamp)
{
yield return record;
previousDateTime = record.TimeStamp;
continue;
}
else
{
yield return new TimeSeriesRecord() { TimeStamp = previousDateTime + seriesIntervalTime, Data = double.NaN };
previousDateTime = previousDateTime + seriesIntervalTime;
}
}
}

Related

Finding the month between a range

I have a datasource that returns dates and I have to find where the months falls within the month and day range buckets. The months and day range buckets are predefined so I put it in a Dictionary (not sure if that is even a good idea). I am using linq to find the min and Max dates and extracting the month from them. I need to find month from the dictionary where that month extracted falls within the range. For Example
Dictionary<int, int> MonthDayBuckets = new Dictionary<int, int>() { { 3,31 }, { 6,30 }, { 9,30 }, { 12,31 } };
var MinyDate = _dataSource.Min(x => x.values[0]);
var MaxDate = _dataSource.Max(x => x.values[0]);
var startMonth = Convert.ToDateTime(MinyDate).ToString("MM");
var endMonth = Convert.ToDateTime(MaxDate).ToString("MM");
Say startmonth return Jan so I want to be able to go to the dictionary and return only march (03.31) and if I get 10 for the Max (October) I am trying to return (12,31) December
If my understanding is correct, your MonthDayBuckets variable is meant to represent date ranges:
3/31 - 6/30
6/30 - 9/30
9/30 - 12/31
12/31 - 3/31
...and given a month, you're wanting to see what the end date is of the interval that the first of that month falls between? Like you gave the example of October returning 12/31.
This problem can be simplified since you'll get the same result saying "what's the next occurring date after this given date?" The next occurring date for 10/01 would be 12/31. So here's how you could rearrange your data:
var availableDates = new List<string> { "03/31", "06/30", "09/30", "12/31" };
Now you'll be able to find a match by finding the index of the first one that's greater than your given date. Note how I made the month/day combos lexicographical orderable.
var startMonth = Convert.ToDateTime(MinyDate).ToString("MM");
var startDate = startMonth + "/01";
var endMonth = Convert.ToDateTime(MaxDate).ToString("MM");
var endDate = endMonth + "/01";
// Wrap around to the first date if this falls after the end
var nextStartDate = availableDates.FirstOrDefault(d => d.CompareTo(startDate) >= 1) ?? availableDates[0];
var nextEndDate = availableDates.FirstOrDefault(d => d.CompareTo(endDate) >= 1) ?? availableDates[0];
You could use Linq for the purpose. For example,
var nearestKey = MonthDayBuckets.Keys.Where(x => x >= endMonth.Month).Min();
var nearestDate = new DateTime(DateTime.Now.Year,nearestKey,MonthDayBuckets[nearestKey]); // or whatever the year it needs to be represent
Though above query would get you the result, I would suggest you define a structure to store the Range itself, rather than using Dictionary
For example,
public class Range
{
public MonthDate StartRange{get;set;}
public MonthDate EndRange{get;set;}
public Range(MonthDate startRange,MonthDate endRange)
{
StartRange = startRange;
EndRange = endRange;
}
}
public class MonthDate
{
public MonthDate(int month,int date)
{
Month = month;
Date = date;
}
public int Month{get;set;}
public int Date{get;set;}
//Depending on if your Ranges are inclusive or not,you need to decide how to compare
public static bool operator >=(MonthDate source, MonthDate comparer)
{
return source.Month>= comparer.Month && source.Date>=comparer.Date;
}
public static bool operator <=(MonthDate source, MonthDate comparer)
{
return source.Month<= comparer.Month && source.Date<=comparer.Date;
}
}
Now you could define ranges as
var dateRanges = new Range[]
{
new Range(new MonthDate(12,31),new MonthDate(3,31)),
new Range(new MonthDate(3,31),new MonthDate(6,30)),
new Range(new MonthDate(6,30),new MonthDate(12,31)),
};
var result = dateRanges.First(x=>x.StartRange <= new MonthDate(endMonth.Month,endMonth.Day) && x.EndRange >= new MonthDate(endMonth.Month,endMonth.Day));

Aggregate and Group by Datetime

Having this data (SQL):
I call (every minute with timer )data then group into 30 minutes interval with this method:
public void GetData()
{
try
{
using (crypto_dbEntities1 context = new crypto_dbEntities1(con))
{
var result = context.kraken_btc.Where(x => x.date >= LastRecordedPoint).ToList();
result = AggregateCandlesIntoRequestedTimePeriod(Period.Minute, Period.HalfAnHour, result);
foreach (var data in result.OrderBy(x => x.date))
{
data.date = DateTime.SpecifyKind(data.date.DateTime, DateTimeKind.Utc).ToUniversalTime();
Point pb = new Point
{
Date = data.date.DateTime.ToLocalTime(),
JDate = (long)data.javaDate,
Open = (double)data.open,
High = (double)data.hight,
Close = (double)data.close,
Low = (double)data.low,
Volume = (long)data.volume,
};
if (pb.Date <= LastRecordedPoint)
{
MainCollection.Last().Date = data.date.DateTime.ToLocalTime();
MainCollection.Last().Close = (double)data.close;
MainCollection.Last().High = (double)data.hight;
MainCollection.Last().Open = (double)data.open;
MainCollection.Last().Low = (double)data.low;
MainCollection.Last().Volume = (long)data.volume;
Debug.WriteLine(pb.Date + " Updated data ..");
}
else
{
MainCollection.Add(pb);
}
LastRecordedPoint = pb.Date;
}
}
}
catch (Exception err)
{
MessageBox.Show(err.ToString());
}
}
public enum Period
{
Minute = 1,
FiveMinutes = 5,
TenMinutes = 10,
QuarterOfAnHour = 15,
HalfAnHour = 30,
Hour = 60
}
private List<kraken_btc> AggregateCandlesIntoRequestedTimePeriod(Period rawPeriod, Period requestedPeriod, List<kraken_btc> candles)
{
int rawPeriodDivisor = (int)requestedPeriod;
candles = candles.GroupBy(g => new { TimeBoundary = new DateTime(g.date.Year, g.date.Month, g.date.Day, g.date.Hour, (g.date.Minute / rawPeriodDivisor) * rawPeriodDivisor, 0) })
.Select(s => new kraken_btc
{
date = s.Key.TimeBoundary,
hight = s.Max(z => z.hight),
low = s.Min(z => z.low),
open = s.First().open,
close = s.Last().close,
volume = s.Sum(z => z.volume),
})
.OrderBy(o => o.date)
.ToList();
return candles;
}
And it does the job of aggregating the data, but the problem is I have to wait 30 minutes for my serie finally update if you look at the last candle its time is 17.30 when database has data for 17.47 (im UTC +2.00).
So the question is how can I group data and start draw an incomplete candle at 18.00 like all exchange platform does...
The GroupBy statement rounds down the dates, so all data from 17:30 up to 17:47 is rounded down to 17:30.
I would move the code to calculate the TimeBoundary to its own method so you can unit test it fully, using the RoundUp/RoundDown methods in this question How can I round up the time to the nearest X minutes? by redent84
public static DateTime RoundUp(this DateTime dt, TimeSpan d)
{
var modTicks = dt.Ticks % d.Ticks;
var delta = modTicks != 0 ? d.Ticks - modTicks : 0;
return new DateTime(dt.Ticks + delta, dt.Kind);
}
public static DateTime RoundDown(this DateTime dt, TimeSpan d)
{
var delta = dt.Ticks % d.Ticks;
return new DateTime(dt.Ticks - delta, dt.Kind);
}

c# get next execution datetime

I have the following code:
Dictionary<DayOfWeek, List<TimeSpan>> Daily = new Dictionary<DayOfWeek, List<TimeSpan>>();
The idea is that i can add a day with a time to the Daily dictionary. But a job can execute on the same day more than once.
so Daily can look like this:
{ "Monday" : [{"Hour":10, "Minute": 15}, {"Hour": 8, "Minute":5}] }
Now i would like to get the next execution datetime.
private void UpdateNextExecutionTime()
{
TimeSpan t = new TimeSpan(15, 15, 0);
DayOfWeek current = DateTime.Now.DayOfWeek;
DayOfWeek tmp = current;
TimeSpan time = new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second);
int cur = (int)current;
for(int i = 0; i < 7; i++) {
if(Daily.ContainsKey((DayOfWeek)cur)) {
tmp = (DayOfWeek)cur;
/* stuck */
continue;
}
cur++;
if (cur >= 7)
cur = 0;
}
}
I managed to get the first upcoming day (including today) in the Daily dictionary but i'm stuck on the getting the time.
How would i be able to do this?
Assuming t is the time you're using as the point at which you'd like to find the next execution time, put this where you have 'stuck' (edit; just realized you changed to using TimeSpan in your list):
var execTimes = Daily[tmp];
if (execTimes != null) {
var nextTime = execTimes.OrderBy(x => x).FirstOrDefault(x => x > t);
if (nextTime != default(TimeSpan)) {
// do something...
}
}
You can also have the day in the TimeSpan,
Here is a simple example the finds the next date:
var schedule = new List<TimeSpan>{
new TimeSpan(0,16,30,0),
new TimeSpan(1,16,30,0),
new TimeSpan(5,16,30,0)
};
var monday = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + 1);
var fromMonday = DateTime.Now - monday;
var next = schedule.OrderBy(t => t).FirstOrDefault(t => t > fromMonday);
Console.Write(monday + next - DateTime.Now);

“Invalid data has been used to update the list item. The field you are trying to update may be read only.”

I am trying to update the field in a Sharepoint Discussion board using the Client Object Model in C#. When the code reaches the clientContext.ExecuteQuery() it throws exception as:
Invalid data has been used to update the list item. The field you are trying to update may be read only.
Here is the part of code:
foreach (var field in newItemProperties)
{
if (field.Key.Equals("ContentType"))
continue;
if (ctFields == null)
spItem[field.Key] = field.Value;
else
{
bool foundField = false;
foreach (var fieldCT in ctFields)
{
fieldCT.ReadOnlyField=false;
if (fieldCT.InternalName == field.Key)
{
foundField = true;
if (isAllDayEvent)
{
if (field.Key == "EventDate")
{
// For all day event, Add 10 hour in order to prevent the EventDate to be decreased by 1 day
var dateVal = (DateTime)field.Value;
// spItem[field.Key] = dateVal.Add(new TimeSpan(0, 10, 0, 0, 0));
}
else if (field.Key == "EndDate")
{
// For all day event, Subtract 10 hour in order to prevent the EndDate to be increased by 1 day
var dt = (DateTime)field.Value;
// spItem[field.Key] = dt.Subtract(new TimeSpan(0, 10, 0, 0, 0));
}
else
spItem[field.Key] = field.Value;
}
else
spItem[field.Key] = field.Value;
break;
}
}
if (!foundField)
{
// Update the item properties values even its not part of CT
// MM hidden fields are not part of CT fields
if (isAllDayEvent)
{
if (field.Key == "EventDate")
{
// For all day event, Add 10 hour in order to prevent the EventDate to be decreased by 1 day
var dateVal = (DateTime)field.Value;
//spItem[field.Key] = dateVal.Add(new TimeSpan(0, 10, 0, 0, 0));
}
else if (field.Key == "EndDate")
{
// For all day event, Subtract 10 hour in order to prevent the EndDate to be increased by 1 day
var dt = (DateTime)field.Value;
//spItem[field.Key] = dt.Subtract(new TimeSpan(0, 10, 0, 0, 0));
}
else
spItem[field.Key] = field.Value;
}
else
spItem[field.Key] = field.Value;
}
}
}
spItem.Update();
clientContext.ExecuteQuery();
I am putting my answer here in case people find this question by googling the error message that in the title of this SO question. This answer might help others who come across this strange error message.
I had the same error when I was updating a calendar event with server side code.
My code first added a new empty list item. This new list item has default values for the start date and end date. A few lines later, the list item fields are updated one by one (similar to the op code) and then a list item update is called. In my case, the start date was not being updated by my code and remained the default value. The end date got updated by my code and the end date was earlier in time than the default start date. When the list item update was fired, this error would be displayed in my exception logs.
Once I corrected this and adjusted the start date to always fall before the end date and then call update, the error went away.
Please try the following:
Microsoft.SharePoint.Client.CamlQuery caml = new Microsoft.SharePoint.Client.CamlQuery();
caml.ViewXml = #"<View><Query><Where><Eq><FieldRef Name='FileLeafRef'/><Value Type='File'>" + itemname+ "</Value></Eq></Where></Query></View>";
caml.FolderServerRelativeUrl = relativepath;
Microsoft.SharePoint.Client.ListItemCollection items = list.GetItems(caml);
clientContext.Load(items);
clientContext.Credentials = new NetworkCredential("username","password","domain");
clientContext.ExecuteQuery();
if (items.Count > 0){item["attribute"]=value;}

How can I make this LINQ query cleaner?

I have recently written a LINQ query to get a Dictionary containing the last 6 month's placement amounts.
It is returning a Dictionary of Month string - Decimal Amount pairs.
It seems kind of cludgey. Any of you LINQ masters out there able to help me refactor this to make a bit cleaner?
/// <summary>
/// Gets the last 6 months of Placement History totalled by Month
/// for all Agencies
/// </summary>
/// <returns></returns>
public Dictionary<string, decimal> getRecentPlacementHistory()
{
var placementHistoryByMonth = new Dictionary<string, decimal>();
using (DemoLinqDataContext db = new DemoLinqDataContext())
{
for (int i = 0; i < 6; i++)
{
Decimal monthTotal =
(from a in db.Accounts
where
(a.Date_Assigned.Value.Month == DateTime.Now.AddMonths(-i).Month &&
a.Date_Assigned.Value.Year == DateTime.Now.AddMonths(-i).Month)
select a.Amount_Assigned).Sum();
String currentMonth = DateTime.Now.AddMonths(-i).ToString("MMM");
placementHistoryByMonth.Add(currentMonth, monthTotal);
}
return placementHistoryByMonth;
}
}
First problem:
where (a.Date_Assigned.Value.Month == DateTime.Now.AddMonths(-i).Month &&
a.Date_Assigned.Value.Year == DateTime.Now.AddMonths(-i).Month)
Shouldn't the latter expression end with .Year rather than .Month? Surely you'll rarely get a year with a value of 1-12...
I would extract the idea of the "current month" as you're using it a lot. Note that you're also taking the current time multiple times, which could give odd results if it runs at midnight at the end of a month...
public Dictionary<string, decimal> getRecentPlacementHistory()
{
var placementHistoryByMonth = new Dictionary<string, decimal>();
using (DemoLinqDataContext db = new DemoLinqDataContext())
{
DateTime now = DateTime.Now;
for (int i = 0; i < 6; i++)
{
DateTime selectedDate = now.AddMonths(-i);
Decimal monthTotal =
(from a in db.Accounts
where (a.Date_Assigned.Value.Month == selectedDate.Month &&
a.Date_Assigned.Value.Year == selectedDate.Year)
select a.Amount_Assigned).Sum();
placementHistoryByMonth.Add(selectedDate.ToString("MMM"),
monthTotal);
}
return placementHistoryByMonth;
}
}
I realise it's probably the loop that you were trying to get rid of. You could try working out the upper and lower bounds of the dates for the whole lot, then grouping by the year/month of a.Date_Assigned within the relevant bounds. It won't be much prettier though, to be honest. Mind you, that would only be one query to the database, if you could pull it off.
Use Group By
DateTime now = DateTime.Now;
DateTime thisMonth = new DateTime(now.Year, now.Month, 1);
Dictionary<string, decimal> dict;
using (DemoLinqDataContext db = new DemoLinqDataContext())
{
var monthlyTotal = from a in db.Accounts
where a.Date_Assigned > thisMonth.AddMonths(-6)
group a by new {a.Date_Assigned.Year, a.Date_Assigned.Month} into g
select new {Month = new DateTime(g.Key.Year, g.Key.Month, 1),
Total = g.Sum(a=>a.Amount_Assigned)};
dict = monthlyTotal.OrderBy(p => p.Month).ToDictionary(n => n.Month.ToString("MMM"), n => n.Total);
}
No loop needed!
If you are not worried about missing months with no data,then I had a similar problem where I did the following : (translated to your variables)
DateTime startPeriod =
new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1).AddMonths(-6);
var query1 = from a in db.Accounts where a.Date_Assigned >= startPeriod
group a by new { a.Date_Assigned.Year ,a.Date_Assigned.Month } into result
select new
{
dt = new DateTime( result.Key.Year, result.Key.Month , 1),
MonthTotal = result.Sum(i => i.Amount_Assigned)
} ;
var dict = query1.OrderBy(p=> p.dt).ToDictionary(n => n.Dt.ToString("MMM") , n => n.MonthTotal );

Categories