public void CanParseRateRules() { // Check happy path StringBuilder builder = new StringBuilder(); builder.AppendLine("// Some cool comments"); builder.AppendLine("DOGE_X = DOGE_BTC * BTC_X * 1.1"); builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)"); builder.AppendLine("// Some other cool comments"); builder.AppendLine("BTC_usd = kraken(BTC_USD)"); builder.AppendLine("BTC_X = Coinbase(BTC_X);"); builder.AppendLine("X_X = CoinAverage(X_X) * 1.02"); Assert.False(RateRules.TryParse("DPW*&W&#hdi&#&3JJD", out var rules)); Assert.True(RateRules.TryParse(builder.ToString(), out rules)); Assert.Equal( "// Some cool comments\n" + "DOGE_X = DOGE_BTC * BTC_X * 1.1;\n" + "DOGE_BTC = bittrex(DOGE_BTC);\n" + "// Some other cool comments\n" + "BTC_USD = kraken(BTC_USD);\n" + "BTC_X = coinbase(BTC_X);\n" + "X_X = coinaverage(X_X) * 1.02;", rules.ToString()); var tests = new[]
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) { var exchangeName = node.Expression.ToString(); if (exchangeName.StartsWith("ERR_", StringComparison.OrdinalIgnoreCase)) { Errors.Add(RateRulesErrors.PreprocessError); return(base.VisitInvocationExpression(node)); } var currencyPair = node.ArgumentList.ChildNodes().FirstOrDefault()?.ToString(); if (currencyPair == null || !CurrencyPair.TryParse(currencyPair, out var pair)) { Errors.Add(RateRulesErrors.InvalidCurrencyIdentifier); return(RateRules.CreateExpression($"ERR_INVALID_CURRENCY_PAIR({node.ToString()})")); } else { var rate = Rates.GetRate(exchangeName, pair); if (rate == null) { Errors.Add(RateRulesErrors.RateUnavailable); return(RateRules.CreateExpression($"ERR_RATE_UNAVAILABLE({exchangeName}, {pair.ToString()})")); } else { return(RateRules.CreateExpression(rate.ToString())); } } }
public static RateRule CreateFromExpression(string expression, CurrencyPair currencyPair) { var ex = RateRules.CreateExpression(expression); RateRules.TryParse("", out var rules); return(new RateRule(rules, currencyPair, ex)); }
public void SecondDuplicatedRuleIsIgnored() { StringBuilder builder = new StringBuilder(); builder.AppendLine("DOGE_X = 1.1"); builder.AppendLine("DOGE_X = 1.2"); Assert.True(RateRules.TryParse(builder.ToString(), out var rules)); var rule = rules.GetRuleFor(new CurrencyPair("DOGE", "BTC")); rule.Reevaluate(); Assert.True(!rule.HasError); Assert.Equal(1.1m, rule.BidAsk.Ask); }
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) { if (IsInvocation) { Errors.Add(RateRulesErrors.InvalidCurrencyIdentifier); return(RateRules.CreateExpression($"ERR_INVALID_CURRENCY_PAIR({node.ToString()})")); } IsInvocation = true; _ExchangeName = node.Expression.ToString(); var result = base.VisitInvocationExpression(node); IsInvocation = false; return(result); }
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) { if ( (!IsInvocation || IsArgumentList) && CurrencyPair.TryParse(node.Identifier.ValueText, out var currentPair)) { var replacedPair = new CurrencyPair(left: currentPair.Left == "X" ? pair.Left : currentPair.Left, right: currentPair.Right == "X" ? pair.Right : currentPair.Right); if (IsInvocation) // eg. replace bittrex(BTC_X) to bittrex(BTC_USD) { ExchangeRates.Add(new ExchangeRate() { CurrencyPair = replacedPair, Exchange = _ExchangeName }); return(SyntaxFactory.IdentifierName(replacedPair.ToString())); } else // eg. replace BTC_X to BTC_USD, then replace by the expression for BTC_USD { var bestCandidate = parent.FindBestCandidate(replacedPair); if (nested > MaxNestedCount) { Errors.Add(RateRulesErrors.TooMuchNestedCalls); return(RateRules.CreateExpression($"ERR_TOO_MUCH_NESTED_CALLS({replacedPair})")); } var innerFlatten = CreateNewContext(replacedPair); var replaced = innerFlatten.Visit(bestCandidate); if (replaced is ExpressionSyntax expression) { var hasBinaryOps = new HasBinaryOperations(); hasBinaryOps.Visit(expression); if (hasBinaryOps.Result) { replaced = SyntaxFactory.ParenthesizedExpression(expression); } } if (Errors.Contains(RateRulesErrors.TooMuchNestedCalls)) { return(RateRules.CreateExpression($"ERR_TOO_MUCH_NESTED_CALLS({replacedPair})")); } return(replaced); } } return(base.VisitIdentifierName(node)); }
public async Task <IActionResult> GetRates2(string cryptoCode = null, string storeId = null) { cryptoCode = cryptoCode ?? "BTC"; var network = _NetworkProvider.GetNetwork(cryptoCode); if (network == null) { return(NotFound()); } RateRules rules = null; if (storeId != null) { var store = await _StoreRepo.FindStore(storeId); if (store == null) { return(NotFound()); } rules = store.GetStoreBlob().GetRateRules(); } var rateProvider = _RateProviderFactory.GetRateProvider(network, rules); if (rateProvider == null) { return(NotFound()); } var allRates = (await rateProvider.GetRatesAsync()); return(Json(allRates.Select(r => new NBitpayClient.Rate() { Code = r.Currency, Name = _CurrencyNameTable.GetCurrencyData(r.Currency)?.Name, Value = r.Value }).Where(n => n.Name != null).ToArray())); }
public IRateProvider ApplyRateRules(BTCPayNetwork network, IRateProvider rateProvider) { if (!PreferredExchange.IsCoinAverage()) { // If the original rateProvider is a cache, use the same inner provider as fallback, and same memory cache to wrap it all if (rateProvider is CachedRateProvider cachedRateProvider) { rateProvider = new FallbackRateProvider(new IRateProvider[] { new CoinAverageRateProvider(network.CryptoCode) { Exchange = PreferredExchange }, cachedRateProvider.Inner }); rateProvider = new CachedRateProvider(network.CryptoCode, rateProvider, cachedRateProvider.MemoryCache) { AdditionalScope = PreferredExchange }; } else { rateProvider = new FallbackRateProvider(new IRateProvider[] { new CoinAverageRateProvider(network.CryptoCode) { Exchange = PreferredExchange }, rateProvider }); } } if (RateRules == null || RateRules.Count == 0) { return(rateProvider); } return(new TweakRateProvider(network, rateProvider, RateRules.ToList())); }
public async Task <IActionResult> Rates(RatesViewModel model, string command = null, string storeId = null, CancellationToken cancellationToken = default) { model.SetExchangeRates(GetSupportedExchanges(), model.PreferredExchange); model.StoreId = storeId ?? model.StoreId; CurrencyPair[] currencyPairs = null; try { currencyPairs = model.DefaultCurrencyPairs? .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(p => CurrencyPair.Parse(p)) .ToArray(); } catch { ModelState.AddModelError(nameof(model.DefaultCurrencyPairs), "Invalid currency pairs (should be for example: BTC_USD,BTC_CAD,BTC_JPY)"); } if (!ModelState.IsValid) { return(View(model)); } if (model.PreferredExchange != null) { model.PreferredExchange = model.PreferredExchange.Trim().ToLowerInvariant(); } var blob = StoreData.GetStoreBlob(); model.DefaultScript = blob.GetDefaultRateRules(_NetworkProvider).ToString(); model.AvailableExchanges = GetSupportedExchanges(); blob.PreferredExchange = model.PreferredExchange; blob.Spread = (decimal)model.Spread / 100.0m; blob.DefaultCurrencyPairs = currencyPairs; if (!model.ShowScripting) { if (!GetSupportedExchanges().Select(c => c.Name).Contains(blob.PreferredExchange, StringComparer.OrdinalIgnoreCase)) { ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})"); return(View(model)); } } RateRules rules = null; if (model.ShowScripting) { if (!RateRules.TryParse(model.Script, out rules, out var errors)) { errors = errors ?? new List <RateRulesErrors>(); var errorString = String.Join(", ", errors.ToArray()); ModelState.AddModelError(nameof(model.Script), $"Parsing error ({errorString})"); return(View(model)); } else { blob.RateScript = rules.ToString(); ModelState.Remove(nameof(model.Script)); model.Script = blob.RateScript; } } rules = blob.GetRateRules(_NetworkProvider); if (command == "Test") { if (string.IsNullOrWhiteSpace(model.ScriptTest)) { ModelState.AddModelError(nameof(model.ScriptTest), "Fill out currency pair to test for (like BTC_USD,BTC_CAD)"); return(View(model)); } var splitted = model.ScriptTest.Split(',', StringSplitOptions.RemoveEmptyEntries); var pairs = new List <CurrencyPair>(); foreach (var pair in splitted) { if (!CurrencyPair.TryParse(pair, out var currencyPair)) { ModelState.AddModelError(nameof(model.ScriptTest), $"Invalid currency pair '{pair}' (it should be formatted like BTC_USD,BTC_CAD)"); return(View(model)); } pairs.Add(currencyPair); } var fetchs = _RateFactory.FetchRates(pairs.ToHashSet(), rules, cancellationToken); var testResults = new List <RatesViewModel.TestResultViewModel>(); foreach (var fetch in fetchs) { var testResult = await(fetch.Value); testResults.Add(new RatesViewModel.TestResultViewModel() { CurrencyPair = fetch.Key.ToString(), Error = testResult.Errors.Count != 0, Rule = testResult.Errors.Count == 0 ? testResult.Rule + " = " + testResult.BidAsk.Bid.ToString(CultureInfo.InvariantCulture) : testResult.EvaluatedRule }); } model.TestRateRules = testResults; return(View(model)); } else // command == Save { if (StoreData.SetStoreBlob(blob)) { await _Repo.UpdateStore(StoreData); StatusMessage = "Rate settings updated"; } return(RedirectToAction(nameof(Rates), new { storeId = StoreData.Id })); } }
public RateRule(RateRules parent, CurrencyPair currencyPair, SyntaxNode candidate) { _CurrencyPair = currencyPair; flatten = new FlattenExpressionRewriter(parent, currencyPair); this.expression = flatten.Visit(candidate); }
public FlattenExpressionRewriter(RateRules parent, CurrencyPair pair) { this.pair = pair; this.parent = parent; }
public async Task <IActionResult> NewAtomicSwap( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, NewViewModel newVM) { var fromWallet = await GetDerivationStrategy(walletId); var statusAsync = ExplorerClientProvider.GetExplorerClient(fromWallet.Network).GetStatusAsync(); if (fromWallet == null) { return(NotFound()); } var wallets = await GetNamedWallets(walletId.CryptoCode); newVM.SetWalletList(wallets, newVM.SelectedWallet); newVM.CryptoCode = fromWallet.Network.CryptoCode; if (!WalletId.TryParse(newVM.SelectedWallet, out var selectedWalletId)) { ModelState.AddModelError(nameof(newVM.SelectedWallet), "Invalid wallet id"); return(View(newVM)); } var toWallet = await GetDerivationStrategy(selectedWalletId); if (toWallet == null) { ModelState.AddModelError(nameof(newVM.SelectedWallet), "Invalid wallet id"); return(View(newVM)); } var id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20)); AtomicSwapOffer offer = new AtomicSwapOffer(); offer.MarketMakerUri = new Uri($"{this.Request.GetAbsoluteRoot().WithTrailingSlash()}api/xswap/{id}", UriKind.Absolute); offer.Offer = new AtomicSwapOfferAsset() { Amount = Money.Coins((decimal)newVM.Amount), CryptoCode = walletId.CryptoCode, }; var minRelayFee = (await statusAsync).BitcoinStatus.MinRelayTxFee; var minimumAmount = minRelayFee.GetFee(200); // Arbitrary but should cover the dust of any output if (offer.Offer.Amount <= minimumAmount) { ModelState.AddModelError(nameof(newVM.Amount), $"Amount must be above {minimumAmount}"); return(View(newVM)); } offer.Price = new AtomicSwapOfferAsset() { CryptoCode = toWallet.PaymentId.CryptoCode }; var lockTimespan = TimeSpan.FromDays(2); offer.CreatedAt = DateTimeOffset.UtcNow; var storeData = await Repository.FindStore(walletId.StoreId, GetUserId()); if (ModelState.IsValid) { var pair = new CurrencyPair("AAA", "BBB"); newVM.RateRule = $"{pair} = {newVM.RateRule}"; if (RateRules.TryParse(newVM.RateRule, out var rules, out var rateRulesErrors)) { rules.Spread = (decimal)newVM.Spread / 100.0m; var rateResult = await RateFetcher.FetchRate(pair, rules, CancellationToken.None); if (rateResult.BidAsk == null) { string errorMessage = "Error when fetching rate"; if (rateResult.EvaluatedRule != null) { errorMessage += $" ({rateResult.EvaluatedRule})"; } ModelState.AddModelError(nameof(newVM.RateRule), errorMessage); } else { offer.Price.Amount = Money.Coins(offer.Offer.Amount.ToDecimal(MoneyUnit.BTC) * rateResult.BidAsk.Ask); rules.Spread = 0; offer.Rule = rules.GetRuleFor(pair).ToString(); } } else { string errorDetails = ""; if (rateRulesErrors.Count > 0) { errorDetails = $" ({rateRulesErrors[0]})"; } ModelState.AddModelError(nameof(newVM.RateRule), $"Impossible to parse rate rules{errorDetails}"); } }
public async Task <decimal> GetCurrentContributionAmount(Dictionary <string, decimal> stats, string primaryCurrency, RateRules rateRules) { var result = new List <decimal>(); var ratesTask = _RateFetcher.FetchRates( stats.Keys .Select((x) => new CurrencyPair(primaryCurrency, PaymentMethodId.Parse(x).CryptoCode)) .Distinct() .ToHashSet(), rateRules).Select(async rateTask => { var(key, value) = rateTask; var tResult = await value; var rate = tResult.BidAsk?.Bid; if (rate == null) { return; } foreach (var stat in stats) { if (string.Equals(PaymentMethodId.Parse(stat.Key).CryptoCode, key.Right, StringComparison.InvariantCultureIgnoreCase)) { result.Add((1m / rate.Value) * stat.Value); } } }); await Task.WhenAll(ratesTask); return(result.Sum()); }