Skip to content

Commit 8880956

Browse files
authored
Allow creating new instance of exchange (#613)
* Allow creating new instance of exchange * Remove test code * Remove redundant try/catch * Discard operator * Cleanup * Remove redundant check * Pattern matching
1 parent 72e0e7b commit 8880956

File tree

5 files changed

+143
-52
lines changed

5 files changed

+143
-52
lines changed

Diff for: CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Please follow these coding guidelines...
2020

2121
When creating a new Exchange API, please do the following:
2222
- For reference comparisons, https://github.com/ccxt/ccxt is a good project to compare against when creating a new exchange. Use node.js in Visual Studio to debug through the code.
23-
- See ```ExchangeAPIDefinitions.cs``` for all possible methods that can be overriden to make an exchange, along with adding the name to the ExchangeName class. Great starting point to copy/paste as your new Exchange...API.cs file.
23+
- See ```ExchangeAPIDefinitions.cs``` for all possible methods that can be overriden to make an exchange, along with adding the name to the ExchangeName class. Great starting point to copy/paste as your new Exchange...API.cs file. The constant in ExchangeName should match the casing of the exchange name in the Exchange[Name]API class.
2424
- Put the exchange API class is in it's own folder (/API/Exchanges). If you are creating model objects or helper classes for an exchange, make internal classes inside a namespace for your exchange and put them in the sub-folder for the exchange. Binance and Bittrex are good examples.
2525
- Please use ```CryptoUtility, BaseAPIExtensions and ExchangeAPIExtensions``` for common code / parsing before rolling your own parsing code.
2626
- Ensure that the unit tests and integrations tests (```ExchangeSharpConsole.exe test exchangeName=[name]```) pass before submitting a pull request.

Diff for: src/ExchangeSharp/API/Exchanges/KuCoin/ExchangeKuCoinAPI.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -728,5 +728,5 @@ private string GetWebsocketBulletToken()
728728
#endregion Private Functions
729729
}
730730

731-
public partial class ExchangeName { public const string Kucoin = "Kucoin"; }
731+
public partial class ExchangeName { public const string Kucoin = "KuCoin"; }
732732
}

Diff for: src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs

+86-44
Original file line numberDiff line numberDiff line change
@@ -343,40 +343,81 @@ private async Task InitializeAsync()
343343
initialized = true;
344344
}
345345

346-
/// <summary>
347-
/// Get an exchange API given an exchange name (see ExchangeName class)
348-
/// </summary>
349-
/// <param name="exchangeName">Exchange name</param>
350-
/// <returns>Exchange API or null if not found</returns>
351-
public static IExchangeAPI GetExchangeAPI(string exchangeName)
346+
private static IExchangeAPI CreateExchangeAPI(Type? type)
352347
{
353-
// find the exchange with the name and creat it
354-
foreach (Type type in exchangeTypes)
348+
if (type is null)
349+
{
350+
throw new ArgumentNullException("No type found for exchange");
351+
}
352+
353+
if (!(Activator.CreateInstance(type, true) is ExchangeAPI api))
354+
{
355+
throw new ArgumentException($"Invalid type {type.FullName}, not a {nameof(ExchangeAPI)} type");
356+
}
357+
358+
Exception? ex = null;
359+
360+
// try up to 3 times to init
361+
for (int i = 0; i < 3; i++)
355362
{
356-
ExchangeAPI api = (Activator.CreateInstance(type, true)as ExchangeAPI) !;
357-
if (api.Name.Equals(exchangeName, StringComparison.OrdinalIgnoreCase))
363+
try
364+
{
365+
api.InitializeAsync().Sync();
366+
break;
367+
}
368+
catch (Exception _ex)
358369
{
359-
return GetExchangeAPI(type);
370+
ex = _ex;
371+
Thread.Sleep(5000);
360372
}
361373
}
362-
throw new ApplicationException("No exchange found with name " + exchangeName);
374+
375+
if (ex != null)
376+
{
377+
throw ex;
378+
}
379+
380+
return api;
381+
}
382+
383+
/// <summary>
384+
/// Create an exchange api, by-passing any cache. Use this method for cases
385+
/// where you need multiple instances of the same exchange, for example
386+
/// multiple credentials.
387+
/// </summary>
388+
/// <typeparam name="T">Type of exchange api to create</typeparam>
389+
/// <returns>Created exchange api</returns>
390+
public static T CreateExchangeAPI<T>() where T : ExchangeAPI
391+
{
392+
return (T)CreateExchangeAPI(typeof(T));
363393
}
364394

