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 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 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 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}"); } }