Resampling with Deedle duplicate keys - c#

My code below resamples 5-minute interval to 1-day interval for the daily profit stats. The problem is that BacktestResult consists of duplicate CloseDate values, because I'm testing with multiple pairs (TRXUSDT, ETHUSDT and BTCUSDT). dailyProfit returns Series<DateTime, double>, which explains the exception. How can I make it grouped by Pair or something? It works fine when tested with one pair.
// Create series
var series = _backtestResults.ToOrdinalSeries();
// daily_profit = results.resample('1d', on = 'close_date')['profit_percent'].sum()
var dailyProfit = series.ResampleEquivalence(
index => new DateTime(series[index].CloseDate.Year, series[index].CloseDate.Month, series[index].CloseDate.Day, 0, 0, 0, DateTimeKind.Utc),
group => group.SelectValues(g => g.ProfitPercentage).Sum()).DropMissing();
// classes
public class BacktestResult
{
public string Pair { get; set; }
public decimal ProfitPercentage { get; set; }
public decimal ProfitAbs { get; set; }
public decimal OpenRate { get; set; }
public decimal CloseRate { get; set; }
public DateTime OpenDate { get; set; }
public DateTime CloseDate { get; set; }
public decimal OpenFee { get; set; }
public decimal CloseFee { get; set; }
public decimal Amount { get; set; }
public decimal TradeDuration { get; set; }
public SellType SellReason { get; set; }
}
Edit:
Example which takes the JSON data from pastebin:
using Deedle;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace Resample
{
class Program
{
public class BacktestResultTest
{
public string Pair { get; set; }
public decimal ProfitPercentage { get; set; }
public decimal ProfitAbs { get; set; }
public decimal OpenRate { get; set; }
public decimal CloseRate { get; set; }
public DateTime OpenDate { get; set; }
public DateTime CloseDate { get; set; }
public decimal OpenFee { get; set; }
public decimal CloseFee { get; set; }
public decimal Amount { get; set; }
public decimal TradeDuration { get; set; }
public bool OpenAtEnd { get; set; }
public int SellReason { get; set; }
}
static void Main(string[] args)
{
// Take JSON data from pastebin
using var webClient = new WebClient();
var json = webClient.DownloadString("https://pastebin.com/raw/Dhp9202f");
// Deserialize the data
var data = JsonConvert.DeserializeObject<List<BacktestResultTest>>(json);
var ts = data.ToOrdinalSeries();
var byDateAndPair = ts.SelectKeys(kvp => Tuple.Create(kvp.Value.Value.CloseDate, kvp.Value.Value.Pair)).SortByKey();
// daily_profit = results.resample('1d', on = 'close_date')['profit_percent'].sum()
var dailyProfit2 = byDateAndPair.ResampleEquivalence(
k => Tuple.Create(new DateTime(k.Item1.Year, k.Item1.Month, k.Item1.Day), k.Item2),
g => g.Select(kvp => kvp.Value.ProfitPercentage).Sum());
// backtest_worst_day = min(daily_profit)
var worstDay2 = dailyProfit2.Min();
// backtest_best_day = max(daily_profit)
var bestDay2 = dailyProfit2.Max();
// winning_days = sum(daily_profit > 0)
var winningDays2 = dailyProfit2.SelectValues(x => x > 0).Sum();
// draw_days = sum(daily_profit == 0)
var drawDays2 = dailyProfit2.SelectValues(x => x == 0).Sum();
// losing_days = sum(daily_profit < 0)
var losingDays2 = dailyProfit2.SelectValues(x => x < 0).Sum();
Console.ReadLine();
}
}
}