365395
/// <summary>
366-
/// Get an exchange API given a type
396+
/// Get a cached exchange API given an exchange name (see ExchangeName class)
397+
/// </summary>
398+
/// <param name="exchangeName">Exchange name. Must match the casing of the ExchangeName class name exactly.</param>
399+
/// <returns>Exchange API or null if not found</returns>
400+
public static IExchangeAPI GetExchangeAPI(string exchangeName)
401+
{
402+
Type type = ExchangeName.GetExchangeType(exchangeName);
403+
return GetExchangeAPI(type);
404+
}
405+
406+
/// <summary>
407+
/// Get a cached exchange API given a type
367408
/// </summary>
368409
/// <typeparam name="T">Type of exchange to get</typeparam>
369410
/// <returns>Exchange API or null if not found</returns>
370-
public static IExchangeAPI GetExchangeAPI<T>()where T : ExchangeAPI
411+
public static IExchangeAPI GetExchangeAPI<T>() where T : ExchangeAPI
371412
{
372413
// note: this method will be slightly slow (milliseconds) the first time it is called due to cache miss and initialization
373414
// subsequent calls with cache hits will be nanoseconds
374-
Type type = typeof(T) !;
415+
Type type = typeof(T)!;
375416
return GetExchangeAPI(type);
376417
}
377418

378419
/// <summary>
379-
/// Get an exchange API given a type
420+
/// Get a cached exchange API given a type
380421
/// </summary>
381422
/// <param name="type">Type of exchange</param>
382423
/// <returns>Exchange API or null if not found</returns>
@@ -386,51 +427,52 @@ public static IExchangeAPI GetExchangeAPI(Type type)
386427
// subsequent calls with cache hits will be nanoseconds
387428
return apis.GetOrAdd(type, _exchangeName =>
388429
{
389-
// find an API with the right name
390-
ExchangeAPI? api = null;
430+
// find the api type
391431
Type? foundType = exchangeTypes.FirstOrDefault(t => t == type);
392-
if (foundType != null)
432+
if (foundType is null)
433+
{
434+
throw new ArgumentException($"Unable to find exchange of type {type?.FullName}");
435+
}
436+
437+
// create the api
438+
if (!(Activator.CreateInstance(foundType, true) is ExchangeAPI api))
393439
{
394-
api = (Activator.CreateInstance(foundType, true)as ExchangeAPI) !;
395-
Exception? ex = null;
396-
const int retryCount = 3;
440+
throw new ApplicationException($"Failed to create exchange of type {foundType.FullName}");
441+
}
442+
443+
Exception? ex = null;
444+
const int retryCount = 3;
397445

398-
// try up to n times to init
399-
for (int i = 1; i <= retryCount; i++)
446+
// try up to n times to init
447+
for (int i = 1; i <= retryCount; i++)
448+
{
449+
try
400450
{
401-
try
402-
{
403-
api.InitializeAsync().Sync();
404-
ex = null;
405-
break;
406-
}
407-
catch (Exception _ex)
408-
{
409-
ex = _ex;
410-
if (i != retryCount)
411-
{
412-
Thread.Sleep(5000);
413-
}
414-
}
451+
api.InitializeAsync().Sync();
452+
ex = null;
453+
break;
415454
}
416-
417-
if (ex != null)
455+
catch (Exception _ex)
418456
{
419-
throw ex;
457+
ex = _ex;
458+
if (i != retryCount)
459+
{
460+
Thread.Sleep(5000);
461+
}
420462
}
421463
}
422464

423-
if (api == null)
465+
if (ex != null)
424466
{
425-
throw new ApplicationException("No exchange found with type " + type.FullName);
467+
throw ex;
426468
}
427469

428470
return api;
429471
});
430472
}
431473

