Skip to content

Commit 7d2ced1

Browse files
authored
add trade stream for Dydx (#744)
1 parent f1c10a5 commit 7d2ced1

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using Newtonsoft.Json.Linq;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace ExchangeSharp
9+
{
10+
public sealed partial class ExchangeDydxApi : ExchangeAPI
11+
{
12+
public override string BaseUrl { get; set; } = "https://api.dydx.exchange";
13+
public override string BaseUrlWebSocket { get; set; } = "wss://api.dydx.exchange/v3/ws";
14+
15+
public ExchangeDydxApi()
16+
{
17+
NonceStyle = NonceStyle.Iso8601;
18+
NonceOffset = TimeSpan.FromSeconds(0.1);
19+
// WebSocketOrderBookType = not implemented
20+
MarketSymbolSeparator = "-";
21+
MarketSymbolIsUppercase = true;
22+
// ExchangeGlobalCurrencyReplacements[] not implemented
23+
}
24+
25+
protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync()
26+
{
27+
/*{
28+
"markets": {
29+
"LINK-USD": {
30+
"market": "LINK-USD",
31+
"status": "ONLINE",
32+
"baseAsset": "LINK",
33+
"quoteAsset": "USD",
34+
"stepSize": "0.1",
35+
"tickSize": "0.01",
36+
"indexPrice": "12",
37+
"oraclePrice": "101",
38+
"priceChange24H": "0",
39+
"nextFundingRate": "0.0000125000",
40+
"nextFundingAt": "2021-03-01T18:00:00.000Z",
41+
"minOrderSize": "1",
42+
"type": "PERPETUAL",
43+
"initialMarginFraction": "0.10",
44+
"maintenanceMarginFraction": "0.05",
45+
"baselinePositionSize": "1000",
46+
"incrementalPositionSize": "1000",
47+
"incrementalInitialMarginFraction": "0.2",
48+
"volume24H": "0",
49+
"trades24H": "0",
50+
"openInterest": "0",
51+
"maxPositionSize": "10000",
52+
"assetResolution": "10000000",
53+
"syntheticAssetId": "0x4c494e4b2d37000000000000000000",
54+
},
55+
...
56+
}*/
57+
var instruments = await MakeJsonRequestAsync<JToken>("v3/markets");
58+
var markets = new List<ExchangeMarket>();
59+
foreach (JToken instrument in instruments["markets"])
60+
{
61+
markets.Add(new ExchangeMarket
62+
{
63+
MarketSymbol = instrument.ElementAt(0)["market"].ToStringInvariant(),
64+
QuoteCurrency = instrument.ElementAt(0)["quoteAsset"].ToStringInvariant(),
65+
BaseCurrency = instrument.ElementAt(0)["baseAsset"].ToStringInvariant(),
66+
});
67+
}
68+
return markets.Select(m => m.MarketSymbol);
69+
}
70+
71+
protected override async Task<IWebSocket> OnGetTradesWebSocketAsync(Func<KeyValuePair<string, ExchangeTrade>, Task> callback, params string[] marketSymbols)
72+
{
73+
if (marketSymbols == null || marketSymbols.Length == 0)
74+
{
75+
marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
76+
}
77+
return await ConnectPublicWebSocketAsync("", async (_socket, msg) =>
78+
{
79+
/*Example initial response:
80+
{
81+
"type": "subscribed",
82+
"id": "BTC-USD",
83+
"connection_id": "e2a6c717-6f77-4c1c-ac22-72ce2b7ed77d",
84+
"channel": "v3_trades",
85+
"message_id": 1,
86+
"contents": {
87+
"trades": [
88+
{
89+
"side": "BUY",
90+
"size": "100",
91+
"price": "4000",
92+
"createdAt": "2020-10-29T00:26:30.759Z"
93+
},
94+
{
95+
"side": "BUY",
96+
"size": "100",
97+
"price": "4000",
98+
"createdAt": "2020-11-02T19:45:42.886Z"
99+
},
100+
{
101+
"side": "BUY",
102+
"size": "100",
103+
"price": "4000",
104+
"createdAt": "2020-10-29T00:26:57.382Z"
105+
}
106+
]
107+
}
108+
}*/
109+
/* Example subsequent response
110+
{
111+
"type": "channel_data",
112+
"id": "BTC-USD",
113+
"connection_id": "e2a6c717-6f77-4c1c-ac22-72ce2b7ed77d",
114+
"channel": "v3_trades",
115+
"message_id": 2,
116+
"contents": {
117+
"trades": [
118+
{
119+
"side": "BUY",
120+
"size": "100",
121+
"price": "4000",
122+
"createdAt": "2020-11-29T00:26:30.759Z"
123+
},
124+
{
125+
"side": "SELL",
126+
"size": "100",
127+
"price": "4000",
128+
"createdAt": "2020-11-29T14:00:03.382Z"
129+
}
130+
]
131+
}
132+
} */
133+
JToken token = JToken.Parse(msg.ToStringFromUTF8());
134+
if (token["type"].ToStringInvariant() == "error")
135+
{
136+
throw new APIException(token["message"].ToStringInvariant());
137+
}
138+
else if (token["channel"].ToStringInvariant() == "v3_trades")
139+
{
140+
var tradesArray = token["contents"]["trades"].ToArray();
141+
for (int i = 0; i < tradesArray.Length; i++)
142+
{
143+
var trade = tradesArray[i].ParseTrade("size", "price", "side", "createdAt", TimestampType.Iso8601UTC, null);
144+
string marketSymbol = token["id"].ToStringInvariant();
145+
if (token["type"].ToStringInvariant() == "subscribed" || token["message_id"].ToObject<int>() == 1)
146+
{
147+
trade.Flags |= ExchangeTradeFlags.IsFromSnapshot;
148+
if (i == tradesArray.Length - 1)
149+
trade.Flags |= ExchangeTradeFlags.IsLastFromSnapshot;
150+
}
151+
await callback(new KeyValuePair<string, ExchangeTrade>(marketSymbol, trade));
152+
}
153+
}
154+
}, async (_socket) =>
155+
{
156+
foreach (var marketSymbol in marketSymbols)
157+
{
158+
var subscribeRequest = new
159+
{
160+
type = "subscribe",
161+
channel = "v3_trades",
162+
id = marketSymbol,
163+
};
164+
await _socket.SendMessageAsync(subscribeRequest);
165+
}
166+
});
167+
}
168+
}
169+
public partial class ExchangeName { public const string Dydx = "Dydx"; }
170+
}

0 commit comments

Comments
 (0)