vdemydiuk / mtapi

MetaTrader API (terminal bridge)
MIT License
530 stars 285 forks source link

double should be replaced by decimal #195

Open arteny opened 4 years ago

arteny commented 4 years ago

As soon we are working in the financial field all these objects like ask, bid, last, high, low, volume should be decimal instead of double. For instance, we can't compare doubles directly without add precision.

vdemydiuk commented 4 years ago

Good point. We need to think how it can be implemented without back capability broken.

mbochmann commented 4 years ago

It's not possible to directly switch from double to decimal without breaking compatibility.

But you can add a custom type like MtDouble with implicit operators, which behaves like a double or like a decimal regarding what the code requests:

    public struct MtDouble
    {
        const string DOUBLE_OBSOLETE = "Doubles should not be used in financial operations. This will be removed in a later version, please use decimal instead.";
        private decimal _value;
        public MtDouble(double val) => _value = (decimal)val;
        public MtDouble(decimal val) => _value = val;
        [Obsolete(DOUBLE_OBSOLETE)]
        public static implicit operator double(MtDouble mtDouble) => (double)mtDouble._value;
        public static implicit operator decimal(MtDouble mtDouble) => mtDouble._value;
        [Obsolete(DOUBLE_OBSOLETE)]
        public static implicit operator MtDouble(double val) => new MtDouble(val);
        public static implicit operator MtDouble(decimal val) => new MtDouble(val);
    }

usage:

    MtDouble doubleValue = 5.0d;
    MtDouble decimalValue = 5.0m;
    double conversion1 = decimalValue;
    double conversion2 = doubleValue;
    decimal conversion3 = decimalValue;
    decimal conversion4 = doubleValue;

Thanks to the Obsolete-Attribute, the dev get a hint everytime he uses a MtDouble as double: image

Then you can just replace double with MtDouble e.g. in MtOrder:

public class MtOrder
    {
        public int Ticket { get; set; }
        public string Symbol { get; set; }
        public TradeOperation Operation { get; set; }
        public MtDouble OpenPrice { get; set; }
        public MtDouble ClosePrice { get; set; }
        public MtDouble Lots { get; set; }
        public int MtOpenTime { get; set; }
        public int MtCloseTime { get; set; }
        public MtDouble Profit { get; set; }
        public string Comment { get; set; }
        public MtDouble Commission { get; set; }
        public int MagicNumber { get; set; }
        public MtDouble Swap { get; set; }
        public int MtExpiration { get; set; }
        public MtDouble TakeProfit { get; set; }
        public MtDouble StopLoss { get; set; }

        public DateTime OpenTime
        {
            get { return MtApiTimeConverter.ConvertFromMtTime(MtOpenTime); }
        }

        public DateTime CloseTime
        {
            get { return MtApiTimeConverter.ConvertFromMtTime(MtCloseTime); }
        }

        public DateTime Expiration
        {
            get { return MtApiTimeConverter.ConvertFromMtTime(MtExpiration); }
        }
    }
vdemydiuk commented 4 years ago

@mbochmann Excellent. I think your solution can be used as temporary fix for migration from double to decimal.