微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Deedle C# 中的 idxmin/idxmax 扩展编辑编辑 2

如何解决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();
        }
    }
}

解决方法

您是对的,没有像 idminidmax 这样的内置函数。这将是一个有用的补充,因此如果您可以将此作为建议发布到 Deedle GitHub(或者甚至可能有助于实施它),那就太棒了。

与此同时,我认为最简单的解决方案是访问系列的 Observations(它为您提供 IEnumerable<KeyValuePair<K,V>>)并使用标准的 LINQ MinMax方法:

var minKvp = dataSeries.Observations.Min(kvp => kvp.Value);
var maxKvp = dataSeries.Observations.Max(kvp => kvp.Value);

然后您可以使用 minKvp.Key 获取密钥,使用 minKvp.Value 获取最小值/最大值。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。