You can use a custom data type as a key in Deedle. If you want to be able to use resampling on the series, then this needs to support IComparable. You can either define your own type or use built-in Tuple.
Assuming we have some very basic data:
var ts =
new[] {
KeyValue.Create(new DateTime(2020,1,1), new { Value = 1.0, Kind = "A" }),
KeyValue.Create(new DateTime(2020,1,2), new { Value = 1.0, Kind = "A" }),
KeyValue.Create(new DateTime(2020,1,3), new { Value = 1.0, Kind = "B" }),
KeyValue.Create(new DateTime(2020,1,4), new { Value = 1.0, Kind = "B" }),
}.ToSeries();
The first thing we need to do is to change the key to be the date together with a kind. (In fact, you can get into trouble earlier in your code if you had duplicate dates!)
var byDateAndKind =
ts.SelectKeys(kvp => Tuple.Create(kvp.Key, kvp.Value.Value.Kind)).SortByKey();
Now the key is Tuple<DateTime, string> consisting of the date and the kind. You can now use ResampleEquivalence on this. Here, we use year and kind as the new key and sum values in group:
var aggByYearAndKind =
byDateAndKind.ResampleEquivalence(
(k) => Tuple.Create(k.Item1.Year, k.Item2),
(g) => g.Select(kvp => kvp.Value.Value).Sum());
aggByYearAndKind.Print();
This will print a series that maps 2020, "A" to 2 and also 2020, "B" to 2.
EDIT You are right - this does not seem to work. I was able to get it to work using GroupBy instead of ResampleEquvialence:
var dailyProfit2 =
ts.GroupBy(kvp =>
new { Date = new DateTime(kvp.Value.CloseDate.Year, kvp.Value.CloseDate.Month, kvp.Value.CloseDate.Day), Kind = kvp.Value.Pair })
.SelectValues(g => g.Select(kvp => kvp.Value.ProfitPercentage).Values.Sum());
// backtest_worst_day = min(daily_profit)
var worstDay2 = dailyProfit2.Min();
// backtest_best_day = max(daily_profit)
var bestDay2 = dailyProfit2.Max();
// winning_days = sum(daily_profit > 0)
var winningDays2 = dailyProfit2.Where(x => x.Value > 0).Values.Sum();
// draw_days = sum(daily_profit == 0)
var drawDays2 = dailyProfit2.Where(x => x.Value == 0).Values.Sum();
// losing_days = sum(daily_profit < 0)
var losingDays2 = dailyProfit2.Where(x => x.Value < 0).Values.Sum();

Related

Unable to get Additional Value from Dictionary

