@@ -27,17 +27,6 @@ public sealed partial class ExchangeKrakenAPI : ExchangeAPI
27
27
{
28
28
public override string BaseUrl { get ; set ; } = "https://api.kraken.com" ;
29
29
public override string BaseUrlWebSocket { get ; set ; } = "wss://ws.kraken.com" ;
30
- static ExchangeKrakenAPI ( )
31
- {
32
- Dictionary < string , string > d = normalizedSymbolToExchangeSymbol as Dictionary < string , string > ;
33
- foreach ( KeyValuePair < string , string > kv in exchangeSymbolToNormalizedSymbol )
34
- {
35
- if ( ! d . ContainsKey ( kv . Value ) )
36
- {
37
- d . Add ( kv . Value , kv . Key ) ;
38
- }
39
- }
40
- }
41
30
42
31
public ExchangeKrakenAPI ( )
43
32
{
@@ -47,164 +36,126 @@ public ExchangeKrakenAPI()
47
36
NonceStyle = NonceStyle . UnixMilliseconds ;
48
37
}
49
38
50
- public override async Task < string > ExchangeMarketSymbolToGlobalMarketSymbolAsync ( string marketSymbol )
39
+ private IReadOnlyDictionary < string , string > exchangeCurrencyToNormalizedCurrency = new Dictionary < string , string > ( ) ;
40
+ private IReadOnlyDictionary < string , string > normalizedCurrencyToExchangeCurrency = new Dictionary < string , string > ( ) ;
41
+ private IReadOnlyDictionary < string , string > exchangeSymbolToNormalizedSymbol = new Dictionary < string , string > ( ) ;
42
+ private IReadOnlyDictionary < string , string > normalizedSymbolToExchangeSymbol = new Dictionary < string , string > ( ) ;
43
+ private IReadOnlyDictionary < string , string > exchangeCurrenciesToMarketSymbol = new Dictionary < string , string > ( ) ;
44
+
45
+ static ExchangeKrakenAPI ( )
51
46
{
52
- if ( exchangeSymbolToNormalizedSymbol . TryGetValue ( marketSymbol , out string normalizedSymbol ) )
47
+ ExchangeGlobalCurrencyReplacements [ typeof ( ExchangeKrakenAPI ) ] = new KeyValuePair < string , string > [ ]
53
48
{
54
- int pos ;
55
- if ( marketSymbol . StartsWith ( "WAVES" ) )
49
+ new KeyValuePair < string , string > ( "XBT" , "BTC" ) ,
50
+ new KeyValuePair < string , string > ( "XDG" , "DOGE" )
51
+ } ;
52
+ }
53
+
54
+ /// <summary>
55
+ /// Populate dictionaries to deal with Kraken weirdness in currency and market names, will use cache if it exists
56
+ /// </summary>
57
+ /// <returns>Task</returns>
58
+ private async Task PopulateLookupTables ( )
59
+ {
60
+ await Cache . Get < object > ( nameof ( PopulateLookupTables ) , async ( ) =>
61
+ {
62
+ IReadOnlyDictionary < string , ExchangeCurrency > currencies = await GetCurrenciesAsync ( ) ;
63
+ ExchangeMarket [ ] markets = ( await GetMarketSymbolsMetadataAsync ( ) ) ? . ToArray ( ) ;
64
+ if ( markets == null || markets . Length == 0 )
56
65
{
57
- pos = 5 ;
66
+ return new CachedItem < object > ( ) ;
58
67
}
59
- else
68
+
69
+ Dictionary < string , string > exchangeCurrencyToNormalizedCurrencyNew = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
70
+ Dictionary < string , string > normalizedCurrencyToExchangeCurrencyNew = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
71
+ Dictionary < string , string > exchangeSymbolToNormalizedSymbolNew = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
72
+ Dictionary < string , string > normalizedSymbolToExchangeSymbolNew = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
73
+ Dictionary < string , string > exchangeCurrenciesToMarketSymbolNew = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
74
+
75
+ foreach ( KeyValuePair < string , ExchangeCurrency > kv in currencies )
60
76
{
61
- pos = ( normalizedSymbol . Length == 6 ? 3 : ( normalizedSymbol . Length == 7 ? 4 : throw new InvalidOperationException ( "Cannot normalize symbol " + normalizedSymbol ) ) ) ;
77
+ string altName = kv . Value . AltName ;
78
+ switch ( altName . ToLowerInvariant ( ) )
79
+ {
80
+ // wtf kraken...
81
+ case "xbt" : altName = "BTC" ; break ;
82
+ case "xdg" : altName = "DOGE" ; break ;
83
+ }
84
+ exchangeCurrencyToNormalizedCurrencyNew [ kv . Value . Name ] = altName ;
85
+ normalizedCurrencyToExchangeCurrencyNew [ altName ] = kv . Value . Name ;
62
86
}
63
- return await base . ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync ( normalizedSymbol . Substring ( 0 , pos ) + GlobalMarketSymbolSeparator + normalizedSymbol . Substring ( pos ) , GlobalMarketSymbolSeparator ) ;
64
- }
65
87
66
- # region if the initial fails, we try to use another method
67
- var symbols = ( await GetMarketSymbolsMetadataAsync ( ) ) . ToList ( ) ;
68
- var symbol = symbols . FirstOrDefault ( a => a . MarketSymbol . Replace ( "/" , "" ) . Equals ( marketSymbol ) ) ;
69
- string _marketSymbol = symbol . BaseCurrency + symbol . QuoteCurrency ;
70
- if ( exchangeSymbolToNormalizedSymbol . TryGetValue ( _marketSymbol , out normalizedSymbol ) )
71
- {
72
- int pos = ( normalizedSymbol . Length == 6 ? 3 : ( normalizedSymbol . Length == 7 ? 4 : throw new InvalidOperationException ( "Cannot normalize symbol " + normalizedSymbol ) ) ) ;
73
- return await base . ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync ( normalizedSymbol . Substring ( 0 , pos ) + GlobalMarketSymbolSeparator + normalizedSymbol . Substring ( pos ) , GlobalMarketSymbolSeparator ) ;
74
- }
75
- #endregion
88
+ foreach ( ExchangeMarket market in markets . Where ( m => ! m . MarketSymbol . Contains ( ".d" ) ) )
89
+ {
90
+ string baseSymbol = market . BaseCurrency ;
91
+ string quoteSymbol = market . QuoteCurrency ;
92
+ string baseNorm = exchangeCurrencyToNormalizedCurrencyNew [ market . BaseCurrency ] ;
93
+ string quoteNorm = exchangeCurrencyToNormalizedCurrencyNew [ market . QuoteCurrency ] ;
94
+ string marketSymbolNorm = baseNorm + quoteNorm ;
95
+ string marketSymbol = market . MarketSymbol ;
96
+ exchangeSymbolToNormalizedSymbolNew [ marketSymbol ] = marketSymbolNorm ;
97
+ normalizedSymbolToExchangeSymbolNew [ marketSymbolNorm ] = marketSymbol ;
98
+ exchangeCurrenciesToMarketSymbolNew [ baseSymbol + quoteSymbol ] = marketSymbol ;
99
+ }
76
100
77
- throw new ArgumentException ( $ "Symbol { marketSymbol } not found in Kraken lookup table") ;
101
+ exchangeCurrencyToNormalizedCurrency = exchangeCurrencyToNormalizedCurrencyNew ;
102
+ normalizedCurrencyToExchangeCurrency = normalizedCurrencyToExchangeCurrencyNew ;
103
+ exchangeSymbolToNormalizedSymbol = exchangeSymbolToNormalizedSymbolNew ;
104
+ normalizedSymbolToExchangeSymbol = normalizedSymbolToExchangeSymbolNew ;
105
+ exchangeCurrenciesToMarketSymbol = exchangeCurrenciesToMarketSymbolNew ;
106
+
107
+ return new CachedItem < object > ( new object ( ) , CryptoUtility . UtcNow . AddHours ( 4.0 ) ) ;
108
+ } ) ;
78
109
}
79
110
80
- public override Task < string > GlobalMarketSymbolToExchangeMarketSymbolAsync ( string marketSymbol )
111
+ public override async Task < ( string baseCurrency , string quoteCurrency ) > ExchangeMarketSymbolToCurrenciesAsync ( string marketSymbol )
81
112
{
82
- if ( normalizedSymbolToExchangeSymbol . TryGetValue ( marketSymbol . Replace ( GlobalMarketSymbolSeparator . ToString ( ) , string . Empty ) , out string exchangeSymbol ) )
113
+ ExchangeMarket market = await GetExchangeMarketFromCacheAsync ( marketSymbol ) ;
114
+ if ( market == null )
83
115
{
84
- return Task . FromResult ( exchangeSymbol ) ;
116
+ throw new ArgumentException ( "Unable to get currencies for market symbol " + marketSymbol ) ;
85
117
}
118
+ return ( market . BaseCurrency , market . QuoteCurrency ) ;
119
+ }
86
120
87
- // not found, reverse the pair
88
- int idx = marketSymbol . IndexOf ( GlobalMarketSymbolSeparator ) ;
89
- marketSymbol = marketSymbol . Substring ( idx + 1 ) + marketSymbol . Substring ( 0 , idx ) ;
90
- if ( normalizedSymbolToExchangeSymbol . TryGetValue ( marketSymbol . Replace ( GlobalMarketSymbolSeparator . ToString ( ) , string . Empty ) , out exchangeSymbol ) )
121
+ public override async Task < string > ExchangeMarketSymbolToGlobalMarketSymbolAsync ( string marketSymbol )
122
+ {
123
+ await PopulateLookupTables ( ) ;
124
+ var ( baseCurrency , quoteCurrency ) = await ExchangeMarketSymbolToCurrenciesAsync ( marketSymbol ) ;
125
+ if ( ! exchangeCurrencyToNormalizedCurrency . TryGetValue ( baseCurrency , out string baseCurrencyNormalized ) )
91
126
{
92
- return Task . FromResult ( exchangeSymbol ) ;
127
+ baseCurrencyNormalized = baseCurrency ;
93
128
}
94
-
95
- throw new ArgumentException ( $ "Symbol { marketSymbol } not found in Kraken lookup table") ;
129
+ if ( ! exchangeCurrencyToNormalizedCurrency . TryGetValue ( quoteCurrency , out string quoteCurrencyNormalized ) )
130
+ {
131
+ quoteCurrencyNormalized = quoteCurrency ;
132
+ }
133
+ return baseCurrencyNormalized + GlobalMarketSymbolSeparatorString + quoteCurrencyNormalized ;
96
134
}
97
135
98
- /// <summary>
99
- /// Change Kraken symbols to more common sense symbols
100
- /// </summary>
101
- private static readonly IReadOnlyDictionary < string , string > exchangeSymbolToNormalizedSymbol = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase )
136
+ public override async Task < string > GlobalMarketSymbolToExchangeMarketSymbolAsync ( string marketSymbol )
102
137
{
103
- { "ADACAD" , "adacad" } ,
104
- { "ADAETH" , "adaeth" } ,
105
- { "ADAEUR" , "adaeur" } ,
106
- { "ADAUSD" , "adausd" } ,
107
- { "ADAXBT" , "adabtc" } ,
108
- { "ATOMCAD" , "atomcad" } ,
109
- { "ATOMETH" , "atometh" } ,
110
- { "ATOMEUR" , "atomeur" } ,
111
- { "ATOMUSD" , "atomusd" } ,
112
- { "ATOMXBT" , "atombtc" } ,
113
- { "BATETH" , "bateth" } ,
114
- { "BATEUR" , "bateur" } ,
115
- { "BATUSD" , "batusd" } ,
116
- { "BATXBT" , "batbtc" } ,
117
- { "BCHEUR" , "bcheur" } ,
118
- { "BCHUSD" , "bchusd" } ,
119
- { "BCHXBT" , "bchbtc" } ,
120
- { "BSVEUR" , "bsveur" } ,
121
- { "BSVUSD" , "bsvusd" } ,
122
- { "BSVXBT" , "bsvbtc" } ,
123
- { "DASHEUR" , "dasheur" } ,
124
- { "DASHUSD" , "dashusd" } ,
125
- { "DASHXBT" , "dashbtc" } ,
126
- { "EOSETH" , "eoseth" } ,
127
- { "EOSEUR" , "eoseur" } ,
128
- { "EOSUSD" , "eosusd" } ,
129
- { "EOSXBT" , "eosbtc" } ,
130
- { "ETCETH" , "etceth" } ,
131
- { "ETCEUR" , "etceur" } ,
132
- { "ETCUSD" , "etcusd" } ,
133
- { "ETCXBT" , "etcbtc" } ,
134
- { "ETHCAD" , "ethcad" } ,
135
- { "GNOETH" , "gnoeth" } ,
136
- { "GNOEUR" , "gnoeur" } ,
137
- { "GNOUSD" , "gnousd" } ,
138
- { "GNOXBT" , "gnobtc" } ,
139
- { "QTUMCAD" , "qtumcad" } ,
140
- { "QTUMETH" , "qtumeth" } ,
141
- { "QTUMEUR" , "qtumeur" } ,
142
- { "QTUMUSD" , "qtumusd" } ,
143
- { "QTUMXBT" , "qtumbtc" } ,
144
- { "USDTZUSD" , "usdtusd" } ,
145
- { "WAVESETH" , "waveseth" } ,
146
- { "WAVESEUR" , "waveseur" } ,
147
- { "WAVESUSD" , "wavesusd" } ,
148
- { "WAVESXBT" , "wavesbtc" } ,
149
- { "XETCXETH" , "etceth" } ,
150
- { "XETCXXBT" , "etcbtc" } ,
151
- { "XETCZEUR" , "etceur" } ,
152
- { "XETCZUSD" , "etcusd" } ,
153
- { "XETHXXBT" , "ethbtc" } ,
154
- { "XETHXXBT.d" , "ethbtcd" } ,
155
- { "XETHZCAD" , "ethcad" } ,
156
- { "XETHZCAD.d" , "ethcadd" } ,
157
- { "XETHZEUR" , "etheur" } ,
158
- { "XETHZEUR.d" , "etheurd" } ,
159
- { "XETHZGBP" , "ethgbp" } ,
160
- { "XETHZGBP.d" , "ethgbpd" } ,
161
- { "XETHZJPY" , "ethjpy" } ,
162
- { "XETHZJPY.d" , "ethjpyd" } ,
163
- { "XETHZUSD" , "ethusd" } ,
164
- { "XETHZUSD.d" , "ethusdd" } ,
165
- { "XLTCXXBT" , "ltcbtc" } ,
166
- { "XLTCZEUR" , "ltceur" } ,
167
- { "XLTCZUSD" , "ltcusd" } ,
168
- { "XMLNXETH" , "mlneth" } ,
169
- { "XMLNXXBT" , "mlnbtc" } ,
170
- { "XREPXETH" , "repeth" } ,
171
- { "XREPXXBT" , "repbtc" } ,
172
- { "XREPZEUR" , "repeur" } ,
173
- { "XREPZUSD" , "repusd" } ,
174
- { "XTZCAD" , "xtzcad" } ,
175
- { "XTZETH" , "xtzeth" } ,
176
- { "XTZEUR" , "xtzeur" } ,
177
- { "XTZUSD" , "xtzusd" } ,
178
- { "XTZXBT" , "xtzbtc" } ,
179
- { "XXBTZCAD" , "btccad" } ,
180
- { "XXBTZCAD.d" , "btccadd" } ,
181
- { "XXBTZEUR" , "btceur" } ,
182
- { "XXBTZEUR.d" , "btceurd" } ,
183
- { "XXBTZGBP" , "btcgbp" } ,
184
- { "XXBTZGBP.d" , "btcgbpd" } ,
185
- { "XXBTZJPY" , "btcjpy" } ,
186
- { "XXBTZJPY.d" , "btcjpyd" } ,
187
- { "XXBTZUSD" , "btcusd" } ,
188
- { "XXBTZUSD.d" , "btcusdd" } ,
189
- { "XXDGXXBT" , "xdgbtc" } ,
190
- { "XXLMXXBT" , "xlmbtc" } ,
191
- { "XXLMZEUR" , "xlmeur" } ,
192
- { "XXLMZUSD" , "xlmusd" } ,
193
- { "XXMRXXBT" , "xmrbtc" } ,
194
- { "XXMRZEUR" , "xmreur" } ,
195
- { "XXMRZUSD" , "xmrusd" } ,
196
- { "XXRPXXBT" , "xrpbtc" } ,
197
- { "XXRPZCAD" , "xrpcad" } ,
198
- { "XXRPZEUR" , "xrpeur" } ,
199
- { "XXRPZJPY" , "xrpjpy" } ,
200
- { "XXRPZUSD" , "xrpusd" } ,
201
- { "XZECXXBT" , "zecbtc" } ,
202
- { "XZECZEUR" , "zeceur" } ,
203
- { "XZECZJPY" , "zecjpy" } ,
204
- { "XZECZUSD" , "zecusd" }
205
- } ;
206
-
207
- private static readonly IReadOnlyDictionary < string , string > normalizedSymbolToExchangeSymbol = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
138
+ await PopulateLookupTables ( ) ;
139
+ string [ ] pieces = marketSymbol . Split ( '-' ) ;
140
+ if ( pieces . Length < 2 )
141
+ {
142
+ throw new ArgumentException ( "Market symbol must be at least two pieces" ) ;
143
+ }
144
+ string marketSymbol2 = pieces [ 0 ] + pieces [ 1 ] ;
145
+ if ( ! normalizedCurrencyToExchangeCurrency . TryGetValue ( pieces [ 0 ] , out string baseCurrencyExchange ) )
146
+ {
147
+ baseCurrencyExchange = pieces [ 0 ] ;
148
+ }
149
+ if ( ! normalizedCurrencyToExchangeCurrency . TryGetValue ( pieces [ 1 ] , out string quoteCurrencyExchange ) )
150
+ {
151
+ quoteCurrencyExchange = pieces [ 1 ] ;
152
+ }
153
+ if ( ! exchangeCurrenciesToMarketSymbol . TryGetValue ( baseCurrencyExchange + quoteCurrencyExchange , out string exchangeMarketSymbol ) )
154
+ {
155
+ throw new ArgumentException ( "Unable to find exchange market for global market symbol " + marketSymbol ) ;
156
+ }
157
+ return exchangeMarketSymbol ;
158
+ }
208
159
209
160
protected override JToken CheckJsonResponse ( JToken json )
210
161
{
@@ -280,6 +231,7 @@ private async Task<ExchangeOrderResult> ParseHistoryOrder(string orderId, JToken
280
231
281
232
private async Task < IEnumerable < ExchangeOrderResult > > QueryOrdersAsync ( string symbol , string path )
282
233
{
234
+ await PopulateLookupTables ( ) ;
283
235
List < ExchangeOrderResult > orders = new List < ExchangeOrderResult > ( ) ;
284
236
JToken result = await MakeJsonRequestAsync < JToken > ( path , null , await GetNoncePayloadAsync ( ) ) ;
285
237
result = result [ "open" ] ;
@@ -299,6 +251,7 @@ private async Task<IEnumerable<ExchangeOrderResult>> QueryOrdersAsync(string sym
299
251
300
252
private async Task < IEnumerable < ExchangeOrderResult > > QueryClosedOrdersAsync ( string symbol , string path )
301
253
{
254
+ await PopulateLookupTables ( ) ;
302
255
List < ExchangeOrderResult > orders = new List < ExchangeOrderResult > ( ) ;
303
256
JToken result = await MakeJsonRequestAsync < JToken > ( path , null , await GetNoncePayloadAsync ( ) ) ;
304
257
result = result [ "closed" ] ;
@@ -325,6 +278,7 @@ private async Task<IEnumerable<ExchangeOrderResult>> QueryClosedOrdersAsync(stri
325
278
326
279
private async Task < IEnumerable < ExchangeOrderResult > > QueryHistoryOrdersAsync ( string symbol , string path )
327
280
{
281
+ await PopulateLookupTables ( ) ;
328
282
List < ExchangeOrderResult > orders = new List < ExchangeOrderResult > ( ) ;
329
283
JToken result = await MakeJsonRequestAsync < JToken > ( path , null , await GetNoncePayloadAsync ( ) ) ;
330
284
result = result [ "trades" ] ;
@@ -421,7 +375,8 @@ protected override async Task<IReadOnlyDictionary<string, ExchangeCurrency>> OnG
421
375
{
422
376
CoinType = token . Value [ "aclass" ] . ToStringInvariant ( ) ,
423
377
Name = token . Name ,
424
- FullName = token . Value [ "altname" ] . ToStringInvariant ( )
378
+ FullName = token . Name ,
379
+ AltName = token . Value [ "altname" ] . ToStringInvariant ( )
425
380
} ;
426
381
427
382
currencies [ coin . Name ] = coin ;
@@ -433,9 +388,7 @@ protected override async Task<IReadOnlyDictionary<string, ExchangeCurrency>> OnG
433
388
protected override async Task < IEnumerable < string > > OnGetMarketSymbolsAsync ( )
434
389
{
435
390
JToken result = await MakeJsonRequestAsync < JToken > ( "/0/public/AssetPairs" ) ;
436
- return ( from prop in result . Children < JProperty > ( )
437
- where ! prop . Name . Contains ( ".d" )
438
- select prop . Value [ "wsname" ] . ToStringInvariant ( ) ) . ToArray ( ) ;
391
+ return result . Children < JProperty > ( ) . Where ( p => ! p . Name . Contains ( ".d" ) ) . Select ( p => p . Name ) . ToArray ( ) ;
439
392
}
440
393
441
394
protected override async Task < IEnumerable < ExchangeMarket > > OnGetMarketSymbolsMetadataAsync ( )
@@ -536,15 +489,14 @@ protected override async Task<IEnumerable<ExchangeMarket>> OnGetMarketSymbolsMet
536
489
JToken allPairs = await MakeJsonRequestAsync < JToken > ( "/0/public/AssetPairs" ) ;
537
490
var res = ( from prop in allPairs . Children < JProperty > ( ) select prop ) . ToArray ( ) ;
538
491
539
- foreach ( JProperty prop in res )
492
+ foreach ( JProperty prop in res . Where ( p => ! p . Name . EndsWith ( ".d" ) ) )
540
493
{
541
494
JToken pair = prop . Value ;
542
495
var quantityStepSize = Math . Pow ( 0.1 , pair [ "lot_decimals" ] . ConvertInvariant < int > ( ) ) . ConvertInvariant < decimal > ( ) ;
543
496
var market = new ExchangeMarket
544
497
{
545
- IsActive = ! prop . Name . Contains ( ".d" ) ,
546
- MarketSymbol = ( pair [ "wsname" ] . ToStringInvariant ( ) != "" ?
547
- pair [ "wsname" ] . ToStringInvariant ( ) : pair [ "altname" ] . ToStringInvariant ( ) ) . Replace ( "/" , string . Empty ) ,
498
+ IsActive = true ,
499
+ MarketSymbol = prop . Name ,
548
500
MinTradeSize = quantityStepSize ,
549
501
MarginEnabled = pair [ "leverage_buy" ] . Children ( ) . Any ( ) || pair [ "leverage_sell" ] . Children ( ) . Any ( ) ,
550
502
BaseCurrency = pair [ "base" ] . ToStringInvariant ( ) ,
0 commit comments