2
2
using System ;
3
3
using System . Collections . Generic ;
4
4
using System . Linq ;
5
+ using System . Net . Http ;
5
6
using System . Text ;
6
7
using System . Threading ;
7
8
using System . Threading . Tasks ;
@@ -11,10 +12,21 @@ namespace ExchangeSharp
11
12
12
13
public partial class ExchangeDigifinexAPI : ExchangeAPI
13
14
{
15
+ string [ ] Urls =
16
+ {
17
+ "openapi.digifinex.com" ,
18
+ "openapi.digifinex.vip" ,
19
+ "openapi.digifinex.xyz" ,
20
+ } ;
21
+ string fastestUrl = null ;
22
+ int failedUrlCount ;
23
+ int successUrlCount ;
24
+
14
25
public override string BaseUrl { get ; set ; } = "https://openapi.digifinex.vip/v3" ;
15
- public override string BaseUrlWebSocket { get ; set ; } = "wss://openapi.digifinex.com /ws/v1/" ;
26
+ public override string BaseUrlWebSocket { get ; set ; } = "wss://openapi.digifinex.vip /ws/v1/" ;
16
27
int websocketMessageId = 0 ;
17
28
string timeWindow ;
29
+ TaskCompletionSource < int > inited = new TaskCompletionSource < int > ( ) ;
18
30
19
31
public ExchangeDigifinexAPI ( )
20
32
{
@@ -23,6 +35,35 @@ public ExchangeDigifinexAPI()
23
35
MarketSymbolIsUppercase = true ;
24
36
WebSocketOrderBookType = WebSocketOrderBookType . FullBookFirstThenDeltas ;
25
37
NonceStyle = NonceStyle . UnixSeconds ;
38
+ RateLimit = new RateGate ( 240 , TimeSpan . FromMinutes ( 1 ) ) ;
39
+ GetFastestUrl ( ) ;
40
+ }
41
+
42
+ void GetFastestUrl ( )
43
+ {
44
+ var client = new HttpClient ( ) ;
45
+ foreach ( var url in Urls )
46
+ {
47
+ var u = url ;
48
+ client . GetAsync ( $ "https://{ u } ") . ContinueWith ( ( t ) =>
49
+ {
50
+ if ( t . Exception != null )
51
+ {
52
+ var count = Interlocked . Increment ( ref failedUrlCount ) ;
53
+ if ( count == Urls . Length )
54
+ inited . SetException ( new APIException ( "All digifinex URLs failed." ) ) ;
55
+ return ;
56
+ }
57
+ if ( Interlocked . Increment ( ref successUrlCount ) == 1 )
58
+ {
59
+ fastestUrl = u ;
60
+ //Console.WriteLine($"Fastest url {GetHashCode()}: {u}");
61
+ BaseUrl = $ "https://{ u } /v3";
62
+ BaseUrlWebSocket = $ "wss://{ u } /ws/v1/";
63
+ inited . SetResult ( 1 ) ;
64
+ }
65
+ } ) ;
66
+ }
26
67
}
27
68
28
69
#region ProcessRequest
@@ -31,30 +72,26 @@ protected override async Task OnGetNonceOffset()
31
72
{
32
73
try
33
74
{
75
+ await inited . Task ;
34
76
var start = CryptoUtility . UtcNow ;
35
77
JToken token = await MakeJsonRequestAsync < JToken > ( "/time" ) ;
36
78
DateTime serverDate = CryptoUtility . UnixTimeStampToDateTimeSeconds ( token [ "server_time" ] . ConvertInvariant < long > ( ) ) ;
37
79
var end = CryptoUtility . UtcNow ;
38
- var now = start + TimeSpan . FromMilliseconds ( ( end - start ) . TotalMilliseconds / 2 ) ;
80
+ var now = start + TimeSpan . FromMilliseconds ( ( end - start ) . TotalMilliseconds ) ;
39
81
var timeFaster = now - serverDate ;
40
- if ( timeFaster <= TimeSpan . Zero )
41
- {
42
- timeWindow = ( timeFaster . Negate ( ) . TotalSeconds * 10 ) . ToStringInvariant ( ) ;
43
- NonceOffset = TimeSpan . FromSeconds ( 2.5 ) ;
44
- }
45
- else
46
- {
47
- NonceOffset = now - serverDate ; // how much time to substract from Nonce when making a request
48
- }
82
+ timeWindow = "30" ; // max latency of 30s
83
+ NonceOffset = now - serverDate ; // how much time to substract from Nonce when making a request
84
+ //Console.WriteLine($"NonceOffset {GetHashCode()}: {NonceOffset}");
49
85
}
50
86
catch
51
87
{
52
- // eat exception
88
+ throw ;
53
89
}
54
90
}
55
91
56
92
protected override async Task ProcessRequestAsync ( IHttpWebRequest request , Dictionary < string , object > payload )
57
93
{
94
+ await inited . Task ;
58
95
var query = request . RequestUri . Query . TrimStart ( '?' ) ;
59
96
if ( CanMakeAuthenticatedRequest ( payload ) )
60
97
{
@@ -124,6 +161,7 @@ private async Task<ExchangeMarket> ParseExchangeMarketAsync(JToken x)
124
161
125
162
protected internal override async Task < IEnumerable < ExchangeMarket > > OnGetMarketSymbolsMetadataAsync ( )
126
163
{
164
+ await inited . Task ;
127
165
JToken obj = await MakeJsonRequestAsync < JToken > ( "markets" ) ;
128
166
JToken data = obj [ "data" ] ;
129
167
List < ExchangeMarket > results = new List < ExchangeMarket > ( ) ;
@@ -179,7 +217,7 @@ protected override async Task<ExchangeOrderBook> OnGetOrderBookAsync(string mark
179
217
180
218
protected override async Task < IEnumerable < ExchangeTrade > > OnGetRecentTradesAsync ( string marketSymbol , int ? limit = null )
181
219
{
182
- JToken obj = await MakeJsonRequestAsync < JToken > ( $ "/trades?symbol={ marketSymbol } &limit=500") ; // maximum limit = 500
220
+ JToken obj = await MakeJsonRequestAsync < JToken > ( $ "/trades?symbol={ marketSymbol } &limit={ limit ?? 500 } ") ; // maximum limit = 500
183
221
return obj [ "data" ] . Select ( x => new ExchangeTrade
184
222
{
185
223
Id = x [ "id" ] . ToStringInvariant ( ) ,
@@ -194,9 +232,6 @@ protected override async Task<IEnumerable<ExchangeTrade>> OnGetRecentTradesAsync
194
232
protected override async Task < IEnumerable < MarketCandle > > OnGetCandlesAsync (
195
233
string marketSymbol , int periodSeconds , DateTime ? startDate = null , DateTime ? endDate = null , int ? limit = null )
196
234
{
197
- if ( limit != null )
198
- throw new ArgumentException ( "Non-null limit is not supported" , "limit" ) ;
199
-
200
235
string period ;
201
236
if ( periodSeconds <= 60 * 720 )
202
237
period = ( periodSeconds / 60 ) . ToStringInvariant ( ) ;
@@ -208,6 +243,20 @@ protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(
208
243
throw new ArgumentException ( $ "Unsupported periodSeconds: { periodSeconds } ", "periodSeconds" ) ;
209
244
210
245
var url = $ "/kline?symbol={ marketSymbol } &period={ period } ";
246
+ if ( startDate != null && endDate != null && limit != null )
247
+ throw new ArgumentException ( "Cannot specify `startDate`, `endDate` and `limit` all at the same time" ) ;
248
+ if ( limit != null )
249
+ {
250
+ if ( startDate != null )
251
+ endDate = startDate + TimeSpan . FromSeconds ( limit . Value * periodSeconds ) ;
252
+ else
253
+ {
254
+ if ( endDate == null )
255
+ endDate = DateTime . Now ;
256
+ startDate = endDate - TimeSpan . FromSeconds ( ( limit . Value - 1 ) * periodSeconds ) ;
257
+ }
258
+ }
259
+
211
260
if ( startDate != null )
212
261
url += $ "&start_time={ new DateTimeOffset ( startDate . Value ) . ToUnixTimeSeconds ( ) } ";
213
262
if ( endDate != null )
@@ -394,6 +443,7 @@ protected override async Task OnCancelOrderAsync(string orderId, string marketSy
394
443
395
444
protected override async Task < IWebSocket > OnGetTradesWebSocketAsync ( Func < KeyValuePair < string , ExchangeTrade > , Task > callback , params string [ ] marketSymbols )
396
445
{
446
+ await inited . Task ;
397
447
if ( callback == null )
398
448
{
399
449
return null ;
@@ -477,8 +527,9 @@ protected override async Task<IWebSocket> OnGetDeltaOrderBookWebSocketAsync(Acti
477
527
{
478
528
return null ;
479
529
}
530
+ await inited . Task ;
480
531
481
- return await ConnectWebSocketAsync ( string . Empty , ( _socket , msg ) =>
532
+ return await ConnectWebSocketAsync ( string . Empty , ( _socket , msg ) =>
482
533
{
483
534
//{
484
535
// "method": "depth.update",
0 commit comments