如何解决Deedle C# 中的 idxmin/idxmax 扩展编辑编辑 2
我有以下系列:Series<new { string,DateTime },double>
。我想知道缩编什么时候开始和结束。可以通过 drawdown.Min().Index
找到提取开始日期。不幸的是,我没有从熊猫中找到类似 .idxmin()
/idxmax()
的方法。如何找到最小和最大索引?
var dataSeries = data.Select(e => keyvalue.Create(new { e.Pair,Date = e.CloseDate },(double)e.ProfitPercentage)).ToSeries();
var cumSum = dataSeries.CumulativeSum();
var cumMax = cumSum.CumulativeMax();
var drawdown = cumSum - cumMax;
扩展
/// <summary>
/// Utility extension methods for deedle series/frames.
/// </summary>
public static class deedleUtils
{
/// <summary>
/// Calculates the cumulative sum for the given series.
/// </summary>
/// <param name="input">Series to calculate cumulative sum for.</param>
/// <returns>Cumulative sum in series form.</returns>
public static Series<T,double> CumulativeSum<T>(this Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
var prev = 0.0;
return input.SelectValues(current =>
{
var sum = prev + current;
prev = sum;
return sum;
});
}
/// <summary>
/// Calculates the cumulative product of the series. This is equal to the python pandas method: `df.cumprod()`.
/// </summary>
/// <param name="input">Input series.</param>
/// <returns>Cumulative product.</returns>
public static Series<T,double> CumulativeProduct<T>(this Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
var prev = 1.0;
return input.SelectValues(current =>
{
var product = prev * current;
prev = product;
return product;
});
}
/// <summary>
/// Calculates the cumulative max of the series. This is equal to the python pandas method: `df.cummax()`.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static Series<T,double> CumulativeMax<T>(this Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
var prevMax = double.NegativeInfinity;
var values = new List<double>();
foreach (var point in input.Values)
{
if (point > prevMax)
{
prevMax = point;
}
values.Add(prevMax);
}
return new Series<T,double>(input.Keys,values);
}
/// <summary>
/// Calculates the percentage change from the prevIoUs value to the current.
/// </summary>
/// <param name="input">Series to calculate percentage change for.</param>
/// <returns>Percentage change in series form.</returns>
/// <remarks>Equivalent to `df.pct_change()`.</remarks>
public static Series<T,double> PercentChange<T>(this Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
var inputShifted = input.Shift(1);
return (input - inputShifted) / inputShifted;
}
/// <summary>
/// Calculates the cumulative returns series of the given input equity curve.
/// </summary>
/// <param name="input">Equity curve series.</param>
/// <returns>Cumulative returns over time.</returns>
public static Series<T,double> CumulativeReturns<T>(this Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
return (input.PercentChange()
.Where(kvp => !double.IsInfinity(kvp.Value)) + 1)
.CumulativeProduct() - 1;
}
/// <summary>
/// Calculates the total returns over a period of time for the given input.
/// </summary>
/// <param name="input">Equity curve series.</param>
/// <returns>Total returns over time.</returns>
public static double TotalReturns<T>(this Series<T,double> input)
{
var returns = input.CumulativeReturns();
if (returns.IsEmpty)
{
return double.NaN;
}
return returns.LastValue();
}
}
编辑
我正在尝试完成类似的事情:
def calculate_max_drawdown(Trades: pd.DataFrame,*,date_col: str = 'close_date',value_col: str = 'profit_percent'
) -> Tuple[float,pd.Timestamp,pd.Timestamp]:
"""
Calculate max drawdown and the corresponding close dates
:param Trades: DataFrame containing Trades (requires columns close_date and profit_percent)
:param date_col: Column in DataFrame to use for dates (defaults to 'close_date')
:param value_col: Column in DataFrame to use for values (defaults to 'profit_percent')
:return: Tuple (float,highdate,lowdate) with absolute max drawdown,high and low time
:raise: ValueError if Trade-dataframe was found empty.
"""
if len(Trades) == 0:
raise ValueError("Trade dataframe empty.")
profit_results = Trades.sort_values(date_col).reset_index(drop=True)
max_drawdown_df = pd.DataFrame()
max_drawdown_df['cumulative'] = profit_results[value_col].cumsum()
max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax()
max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value']
idxmin = max_drawdown_df['drawdown'].idxmin()
if idxmin == 0:
raise ValueError("No losing Trade,therefore no drawdown.")
high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(),date_col]
low_date = profit_results.loc[idxmin,date_col]
return abs(min(max_drawdown_df['drawdown'])),high_date,low_date
编辑 2
using deedle;
using Microsoft.FSharp.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace Resample
{
class Program
{
public class JsonTimestampConverter : DateTimeConverterBase
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(long) || objectType == typeof(string);
}
public override object ReadJson(JsonReader reader,Type objectType,object existingValue,JsonSerializer serializer)
{
long milliseconds;
if (reader.TokenType == JsonToken.Integer)
{
milliseconds = (long)reader.Value!;
}
else if (reader.TokenType == JsonToken.String)
{
if (!long.TryParse((string)reader.Value!,out milliseconds))
{
throw new JsonSerializationException($"Cannot convert invalid value to {objectType}.");
}
}
else
{
throw new JsonSerializationException($"Unexpected token parsing date. Expected Integer or String,got {reader.TokenType}.");
}
return DateTimeOffset.FromUnixTimeMilliseconds(milliseconds).DateTime;
}
public override void WriteJson(JsonWriter writer,object value,JsonSerializer serializer)
{
DateTime utcTime;
if (value is DateTime dateTime)
{
utcTime = DateTime.SpecifyKind(dateTime,DateTimeKind.Utc);
}
else
{
throw new JsonSerializationException("Expected date object value.");
}
writer.WriterawValue($"{((DateTimeOffset)utcTime).ToUnixTimeMilliseconds()}");
}
}
public class BResult
{
[JsonProperty("pair")]
public string Pair { get; set; }
[JsonProperty("profit_percent")]
public decimal ProfitPercentage { get; set; }
[JsonProperty("profit_abs")]
public decimal ProfitAbs { get; set; }
[JsonProperty("open_rate")]
public decimal OpenRate { get; set; }
[JsonProperty("close_rate")]
public decimal CloseRate { get; set; }
[JsonProperty("open_date")]
[JsonConverter(typeof(JsonTimestampConverter))]
public DateTime OpenDate { get; set; }
[JsonProperty("close_date")]
[JsonConverter(typeof(JsonTimestampConverter))]
public DateTime CloseDate { get; set; }
[JsonProperty("open_fee")]
public decimal OpenFee { get; set; }
[JsonProperty("close_fee")]
public decimal CloseFee { get; set; }
[JsonProperty("amount")]
public decimal Amount { get; set; }
[JsonProperty("Trade_duration")]
public decimal TradeDuration { get; set; }
[JsonProperty("open_at_end")]
public bool OpenAtEnd { get; set; }
[JsonProperty("sell_reason")]
public string 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/0bASqR47");
// Deserialize the data
var data = JsonConvert.DeserializeObject<List<BResult>>(json);
// Summary
foreach (var result in data.GroupBy(e => e.Pair)
.Select(e => new { Pair = e.Key,Count = e.Count(),Value = e }))
{
var pairsCount = 1;
var key = result.Pair;
var Trades = result.Count;
var profitSum = result.Value.Sum(e => e.ProfitPercentage);
var profitSumPercentage = profitSum * 100;
var profitTotal = profitSum / pairsCount;
var profitTotalPercentage = profitTotal * 100;
Console.WriteLine($"Cumulative Profit %: {profitSumPercentage:f2}% | Total Profit %: {profitTotalPercentage:f2}%");
}
// Create series
var series = data.Select(e => keyvalue.Create(e.CloseDate,e)).ToSeries();
// Resample data
// daily_profit = results.resample('1d',on = 'close_date')['profit_percent'].sum()
var dailyProfit = series.ResampleEquivalence(dt => new DateTime(dt.Year,dt.Month,dt.Day,DateTimeKind.Utc),group => group.SelectValues(g => g.ProfitPercentage).Sum());
// backtest_worst_day = min(daily_profit) // -0.26123255999999995
var worstDay = dailyProfit.Min();
// backtest_best_day = max(daily_profit) // 0.029468
var bestDay = dailyProfit.Max();
// winning_days = sum(daily_profit > 0) // 11
var winningDays = dailyProfit.SelectValues(x => x > 0).Sum();
// draw_days = sum(daily_profit == 0) // 0
var drawDays = dailyProfit.SelectValues(x => x == 0).Sum();
// losing_days = sum(daily_profit < 0) // 20
var losingDays = dailyProfit.SelectValues(x => x < 0).Sum();
// Summary on daily basis
Console.WriteLine($"Best Day: {bestDay:p2} | Worst Day: {worstDay:p2}");
Console.WriteLine($"Days win/draw/lose: {winningDays} / {drawDays} / {losingDays}");
var dataSeries = data.Select(e => keyvalue.Create(new { e.Pair,(double)e.ProfitPercentage)).ToSeries();
var cumSum = dataSeries.CumulativeSum();
var cumMax = cumSum.CumulativeMax();
var drawdown = cumSum - cumMax;
var maxDrawdown = Math.Abs(drawdown.Min());
var drawdownStart = DateTime.Now; //cumMax.IndexMax(); // Date = {11/7/2020 11:35:00 AM}
var drawdownEnd = DateTime.Now; //drawdown.IndexMin(); // Date = {12/5/2020 12:00:00 AM}
// expected Max drawdown: $129.56% | Max: $11/7/2020 11:35:00 AM | Min: $12/5/2020 12:00:00 AM
Console.WriteLine($"Max drawdown: ${maxDrawdown:p2} | Max: ${drawdownStart} | Min: ${drawdownEnd}");
Console.ReadLine();
}
}
/// <summary>
/// Utility extension methods for deedle series/frames.
/// </summary>
public static class deedleUtils
{
/// <summary>
/// Calculates the cumulative sum for the given series.
/// </summary>
/// <param name="input">Series to calculate cumulative sum for.</param>
/// <returns>Cumulative sum in series form.</returns>
public static Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
var prev = 0.0;
return input.SelectValues(current =>
{
var sum = prev + current;
prev = sum;
return sum;
});
}
/// <summary>
/// Calculates the cumulative product of the series. This is equal to the python pandas method: `df.cumprod()`.
/// </summary>
/// <param name="input">Input series.</param>
/// <returns>Cumulative product.</returns>
public static Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
var prev = 1.0;
return input.SelectValues(current =>
{
var product = prev * current;
prev = product;
return product;
});
}
/// <summary>
/// Calculates the cumulative max of the series. This is equal to the python pandas method: `df.cummax()`.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
var prevMax = double.NegativeInfinity;
var values = new List<double>();
foreach (var point in input.Values)
{
if (point > prevMax)
{
prevMax = point;
}
values.Add(prevMax);
}
return new Series<T,values);
}
/// <summary>
/// Calculates the percentage change from the prevIoUs value to the current.
/// </summary>
/// <param name="input">Series to calculate percentage change for.</param>
/// <returns>Percentage change in series form.</returns>
/// <remarks>Equivalent to `df.pct_change()`.</remarks>
public static Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
var inputShifted = input.Shift(1);
return (input - inputShifted) / inputShifted;
}
/// <summary>
/// Calculates the cumulative returns series of the given input equity curve.
/// </summary>
/// <param name="input">Equity curve series.</param>
/// <returns>Cumulative returns over time.</returns>
public static Series<T,double> input)
{
if (input.IsEmpty)
{
return input;
}
return (input.PercentChange()
.Where(kvp => !double.IsInfinity(kvp.Value)) + 1)
.CumulativeProduct() - 1;
}
/// <summary>
/// Calculates the total returns over a period of time for the given input.
/// </summary>
/// <param name="input">Equity curve series.</param>
/// <returns>Total returns over time.</returns>
public static double TotalReturns<T>(this Series<T,double> input)
{
var returns = input.CumulativeReturns();
if (returns.IsEmpty)
{
return double.NaN;
}
return returns.LastValue();
}
}
}
解决方法
您是对的,没有像 idmin
和 idmax
这样的内置函数。这将是一个有用的补充,因此如果您可以将此作为建议发布到 Deedle GitHub(或者甚至可能有助于实施它),那就太棒了。
与此同时,我认为最简单的解决方案是访问系列的 Observations
(它为您提供 IEnumerable<KeyValuePair<K,V>>
)并使用标准的 LINQ Min
或 Max
方法:
var minKvp = dataSeries.Observations.Min(kvp => kvp.Value);
var maxKvp = dataSeries.Observations.Max(kvp => kvp.Value);
然后您可以使用 minKvp.Key
获取密钥,使用 minKvp.Value
获取最小值/最大值。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。