Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address Missing StableCoin Pairs in Crypto Exchanges #5488

Merged
merged 7 commits into from
Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions Common/Currencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@ public static class Currencies
{"USDT", "USDT"}
};

/// <summary>
/// Define some StableCoins that don't have direct pairs for base currencies in our SPDB
/// This is because some CryptoExchanges do not define direct pairs with the stablecoins they offer.
///
/// We use this to allow setting cash amounts for these stablecoins without needing a conversion
/// security.
/// </summary>
public static HashSet<Symbol> StableCoinsWithoutPairs = new HashSet<Symbol>
{
// Binance StableCoins Missing 1-1 Pairs
Symbol.Create("USDCUSD", SecurityType.Crypto, Market.Binance), // USD -> USDC
Symbol.Create("BGBPGBP", SecurityType.Crypto, Market.Binance), // GBP -> BGBP

// Coinbase StableCoins Missing 1-1 Pairs
Symbol.Create("USDCUSD", SecurityType.Crypto, Market.GDAX), // USD -> USDC

// Bitfinex StableCoins Missing 1-1 Pairs
Symbol.Create("EURSEUR", SecurityType.Crypto, Market.Bitfinex), // EUR -> EURS
};

/// <summary>
/// Gets the currency symbol for the specified currency code
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions Common/Securities/Cash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,16 @@ public void SetAmount(decimal amount)
}
}

// Special case for crypto markets without direct pairs (They wont be found by the above)
// This allows us to add cash for "StableCoins" that are 1-1 with our account currency without needing a conversion security.
// Check out the StableCoinsWithoutPairs static var for those that are missing their 1-1 conversion pairs
if (Currencies.StableCoinsWithoutPairs.Contains(QuantConnect.Symbol.Create(normal, SecurityType.Crypto, marketMap[SecurityType.Crypto])))
{
ConversionRateSecurity = null;
ConversionRate = 1.0m;
C-SELLERS marked this conversation as resolved.
Show resolved Hide resolved
return null;
}

// if this still hasn't been set then it's an error condition
throw new ArgumentException($"In order to maintain cash in {Symbol} you are required to add a " +
$"subscription for Forex pair {Symbol}{accountCurrency} or {accountCurrency}{Symbol}"
Expand Down
2 changes: 2 additions & 0 deletions Data/symbol-properties/symbol-properties-database.csv
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ gdax,UMAEUR,crypto,UMA-Euro,EUR,1,0.001,0.001,UMA-EUR
gdax,UMAGBP,crypto,UMA-British Pound,GBP,1,0.001,0.001,UMA-GBP
gdax,UMAUSD,crypto,UMA-United States Dollar,USD,1,0.001,0.001,UMA-USD
gdax,UNIUSD,crypto,Uniswap-United States Dollar,USD,1,0.0001,0.000001,UNI-USD
gdax,USDCEUR,crypto,USDC-Eur,EUR,1,0.001,0.01,USDC-EUR
gdax,USDCGBP,crypto,USDC-GBP,GBP,1,0.001,0.01,USDC-GBP
gdax,WBTCBTC,crypto,Wrapped Bitcoin-Bitcoin,BTC,1,0.0001,0.00000001,WBTC-BTC
gdax,WBTCUSD,crypto,Wrapped Bitcoin-United States Dollar,USD,1,0.01,0.00000001,WBTC-USD
gdax,XLMBTC,crypto,Stellar-Bitcoin,BTC,1,0.00000001,1,XLM-BTC
Expand Down
80 changes: 80 additions & 0 deletions Tests/Common/Securities/CashTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -601,9 +601,89 @@ public void EnsureCurrencyDataFeedDoesNothingWithUnsupportedCurrency()
Assert.IsEmpty(added);
}

