mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 17:36:59 +01:00
Make sure the rate of the merchant is using the ask of a divided exchange
This commit is contained in:
parent
ead67887ab
commit
ba3d13d56c
9 changed files with 253 additions and 48 deletions
|
@ -129,19 +129,19 @@ namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
Exchange = "coinaverage",
|
Exchange = "coinaverage",
|
||||||
CurrencyPair = CurrencyPair.Parse("BTC_USD"),
|
CurrencyPair = CurrencyPair.Parse("BTC_USD"),
|
||||||
Value = 5000m
|
BidAsk = new BidAsk(5000m)
|
||||||
});
|
});
|
||||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
{
|
{
|
||||||
Exchange = "coinaverage",
|
Exchange = "coinaverage",
|
||||||
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
|
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
|
||||||
Value = 4500m
|
BidAsk = new BidAsk(4500m)
|
||||||
});
|
});
|
||||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
{
|
{
|
||||||
Exchange = "coinaverage",
|
Exchange = "coinaverage",
|
||||||
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
|
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
|
||||||
Value = 500m
|
BidAsk = new BidAsk(500m)
|
||||||
});
|
});
|
||||||
rateProvider.DirectProviders.Add("coinaverage", coinAverageMock);
|
rateProvider.DirectProviders.Add("coinaverage", coinAverageMock);
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,12 +94,12 @@ namespace BTCPayServer.Tests
|
||||||
Assert.Equal(test.ExpectedExchangeRates, string.Join(',', rule.ExchangeRates.OfType<object>().ToArray()));
|
Assert.Equal(test.ExpectedExchangeRates, string.Join(',', rule.ExchangeRates.OfType<object>().ToArray()));
|
||||||
}
|
}
|
||||||
var rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_CAD"));
|
var rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_CAD"));
|
||||||
rule2.ExchangeRates.SetRate("bittrex", CurrencyPair.Parse("DOGE_BTC"), 5000);
|
rule2.ExchangeRates.SetRate("bittrex", CurrencyPair.Parse("DOGE_BTC"), new BidAsk(5000m));
|
||||||
rule2.Reevaluate();
|
rule2.Reevaluate();
|
||||||
Assert.True(rule2.HasError);
|
Assert.True(rule2.HasError);
|
||||||
Assert.Equal("5000 * ERR_RATE_UNAVAILABLE(coinbase, BTC_CAD) * 1.1", rule2.ToString(true));
|
Assert.Equal("5000 * ERR_RATE_UNAVAILABLE(coinbase, BTC_CAD) * 1.1", rule2.ToString(true));
|
||||||
Assert.Equal("bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", rule2.ToString(false));
|
Assert.Equal("bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", rule2.ToString(false));
|
||||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), 2000.4m);
|
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(2000.4m));
|
||||||
rule2.Reevaluate();
|
rule2.Reevaluate();
|
||||||
Assert.False(rule2.HasError);
|
Assert.False(rule2.HasError);
|
||||||
Assert.Equal("5000 * 2000.4 * 1.1", rule2.ToString(true));
|
Assert.Equal("5000 * 2000.4 * 1.1", rule2.ToString(true));
|
||||||
|
@ -116,7 +116,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD"));
|
rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD"));
|
||||||
Assert.Equal("(2000 * (-3 + coinbase(BTC_CAD) + 50 - 5)) * 1.1", rule2.ToString());
|
Assert.Equal("(2000 * (-3 + coinbase(BTC_CAD) + 50 - 5)) * 1.1", rule2.ToString());
|
||||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), 1000m);
|
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(1000m));
|
||||||
Assert.True(rule2.Reevaluate());
|
Assert.True(rule2.Reevaluate());
|
||||||
Assert.Equal("(2000 * (-3 + 1000 + 50 - 5)) * 1.1", rule2.ToString(true));
|
Assert.Equal("(2000 * (-3 + 1000 + 50 - 5)) * 1.1", rule2.ToString(true));
|
||||||
Assert.Equal((2000m * (-3m + 1000m + 50m - 5m)) * 1.1m, rule2.Value.Value);
|
Assert.Equal((2000m * (-3m + 1000m + 50m - 5m)) * 1.1m, rule2.Value.Value);
|
||||||
|
@ -124,7 +124,7 @@ namespace BTCPayServer.Tests
|
||||||
// Test inverse
|
// Test inverse
|
||||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_DOGE"));
|
rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_DOGE"));
|
||||||
Assert.Equal("(1 / (2000 * (-3 + coinbase(BTC_CAD) + 50 - 5))) * 1.1", rule2.ToString());
|
Assert.Equal("(1 / (2000 * (-3 + coinbase(BTC_CAD) + 50 - 5))) * 1.1", rule2.ToString());
|
||||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), 1000m);
|
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(1000m));
|
||||||
Assert.True(rule2.Reevaluate());
|
Assert.True(rule2.Reevaluate());
|
||||||
Assert.Equal("(1 / (2000 * (-3 + 1000 + 50 - 5))) * 1.1", rule2.ToString(true));
|
Assert.Equal("(1 / (2000 * (-3 + 1000 + 50 - 5))) * 1.1", rule2.ToString(true));
|
||||||
Assert.Equal(( 1.0m / (2000m * (-3m + 1000m + 50m - 5m))) * 1.1m, rule2.Value.Value);
|
Assert.Equal(( 1.0m / (2000m * (-3m + 1000m + 50m - 5m))) * 1.1m, rule2.Value.Value);
|
||||||
|
@ -135,8 +135,22 @@ namespace BTCPayServer.Tests
|
||||||
builder.AppendLine("BTC_USD = kraken(BTC_USD)");
|
builder.AppendLine("BTC_USD = kraken(BTC_USD)");
|
||||||
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
||||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("BTC_USD"));
|
rule2 = rules.GetRuleFor(CurrencyPair.Parse("BTC_USD"));
|
||||||
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), 1000m);
|
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(1000m));
|
||||||
Assert.True(rule2.Reevaluate());
|
Assert.True(rule2.Reevaluate());
|
||||||
|
|
||||||
|
// Make sure can handle pairs
|
||||||
|
builder = new StringBuilder();
|
||||||
|
builder.AppendLine("BTC_USD = kraken(BTC_USD)");
|
||||||
|
rule2 = rules.GetRuleFor(CurrencyPair.Parse("BTC_USD"));
|
||||||
|
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||||
|
Assert.True(rule2.Reevaluate());
|
||||||
|
Assert.Equal("(6000, 6100)", rule2.ToString(true));
|
||||||
|
Assert.Equal(6000m, rule2.Value.Value);
|
||||||
|
rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_BTC"));
|
||||||
|
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||||
|
Assert.True(rule2.Reevaluate());
|
||||||
|
Assert.Equal("1 / (6000, 6100)", rule2.ToString(true));
|
||||||
|
Assert.Equal(1m/6100m, rule2.Value.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1464,10 +1464,10 @@ namespace BTCPayServer.Tests
|
||||||
var quadri = new QuadrigacxRateProvider();
|
var quadri = new QuadrigacxRateProvider();
|
||||||
var rates = quadri.GetRatesAsync().GetAwaiter().GetResult();
|
var rates = quadri.GetRatesAsync().GetAwaiter().GetResult();
|
||||||
Assert.NotEmpty(rates);
|
Assert.NotEmpty(rates);
|
||||||
Assert.NotEqual(0.0m, rates.First().Value);
|
Assert.NotEqual(0.0m, rates.First().BidAsk.Bid);
|
||||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Value);
|
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Bid);
|
||||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Value);
|
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Bid);
|
||||||
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Value);
|
Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Bid);
|
||||||
Assert.Null(rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_USD")));
|
Assert.Null(rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_USD")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1492,7 +1492,7 @@ namespace BTCPayServer.Tests
|
||||||
e => (e.CurrencyPair == new CurrencyPair("BTC", "USD") ||
|
e => (e.CurrencyPair == new CurrencyPair("BTC", "USD") ||
|
||||||
e.CurrencyPair == new CurrencyPair("BTC", "EUR") ||
|
e.CurrencyPair == new CurrencyPair("BTC", "EUR") ||
|
||||||
e.CurrencyPair == new CurrencyPair("BTC", "USDT"))
|
e.CurrencyPair == new CurrencyPair("BTC", "USDT"))
|
||||||
&& e.Value > 1.0m // 1BTC will always be more than 1USD
|
&& e.BidAsk.Bid > 1.0m // 1BTC will always be more than 1USD
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,9 @@ namespace BTCPayServer.Rating
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (rate.Value.HasValue)
|
if (rate.BidAsk != null)
|
||||||
{
|
{
|
||||||
_AllRates[key].Value = rate.Value;
|
_AllRates[key].BidAsk = rate.BidAsk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,39 +58,167 @@ namespace BTCPayServer.Rating
|
||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRate(string exchangeName, CurrencyPair currencyPair, decimal value)
|
public void SetRate(string exchangeName, CurrencyPair currencyPair, BidAsk bidAsk)
|
||||||
{
|
{
|
||||||
if (ByExchange.TryGetValue(exchangeName, out var rates))
|
if (ByExchange.TryGetValue(exchangeName, out var rates))
|
||||||
{
|
{
|
||||||
var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair);
|
var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair);
|
||||||
if (rate != null)
|
if (rate != null)
|
||||||
rate.Value = value;
|
rate.BidAsk = bidAsk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public decimal? GetRate(string exchangeName, CurrencyPair currencyPair)
|
public BidAsk GetRate(string exchangeName, CurrencyPair currencyPair)
|
||||||
{
|
{
|
||||||
if (currencyPair.Left == currencyPair.Right)
|
if (currencyPair.Left == currencyPair.Right)
|
||||||
return 1.0m;
|
return BidAsk.One;
|
||||||
if (ByExchange.TryGetValue(exchangeName, out var rates))
|
if (ByExchange.TryGetValue(exchangeName, out var rates))
|
||||||
{
|
{
|
||||||
var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair);
|
var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair);
|
||||||
if (rate != null)
|
if (rate != null)
|
||||||
return rate.Value;
|
return rate.BidAsk;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public class BidAsk
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly static BidAsk _One = new BidAsk(1.0m);
|
||||||
|
public static BidAsk One
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _One;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly static BidAsk _Zero = new BidAsk(0.0m);
|
||||||
|
public static BidAsk Zero
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public BidAsk(decimal bid, decimal ask)
|
||||||
|
{
|
||||||
|
if (bid > ask)
|
||||||
|
throw new ArgumentException("the bid should be lower than ask", nameof(bid));
|
||||||
|
_Ask = ask;
|
||||||
|
_Bid = bid;
|
||||||
|
}
|
||||||
|
public BidAsk(decimal v) : this(v, v)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly decimal _Bid;
|
||||||
|
public decimal Bid
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _Bid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private readonly decimal _Ask;
|
||||||
|
public decimal Ask
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _Ask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public BidAsk Inverse()
|
||||||
|
{
|
||||||
|
return new BidAsk(1.0m / Ask, 1.0m / Bid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BidAsk operator+(BidAsk a, BidAsk b)
|
||||||
|
{
|
||||||
|
return new BidAsk(a.Bid + b.Bid, a.Ask + b.Ask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BidAsk operator +(BidAsk a)
|
||||||
|
{
|
||||||
|
return new BidAsk(a.Bid, a.Ask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BidAsk operator -(BidAsk a)
|
||||||
|
{
|
||||||
|
return new BidAsk(-a.Bid, -a.Ask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BidAsk operator *(BidAsk a, BidAsk b)
|
||||||
|
{
|
||||||
|
return new BidAsk(a.Bid * b.Bid, a.Ask * b.Ask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BidAsk operator /(BidAsk a, BidAsk b)
|
||||||
|
{
|
||||||
|
// This one is tricky.
|
||||||
|
// BTC_EUR = (6000, 6100)
|
||||||
|
// Implicit rule give
|
||||||
|
// EUR_BTC = 1 / BTC_EUR
|
||||||
|
// Or
|
||||||
|
// EUR_BTC = (1, 1) / BTC_EUR
|
||||||
|
// Naive calculation would give us ( 1/6000, 1/6100) = (0.000166, 0.000163)
|
||||||
|
// However, this is an invalid BidAsk!!! because 0.000166 > 0.000163
|
||||||
|
// So instead, we need to calculate (1/6100, 1/6000)
|
||||||
|
return new BidAsk(a.Bid / b.Ask, a.Ask / b.Bid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BidAsk operator-(BidAsk a, BidAsk b)
|
||||||
|
{
|
||||||
|
return new BidAsk(a.Bid - b.Bid, a.Ask - b.Ask);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
BidAsk item = obj as BidAsk;
|
||||||
|
if (item == null)
|
||||||
|
return false;
|
||||||
|
return Bid == item.Bid && Ask == item.Ask;
|
||||||
|
}
|
||||||
|
public static bool operator ==(BidAsk a, BidAsk b)
|
||||||
|
{
|
||||||
|
if (System.Object.ReferenceEquals(a, b))
|
||||||
|
return true;
|
||||||
|
if (((object)a == null) || ((object)b == null))
|
||||||
|
return false;
|
||||||
|
return a.Bid == b.Bid && a.Ask == b.Ask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(BidAsk a, BidAsk b)
|
||||||
|
{
|
||||||
|
return !(a == b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return ToString().GetHashCode(StringComparison.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (Bid == Ask)
|
||||||
|
return Bid.ToString(CultureInfo.InvariantCulture);
|
||||||
|
return $"({Bid.ToString(CultureInfo.InvariantCulture)} , {Ask.ToString(CultureInfo.InvariantCulture)})";
|
||||||
|
}
|
||||||
|
}
|
||||||
public class ExchangeRate
|
public class ExchangeRate
|
||||||
{
|
{
|
||||||
public string Exchange { get; set; }
|
public string Exchange { get; set; }
|
||||||
public CurrencyPair CurrencyPair { get; set; }
|
public CurrencyPair CurrencyPair { get; set; }
|
||||||
public decimal? Value { get; set; }
|
public BidAsk BidAsk { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (Value == null)
|
if (BidAsk == null)
|
||||||
return $"{Exchange}({CurrencyPair})";
|
return $"{Exchange}({CurrencyPair})";
|
||||||
return $"{Exchange}({CurrencyPair}) == {Value.Value.ToString(CultureInfo.InvariantCulture)}";
|
return $"{Exchange}({CurrencyPair}) == {BidAsk.ToString()}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace BTCPayServer.Rating
|
||||||
UnsupportedOperator,
|
UnsupportedOperator,
|
||||||
MissingArgument,
|
MissingArgument,
|
||||||
DivideByZero,
|
DivideByZero,
|
||||||
|
InvalidNegative,
|
||||||
PreprocessError,
|
PreprocessError,
|
||||||
RateUnavailable,
|
RateUnavailable,
|
||||||
InvalidExchangeName,
|
InvalidExchangeName,
|
||||||
|
@ -216,8 +217,7 @@ namespace BTCPayServer.Rating
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var token = SyntaxFactory.ParseToken(rate.Value.ToString(CultureInfo.InvariantCulture));
|
return RateRules.CreateExpression(rate.ToString());
|
||||||
return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, token);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ namespace BTCPayServer.Rating
|
||||||
|
|
||||||
class CalculateWalker : CSharpSyntaxWalker
|
class CalculateWalker : CSharpSyntaxWalker
|
||||||
{
|
{
|
||||||
public Stack<decimal> Values = new Stack<decimal>();
|
public Stack<BidAsk> Values = new Stack<BidAsk>();
|
||||||
public List<RateRulesErrors> Errors = new List<RateRulesErrors>();
|
public List<RateRulesErrors> Errors = new List<RateRulesErrors>();
|
||||||
|
|
||||||
public override void VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node)
|
public override void VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node)
|
||||||
|
@ -254,7 +254,15 @@ namespace BTCPayServer.Rating
|
||||||
switch (node.Kind())
|
switch (node.Kind())
|
||||||
{
|
{
|
||||||
case SyntaxKind.UnaryMinusExpression:
|
case SyntaxKind.UnaryMinusExpression:
|
||||||
Values.Push(-Values.Pop());
|
var v = Values.Pop();
|
||||||
|
if(v.Bid == v.Ask)
|
||||||
|
{
|
||||||
|
Values.Push(-v);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Errors.Add(RateRulesErrors.InvalidNegative);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case SyntaxKind.UnaryPlusExpression:
|
case SyntaxKind.UnaryPlusExpression:
|
||||||
Values.Push(+Values.Pop());
|
Values.Push(+Values.Pop());
|
||||||
|
@ -299,7 +307,7 @@ namespace BTCPayServer.Rating
|
||||||
Values.Push(a * b);
|
Values.Push(a * b);
|
||||||
break;
|
break;
|
||||||
case SyntaxKind.DivideExpression:
|
case SyntaxKind.DivideExpression:
|
||||||
if (b == decimal.Zero)
|
if (a.Ask == decimal.Zero || b.Ask == decimal.Zero)
|
||||||
{
|
{
|
||||||
Errors.Add(RateRulesErrors.DivideByZero);
|
Errors.Add(RateRulesErrors.DivideByZero);
|
||||||
}
|
}
|
||||||
|
@ -309,19 +317,48 @@ namespace BTCPayServer.Rating
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SyntaxKind.SubtractExpression:
|
case SyntaxKind.SubtractExpression:
|
||||||
|
if (b.Bid == b.Ask)
|
||||||
|
{
|
||||||
Values.Push(a - b);
|
Values.Push(a - b);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Errors.Add(RateRulesErrors.InvalidNegative);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException("Should never happen");
|
throw new NotSupportedException("Should never happen");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stack<decimal> _TupleValues = null;
|
||||||
|
public override void VisitTupleExpression(TupleExpressionSyntax node)
|
||||||
|
{
|
||||||
|
_TupleValues = new Stack<decimal>();
|
||||||
|
base.VisitTupleExpression(node);
|
||||||
|
if(_TupleValues.Count != 2)
|
||||||
|
{
|
||||||
|
Errors.Add(RateRulesErrors.MissingArgument);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var ask = _TupleValues.Pop();
|
||||||
|
var bid = _TupleValues.Pop();
|
||||||
|
Values.Push(new BidAsk(bid, ask));
|
||||||
|
}
|
||||||
|
_TupleValues = null;
|
||||||
|
}
|
||||||
|
|
||||||
public override void VisitLiteralExpression(LiteralExpressionSyntax node)
|
public override void VisitLiteralExpression(LiteralExpressionSyntax node)
|
||||||
{
|
{
|
||||||
switch (node.Kind())
|
switch (node.Kind())
|
||||||
{
|
{
|
||||||
case SyntaxKind.NumericLiteralExpression:
|
case SyntaxKind.NumericLiteralExpression:
|
||||||
Values.Push(decimal.Parse(node.ToString(), CultureInfo.InvariantCulture));
|
var v = decimal.Parse(node.ToString(), CultureInfo.InvariantCulture);
|
||||||
|
if (_TupleValues == null)
|
||||||
|
Values.Push(new BidAsk(v));
|
||||||
|
else
|
||||||
|
_TupleValues.Push(v);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,7 +524,7 @@ namespace BTCPayServer.Rating
|
||||||
Errors.AddRange(calculate.Errors);
|
Errors.AddRange(calculate.Errors);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_Value = calculate.Values.Pop();
|
_Value = calculate.Values.Pop().Bid;
|
||||||
_EvaluatedNode = result;
|
_EvaluatedNode = result;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace BTCPayServer.Services.Rates
|
||||||
{
|
{
|
||||||
return new ExchangeRates((await _Bitpay.GetRatesAsync().ConfigureAwait(false))
|
return new ExchangeRates((await _Bitpay.GetRatesAsync().ConfigureAwait(false))
|
||||||
.AllRates
|
.AllRates
|
||||||
.Select(r => new ExchangeRate() { Exchange = BitpayName, CurrencyPair = new CurrencyPair("BTC", r.Code), Value = r.Value })
|
.Select(r => new ExchangeRate() { Exchange = BitpayName, CurrencyPair = new CurrencyPair("BTC", r.Code), BidAsk = new BidAsk(r.Value) })
|
||||||
.ToList());
|
.ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,10 +69,30 @@ namespace BTCPayServer.Services.Rates
|
||||||
|
|
||||||
public ICoinAverageAuthenticator Authenticator { get; set; }
|
public ICoinAverageAuthenticator Authenticator { get; set; }
|
||||||
|
|
||||||
private bool TryToDecimal(JProperty p, out decimal v)
|
private bool TryToBidAsk(JProperty p, out BidAsk bidAsk)
|
||||||
{
|
{
|
||||||
JToken token = p.Value[Exchange == CoinAverageName ? "last" : "bid"];
|
bidAsk = null;
|
||||||
return decimal.TryParse(token.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v);
|
if (Exchange == CoinAverageName)
|
||||||
|
{
|
||||||
|
JToken last = p.Value["last"];
|
||||||
|
if (!decimal.TryParse(last.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v) ||
|
||||||
|
v <= 0)
|
||||||
|
return false;
|
||||||
|
bidAsk = new BidAsk(v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JToken bid = p.Value["bid"];
|
||||||
|
JToken ask = p.Value["bid"];
|
||||||
|
if (!decimal.TryParse(bid.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) ||
|
||||||
|
!decimal.TryParse(ask.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) ||
|
||||||
|
v1 > v2 ||
|
||||||
|
v1 <= 0 || v2 <= 0)
|
||||||
|
return false;
|
||||||
|
bidAsk = new BidAsk(v1, v2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ExchangeRates> GetRatesAsync()
|
public async Task<ExchangeRates> GetRatesAsync()
|
||||||
|
@ -108,9 +128,9 @@ namespace BTCPayServer.Services.Rates
|
||||||
{
|
{
|
||||||
ExchangeRate exchangeRate = new ExchangeRate();
|
ExchangeRate exchangeRate = new ExchangeRate();
|
||||||
exchangeRate.Exchange = Exchange;
|
exchangeRate.Exchange = Exchange;
|
||||||
if (!TryToDecimal(prop, out decimal value))
|
if (!TryToBidAsk(prop, out var value))
|
||||||
continue;
|
continue;
|
||||||
exchangeRate.Value = value;
|
exchangeRate.BidAsk = value;
|
||||||
if (CurrencyPair.TryParse(prop.Name, out var pair))
|
if (CurrencyPair.TryParse(prop.Name, out var pair))
|
||||||
{
|
{
|
||||||
exchangeRate.CurrencyPair = pair;
|
exchangeRate.CurrencyPair = pair;
|
||||||
|
|
|
@ -61,7 +61,7 @@ namespace BTCPayServer.Services.Rates
|
||||||
var rate = new ExchangeRate();
|
var rate = new ExchangeRate();
|
||||||
rate.CurrencyPair = pair;
|
rate.CurrencyPair = pair;
|
||||||
rate.Exchange = _ExchangeName;
|
rate.Exchange = _ExchangeName;
|
||||||
rate.Value = ticker.Value.Bid;
|
rate.BidAsk = new BidAsk(ticker.Value.Bid, ticker.Value.Ask);
|
||||||
return rate;
|
return rate;
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
|
|
|
@ -14,13 +14,19 @@ namespace BTCPayServer.Services.Rates
|
||||||
public const string QuadrigacxName = "quadrigacx";
|
public const string QuadrigacxName = "quadrigacx";
|
||||||
static HttpClient _Client = new HttpClient();
|
static HttpClient _Client = new HttpClient();
|
||||||
|
|
||||||
private bool TryToDecimal(JObject p, out decimal v)
|
private bool TryToBidAsk(JObject p, out BidAsk v)
|
||||||
{
|
{
|
||||||
v = 0.0m;
|
v = null;
|
||||||
JToken token = p.Property("bid")?.Value;
|
JToken bid = p.Property("bid")?.Value;
|
||||||
if (token == null)
|
JToken ask = p.Property("ask")?.Value;
|
||||||
|
if (bid == null || ask == null)
|
||||||
return false;
|
return false;
|
||||||
return decimal.TryParse(token.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out v);
|
if (!decimal.TryParse(bid.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) ||
|
||||||
|
!decimal.TryParse(bid.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) ||
|
||||||
|
v1 <= 0m || v2 <= 0m || v1 > v2)
|
||||||
|
return false;
|
||||||
|
v = new BidAsk(v1, v2);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ExchangeRates> GetRatesAsync()
|
public async Task<ExchangeRates> GetRatesAsync()
|
||||||
|
@ -37,9 +43,9 @@ namespace BTCPayServer.Services.Rates
|
||||||
continue;
|
continue;
|
||||||
rate.CurrencyPair = pair;
|
rate.CurrencyPair = pair;
|
||||||
rate.Exchange = QuadrigacxName;
|
rate.Exchange = QuadrigacxName;
|
||||||
if (!TryToDecimal((JObject)prop.Value, out var v))
|
if (!TryToBidAsk((JObject)prop.Value, out var v))
|
||||||
continue;
|
continue;
|
||||||
rate.Value = v;
|
rate.BidAsk = v;
|
||||||
exchangeRates.Add(rate);
|
exchangeRates.Add(rate);
|
||||||
}
|
}
|
||||||
return exchangeRates;
|
return exchangeRates;
|
||||||
|
|
Loading…
Add table
Reference in a new issue