I have a dictionary collection which consist "TextDescription" and "TextValues" (List Collection) which has two objects "Date","Text"
"TextDescription":"Your payment due date is dec 12 on every month"
0 th Item
"Date"=> "12/12/2018"
"Text"=>"A,B,C,D,E,F"
1 st Item
"Date"=> "12/12/2019"
"Text"=>"G,H,I,J,K,L"
I have to split the "Text" object with it's comma separated values and add it in a list so I achieved it from below code
var lstDictionaryItems = cache.Get(CacheKeys.AutoText) as Dictionary<string, Autotext>;
var txtItemsList = lstDictionaryItems.FirstOrDefault(i => i.Key.ToString() == _languageIndicator.ToString()).Value.
TextValues.SelectMany(st => st.Text.Split(',').Select(x => new TextValues { Date = x.Trim(), Text = st.Text }));
However I have achieved the Text and Date fields in a separate list item but I left with "TextDescription".
txtItemsList doesn't have the property "TextDescription" how can I get this in txtItemList object
Here is my class structure
public class AutoText
{
public string TextDescription{ get; set; }
public List<TextValues> TextValues{ get; set; }
}
public class TextValues
{
public string Date { get; set; }
public string Text{ get; set; }
}
See following :
class Program
{
static void Main(string[] args)
{
List<List<string>> inputs = new List<List<string>>(){
new List<string>(){"Your payment due date is dec 12 on every month","12/12/2018","A,B,C,D,E,F"},
new List<string>(){"Your payment due date is dec 12 on every month","12/12/2019","G,H,I,J,K,L"}
};
var listInput = inputs.Select(x => new {TextDescription = x[0], Date = DateTime.Parse(x[1]), Text = x[2]}).ToList();
Dictionary<DateTime, AutoText> dict = listInput.Select(x => new AutoText()
{
TextDescription = x.TextDescription,
TextValues = x.Text.Split(new char[] { ',' }).Select(y => new TextValues() { Date = x.Date, Text = y }).ToList()
}).GroupBy(x => x.TextValues.FirstOrDefault().Date, y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
public class AutoText
{
public string TextDescription { get; set; }
public List<TextValues> TextValues { get; set; }
}
public class TextValues
{
public DateTime Date { get; set; }
public string Text { get; set; }
}

Map one class data to another class with iteration

I have a C# project and looking for simple solution for map one class object data to list of another class object.
This is my input class
public class RatesInput
{
public string Type1 { get; set; }
public string Break1 { get; set; }
public string Basic1 { get; set; }
public string Rate1 { get; set; }
public string Type2 { get; set; }
public string Break2 { get; set; }
public string Basic2 { get; set; }
public string Rate2 { get; set; }
public string Type3 { get; set; }
public string Break3 { get; set; }
public string Basic3 { get; set; }
public string Rate3 { get; set; }
}
This is my another class structure
public class RateDetail
{
public string RateType { get; set; }
public decimal Break { get; set; }
public decimal Basic { get; set; }
public decimal Rate { get; set; }
}
it has a object like below. (For easiering the understanding, I use hardcoded values and actually values assign from a csv file)
RatesInput objInput = new RatesInput();
objInput.Type1 = "T";
objInput.Break1 = 100;
objInput.Basic1 = 50;
objInput.Rate1 = 0.08;
objInput.Type2 = "T";
objInput.Break2 = 200;
objInput.Basic2 = 50;
objInput.Rate2 = 0.07;
objInput.Type3 = "T";
objInput.Break3 = 500;
objInput.Basic3 = 50;
objInput.Rate3 = 0.06;
Then I need to assign values to "RateDetail" list object like below.
List<RateDetail> lstDetails = new List<RateDetail>();
//START Looping using foreach or any looping mechanism
RateDetail obj = new RateDetail();
obj.RateType = //first iteration this should be assigned objInput.Type1, 2nd iteration objInput.Type2 etc....
obj.Break = //first iteration this should be assigned objInput.Break1 , 2nd iteration objInput.Break2 etc....
obj.Basic = //first iteration this should be assigned objInput.Basic1 , 2nd iteration objInput.Basic2 etc....
obj.Rate = //first iteration this should be assigned objInput.Rate1, 2nd iteration objInput.Rate2 etc....
lstDetails.Add(obj); //Add obj to the list
//END looping
Is there any way to convert "RatesInput" class data to "RateDetail" class like above method in C#? If yes, how to iterate data set?
Try this:
public class RatesList : IEnumerable<RateDetail>
{
public RatesList(IEnumerable<RatesInput> ratesInputList)
{
RatesInputList = ratesInputList;
}
private readonly IEnumerable<RatesInput> RatesInputList;
public IEnumerator<RateDetail> GetEnumerator()
{
foreach (var ratesInput in RatesInputList)
{
yield return new RateDetail
{
RateType = ratesInput.Type1,
Break = Convert.ToDecimal(ratesInput.Break1, new CultureInfo("en-US")),
Basic = Convert.ToDecimal(ratesInput.Basic1, new CultureInfo("en-US")),
Rate = Convert.ToDecimal(ratesInput.Rate1, new CultureInfo("en-US"))
};
yield return new RateDetail
{
RateType = ratesInput.Type2,
Break = Convert.ToDecimal(ratesInput.Break2),
Basic = Convert.ToDecimal(ratesInput.Basic2),
Rate = Convert.ToDecimal(ratesInput.Rate2, new CultureInfo("en-US"))
};
yield return new RateDetail
{
RateType = ratesInput.Type3,
Break = Convert.ToDecimal(ratesInput.Break3),
Basic = Convert.ToDecimal(ratesInput.Basic3),
Rate = Convert.ToDecimal(ratesInput.Rate3, new CultureInfo("en-US"))
};
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
And use:
var list = new RatesList(new List<RatesInput>() { objInput });
foreach (var item in list)
{
Console.WriteLine(item.Basic);
}
You can use Reflection to get the properties info like this:
var props = objInput.GetType().GetProperties();
var types = props.Where(x => x.Name.StartsWith("Type"))
.Select(x => x.GetValue(objInput)).ToList();
var breaks = props.Where(x => x.Name.StartsWith("Break"))
.Select(x => x.GetValue(objInput)).ToList();
var basics = props.Where(x => x.Name.StartsWith("Basic"))
.Select(x => x.GetValue(objInput)).ToList();
var rates = props.Where(x => x.Name.StartsWith("Rate"))
.Select(x => x.GetValue(objInput)).ToList();
List<RateDetail> lstDetails = new List<RateDetail>();
for (int i = 0; i < types.Count; i++)
{
lstDetails.Add(new RateDetail
{
RateType = types[i].ToString(),
Break = Convert.ToDecimal(breaks[i]),
Basic = Convert.ToDecimal(basics[i]),
Rate = Convert.ToDecimal(rates[i])
});
}

Automapper map child object based on parent value

If the header object has a prop set to 1 then it should map field type1 in the child to type in the destination. Otherwise it should use type2.
Bonus points if I can use IValueResolver to use type1 or type1extended if extended is filled.
Here is my minimum viable product/demo
using AutoMapper;
using AutoMapper.Configuration.Conventions;
using System;
using System.Collections.Generic;
namespace ConsoleAppAutoMapper
{
class Program
{
static void Main(string[] args)
{
var source = new SourceParent() {
Header = new SourceHeader() { Currency = 30, FileName = "testfile.txt", Type = 1 },
Rows = new List<SourceRow>() {
new SourceRow() { ID = 1, Amount1 = 100, Amount2 = 200 },
new SourceRow() { ID = 2, Amount1 = 101, Amount2 = 201 },
new SourceRow() { ID = 3, Amount1 = 102, Amount2 = 202 }
} };
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<SourceParent, DestinationParent>();
cfg.CreateMap<SourceRow, DestinationRow>()
.ForMember(x => x.Type, opt => opt.MapFrom(p => p.Type1));
});
var mapper = config.CreateMapper();
var dest = mapper.Map<DestinationParent>(source);
Console.WriteLine(dest.Rows[0].Type == 100); // should be true if SourceHeader.Type = 1 and should be 200 (SourceRow.Type2) if SourceHeader.Type = 2
Console.ReadKey();
}
}
// source
public class SourceParent
{
public SourceHeader Header { get; set; }
public List<SourceRow> Rows { get; set; }
}
public class SourceHeader
{
public string FileName { get; set; }
public int Type { get; set; }
}
public class SourceRow
{
public int ID { get; set; }
public int Amount1 { get; set; }
public int Amount2 { get; set; }
}
//destination
public class DestinationParent
{
public DestinationHeader Header { get; set; }
public List<DestinationRow> Rows { get; set; }
}
public class DestinationHeader
{
public string FileName { get; set; }
}
public class DestinationRow
{
public int ID { get; set; }
public int Type { get; set; }
public int Amount{ get; set; } // if type=1 then source is amount1 otherwise amount2
}
}
edit
I tried to solve it by having an Aftermap on the sourceparent mapping which took the value from the header and put it in a prop from the destinationrow (it is the Type value) and wanted another aftermap on the row to see if I needed prop A or B (type1 or type2) but that aftermap still does not know (it's null) what type it is because it happens before the aftermap of the parent it seems.
public class MapRowType : IMappingAction<SourceParent, DestinationParent>
{
public void Process(SourceParentsource, DestinationParent destination)
{
foreach (var row in destination.Rows)
{
row.Type = source.Header.Type; // so now I have type in the row, but still do not know if I should use Amount1 or Amount2
}
}
}
you can use the resolution context. Declare the mapping:
cfg.CreateMap<SourceRow, DestinationRow>()
.ForMember(x => x.Type,
opt => opt.ResolveUsing((src, dest1, destMember, resContext) => resContext.Items["Type"] as int? == 1? src.Type2: src.Type1));
After pass the value:
var dest = mapper.Map<DestinationParent>(source, opts=> { opts.Items["Type"] = source.Header.Type;});

Sorting and Updating a Generic List of Object based on a Sub Object

I have the following objects:
public class TestResult
{
public string SectionName { get; set; }
public int Score { get; set; }
public int MaxSectionScore { get; set; }
public bool IsPartialScore { get; set; }
public string Name { get; set; }
public int NumberOfAttempts { get; set; }
}
public class TestResultGroup
{
public TestResultGroup()
{
Results = new List<TestResult>();
Sections = new List<string>();
}
public List<TestResult> Results { get; set; }
public List<string> Sections { get; set; }
public string Name { get; set; }
public int Rank { get; set; }
}
So, a TestResultGroup can have any number of results of type TestResult. These test results only differ by their SectionName.
I have a List<TestResultGroup> which I need to sort into descending order based on a score in the Results property, but only when Results has an item whos SectionName = "MeanScore" (if it doesnt have this section we can assume a score of -1). How would I go about ordering the list? Ideally I would also like to apply the result of this ordering to the Rank property.
Many Thanks
List<TestResultGroup> groups = ...
// group test result groups by the same score and sort
var sameScoreGroups = groups.GroupBy(
gr =>
{
var meanResult = gr.Results.FirstOrDefault(res => res.SectionName == "MeanScore");
return meanResult != null ? meanResult.Score : -1;
})
.OrderByDescending(gr => gr.Key);
int rank = 1;
foreach (var sameScoreGroup in sameScoreGroups)
{
foreach (var group in sameScoreGroup)
{
group.Rank = rank;
}
rank++;
}
// to obtain sorted groups:
var sortedGroups = groups.OrderByDescending(gr => gr.Rank).ToArray();
Or even write one expression with a side effect:
List<TestResultGroup> groups = ...
int rank = 1;
var sortedGroups = groups
.GroupBy(
gr =>
{
var meanResult = gr.Results.FirstOrDefault(res => res.SectionName == "MeanScore");
return meanResult != null ? meanResult.Score : -1;
})
.OrderByDescending(grouping => grouping.Key)
.SelectMany(grouping =>
{
int groupRank = rank++;
foreach (var group in grouping)
{
group.Rank = groupRank;
}
return grouping;
})
.ToArray(); // or ToList

How to use LINQ to assign while selecting to new object

I have a data contract called GameImage and GameTone. I am trying to join the two entities, and assign a unique random position between 0-11 to an Image/Tone association. I am able to join the tables but am unsure if there is a way to assign the position while creating the object in a LINQ lambda expression.
// Need random positions from 0-11 to to be associated to an image/tone
var positions = Enumerable.Range(0, 11).Shuffle().ToList();
// Associate image/tones
imageToneData = game.GameImages.Shuffle()
.Join(game.GameTones, gi => gi.GameId, gt => gt.GameId, (gi, gt) => new ImageToneData
{
Image = new ImageData()
{
ImageFileName = gi.Image.ImageFileName,
ImageId = gi.ImageId
},
Tone = new ToneData()
{
ToneFileName = gt.Tone.ToneFileName,
ToneId = gt.ToneId
},
Position = // What goes here?
});
These are my data contracts
[DataContract]
public class ImageToneData
{
[DataMember]
public ImageData Image { get; set; }
[DataMember]
public ToneData Tone { get; set; }
[DataMember]
public int Position { get; set; }
}
[DataContract]
public class ImageData
{
[DataMember]
public int ImageId { get; set; }
[DataMember]
public string ImageFileName { get; set; }
}
}
[DataContract]
public class ToneData
{
[DataMember]
public int ToneId { get; set; }
[DataMember]
public string ToneFileName { get; set; }
}
var positions = Enumerable.Range(0, 11).OrderBy(a => Guid.NewGuid()).ToList();
// Associate image/tones
imageToneData = game.GameImages.Shuffle()
.Join(game.GameTones, gi => gi.GameId, gt => gt.GameId, (gi, gt) => new ImageToneData
{
Image = new ImageData()
{
ImageFileName = gi.Image.ImageFileName,
ImageId = gi.ImageId
},
Tone = new ToneData()
{
ToneFileName = gt.Tone.ToneFileName,
ToneId = gt.ToneId
},
Position = positions.First()
});

Categories

Resources