432474
/// <summary>
433-
/// Get all exchange APIs
475+
/// Get all cached versions of exchange APIs
434476
/// </summary>
435477
/// <returns>All APIs</returns>
436478
public static IExchangeAPI[] GetExchangeAPIs()

Diff for: src/ExchangeSharp/API/Exchanges/_Base/ExchangeName.cs

+35-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
MIT LICENSE
33
44
Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com
@@ -24,16 +24,45 @@ namespace ExchangeSharp
2424
/// </summary>
2525
public static partial class ExchangeName
2626
{
27-
private static readonly HashSet<string> exchangeNames = new HashSet<string>();
27+
private static readonly Type exchangeApiType = typeof(ExchangeAPI);
28+
private static readonly HashSet<string> exchangeNames = new HashSet<string>();
2829

2930
static ExchangeName()
3031
{
31-
foreach (FieldInfo field in typeof(ExchangeName).GetFields(BindingFlags.Public | BindingFlags.Static))
32-
{
33-
exchangeNames.Add(field.GetValue(null).ToString());
34-
}
32+
foreach (FieldInfo field in typeof(ExchangeName).GetFields(BindingFlags.Public | BindingFlags.Static))
33+
{
34+
// pull value of name field
35+
string name = field.GetValue(null)!.ToString();
36+
37+
// make sure we have a valid type for the name
38+
_ = GetExchangeType(name);
39+
40+
// add to unique list of names
41+
exchangeNames.Add(name);
42+
}
3543
}
3644

45+
internal static Type GetExchangeType(string exchangeName)
46+
{
47+
try
48+
{
49+
// make sure we have a valid type for the name
50+
Type type = Type.GetType($"ExchangeSharp.Exchange{exchangeName}API");
51+
52+
// we had better have a type sub-classing from ExchangeAPI
53+
if (type is null || !type.IsSubclassOf(exchangeApiType))
54+
{
55+
throw new ApplicationException($"Name of {exchangeName} is not an {nameof(ExchangeAPI)} class");
56+
}
57+
return type;
58+
}
59+
catch (Exception ex)
60+
{
61+
// fatal
62+
throw new ApplicationException($"Failed to get type from exchange name {exchangeName}", ex);
63+
}
64+
}
65+
3766
/// <summary>
3867
/// Check if an exchange name exists
3968
/// </summary>

Diff for: tests/ExchangeSharpTests/ExchangeTests.cs

+20
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,26 @@ private async Task<string> GetAllSymbolsJsonAsync()
6262
return JsonConvert.SerializeObject(allSymbols);
6363
}
6464

65+
[TestMethod]
66+
public void ExchangeGetCreateTest()
67+
{
68+
// make sure get exchange api calls serve up the same instance
69+
var ex1 = ExchangeAPI.GetExchangeAPI<ExchangeGeminiAPI>();
70+
var ex2 = ExchangeAPI.GetExchangeAPI(ExchangeName.Gemini);
71+
Assert.AreSame(ex1, ex2);
72+
Assert.IsInstanceOfType(ex2, typeof(ExchangeGeminiAPI));
73+
74+
// make sure create exchange serves up new instances
75+
var ex3 = ExchangeAPI.CreateExchangeAPI<ExchangeGeminiAPI>();
76+
Assert.AreNotSame(ex3, ex2);
77+
78+
// make sure a bad exchange name throws correct exception
79+
Assert.ThrowsException<ApplicationException>(() =>
80+
{
81+
ExchangeAPI.GetExchangeAPI("SirExchangeNotAppearingInThisFilm");
82+
});
83+
}
84+
6585
[TestMethod]
6686
public async Task GlobalSymbolTest()
6787
{

0 commit comments

Comments
 (0)