[TestCaseSource(nameof(cryptoBrokerageStableCoinCases))]
public void CryptoStableCoinMappingIsCorrect(IBrokerageModel brokerageModel, string accountCurrency, string stableCoin, bool shouldThrow, Symbol expectedConversionSymbol)
{
var cashBook = new CashBook() {AccountCurrency = accountCurrency};
var cash = new Cash(stableCoin, 10m, 1m);
cashBook.Add(cash.Symbol, cash);

var subscriptions = new SubscriptionManager();
var dataManager = new DataManagerStub(TimeKeeper);
subscriptions.SetDataManager(dataManager);
var securities = new SecurityManager(TimeKeeper);

// Verify the behavior throws or doesn't throw depending on the case
if (shouldThrow)
{
Assert.Throws<ArgumentException>(() =>
{
cash.EnsureCurrencyDataFeed(securities, subscriptions, brokerageModel.DefaultMarkets, SecurityChanges.None, dataManager.SecurityService, cashBook.AccountCurrency);
});
}
else
{
Assert.DoesNotThrow(() =>
{
cash.EnsureCurrencyDataFeed(securities, subscriptions, brokerageModel.DefaultMarkets, SecurityChanges.None, dataManager.SecurityService, cashBook.AccountCurrency);
});
}

// Verify the conversion symbol is correct
if (expectedConversionSymbol == null)
{
Assert.IsNull(cash.ConversionRateSecurity);
}
else
{
Assert.AreEqual(expectedConversionSymbol, cash.ConversionRateSecurity.Symbol);
}
}

private static TimeKeeper TimeKeeper
{
get { return new TimeKeeper(DateTime.Now, new[] { TimeZone }); }
}

// Crypto brokerage model stable coin and account currency cases
// The last var is expectedConversionSymbol, and is null when we expect there
// not to be a conversion security for our tests output
private static object[] cryptoBrokerageStableCoinCases =
{
// *** Bitfinex ***
// Trades USDC, EURS, and USDT
// USDC Cases
new object[] { new BitfinexBrokerageModel(), Currencies.USD, "USDC", false, Symbol.Create("USDCUSD", SecurityType.Crypto, Market.Bitfinex) },
new object[] { new BitfinexBrokerageModel(), Currencies.EUR, "USDC", true, null }, // No USDCEUR, does throw!
new object[] { new BitfinexBrokerageModel(), Currencies.GBP, "USDC", true, null }, // No USDCGBP, does throw!

// EURS Cases
new object[] { new BitfinexBrokerageModel(), Currencies.USD, "EURS", false, Symbol.Create("EURSUSD", SecurityType.Crypto, Market.Bitfinex) },
new object[] { new BitfinexBrokerageModel(), Currencies.EUR, "EURS", false, null }, // No EURSEUR, but does not throw! Conversion 1-1
new object[] { new BitfinexBrokerageModel(), Currencies.GBP, "EURS", true, null }, // No EURSGBP, does throw!

// USDT (Tether) Cases
new object[] { new BitfinexBrokerageModel(), Currencies.USD, "USDT", false, Symbol.Create("USDTUSD", SecurityType.Crypto, Market.Bitfinex) },
new object[] { new BitfinexBrokerageModel(), Currencies.EUR, "USDT", true, null }, // No USDTEUR, does throw!
new object[] { new BitfinexBrokerageModel(), Currencies.GBP, "USDT", true, null }, // No USDTGBP, does throw!

// *** GDAX ***
// Trades USDC and USDT* (*Not yet trading live, but expected soon)
// USDC Cases
new object[] { new GDAXBrokerageModel(), Currencies.USD, "USDC", false, null }, // No USDCUSD, but does not throw! Conversion 1-1
new object[] { new GDAXBrokerageModel(), Currencies.EUR, "USDC", false, Symbol.Create("USDCEUR", SecurityType.Crypto, Market.GDAX) },
new object[] { new GDAXBrokerageModel(), Currencies.GBP, "USDC", false, Symbol.Create("USDCGBP", SecurityType.Crypto, Market.GDAX) },

// *** Binance ***
// USDC Cases
new object[] { new BinanceBrokerageModel(), Currencies.USD, "USDC", false, null }, // No USDCUSD, but does not throw! Conversion 1-1
new object[] { new BinanceBrokerageModel(), Currencies.EUR, "USDC", true, null }, // No USDCEUR, does throw!
new object[] { new BinanceBrokerageModel(), Currencies.GBP, "USDC", true, null }, // No USDCGBP, does throw!

// BGBP Cases
new object[] { new BinanceBrokerageModel(), Currencies.USD, "BGBP", true, null }, // No BGBPUSD, does throw!
new object[] { new BinanceBrokerageModel(), Currencies.EUR, "BGBP", true, null }, // No BGBPEUR, does throw!
new object[] { new BinanceBrokerageModel(), Currencies.GBP, "BGBP", false, null }, // No BGBPGBP, but does not throw! Conversion 1-1
};
}
}