-
Notifications
You must be signed in to change notification settings - Fork 32
/
MarketMakerBot.cs
210 lines (171 loc) · 7.97 KB
/
MarketMakerBot.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#define TEST_ORDER_CREATION_MODE // Test order flag. See details: https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#test-new-order-trade
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Binance.Net.Enums;
using Binance.Net.Interfaces.Clients;
using Binance.Net.Objects.Models.Spot;
using BinanceBot.Market.Core;
using BinanceBot.Market.Strategies;
using CryptoExchange.Net.Objects;
using NLog;
namespace BinanceBot.Market
{
/// <summary>
/// Market Maker Bot
/// </summary>
public class MarketMakerBot : BaseMarketBot<NaiveMarketMakerStrategy>
{
private readonly IBinanceClient _binanceRestClient;
private readonly IBinanceSocketClient _webSocketClient;
private readonly MarketDepth _marketDepth;
/// <param name="symbol"></param>
/// <param name="marketStrategy"></param>
/// <param name="webSocketClient"></param>
/// <param name="logger"></param>
/// <param name="binanceRestClient"></param>
/// <exception cref="ArgumentNullException"><paramref name="symbol"/> cannot be <see langword="null"/></exception>
/// <exception cref="ArgumentNullException"><paramref name="marketStrategy"/> cannot be <see langword="null"/></exception>
/// <exception cref="ArgumentNullException"><paramref name="webSocketClient"/> cannot be <see langword="null"/></exception>
/// <exception cref="ArgumentNullException"><paramref name="binanceRestClient"/> cannot be <see langword="null"/></exception>
public MarketMakerBot(
string symbol,
NaiveMarketMakerStrategy marketStrategy,
IBinanceClient binanceRestClient,
IBinanceSocketClient webSocketClient,
Logger logger) :
base(symbol, marketStrategy, logger)
{
_marketDepth = new MarketDepth(symbol);
_binanceRestClient = binanceRestClient ?? throw new ArgumentNullException(nameof(binanceRestClient));
_webSocketClient = webSocketClient ?? throw new ArgumentNullException(nameof(webSocketClient));
}
public override async Task ValidateServerTimeAsync()
{
CallResult<long> testConnectResponse = await _binanceRestClient.SpotApi.ExchangeData.PingAsync().ConfigureAwait(false);
if (testConnectResponse.Error != null)
Logger.Error(testConnectResponse.Error.Message);
else
{
string msg = $"Connection was established successfully. Approximate ping time: {testConnectResponse.Data} ms";
if (testConnectResponse.Data > 1000)
Logger.Warn(msg);
else
Logger.Info(msg);
}
}
public override async Task<IEnumerable<BinanceOrder>> GetOpenedOrdersAsync(string symbol)
{
if (string.IsNullOrEmpty(symbol))
throw new ArgumentException("Invalid symbol value", nameof(symbol));
var response = await _binanceRestClient.SpotApi.Trading.GetOpenOrdersAsync(symbol).ConfigureAwait(false);
return response.Data;
}
public override async Task CancelOrdersAsync(IEnumerable<BinanceOrder> orders)
{
if (orders == null)
throw new ArgumentNullException(nameof(orders));
foreach (var order in orders)
await _binanceRestClient.SpotApi.Trading.CancelOrderAsync(orderId: order.Id, origClientOrderId: order.ClientOrderId, symbol: order.Symbol).ConfigureAwait(false);
}
public override async Task<BinancePlacedOrder> CreateOrderAsync(CreateOrderRequest order)
{
if (order == null) throw new ArgumentNullException(nameof(order));
#if TEST_ORDER_CREATION_MODE
WebCallResult<BinancePlacedOrder> response = await _binanceRestClient.SpotApi.Trading.PlaceTestOrderAsync(
// general
order.Symbol,
order.Side,
order.OrderType,
// price-quantity
price: order.Price,
quantity: order.Quantity,
// metadata
newClientOrderId: order.NewClientOrderId,
timeInForce: order.TimeInForce,
receiveWindow: order.RecvWindow)
.ConfigureAwait(false);
#else
WebCallResult<BinancePlacedOrder> response = await _binanceRestClient.SpotApi.Trading.PlaceOrderAsync(
// general
order.Symbol,
order.Side,
order.OrderType,
// price-quantity
price: order.Price,
quantity: order.Quantity,
// metadata
newClientOrderId: order.NewClientOrderId,
timeInForce: order.TimeInForce,
receiveWindow: order.RecvWindow)
.ConfigureAwait(false);
#endif
if (response.Error != null)
Logger.Error(response.Error.Message);
return response.Data;
}
#region Run bot section
public override async Task RunAsync()
{
// validate connection w/ stock
await ValidateServerTimeAsync();
// subscribe on order book updates
_marketDepth.MarketBestPairChanged += async (s, e) => await OnMarketBestPairChanged(s, e);
var marketDepthManager = new MarketDepthManager(_binanceRestClient, _webSocketClient);
// stream order book updates
marketDepthManager.StreamUpdates(_marketDepth, TimeSpan.FromMilliseconds(1000));
// build order book
await marketDepthManager.BuildAsync(_marketDepth, 100);
}
private async Task OnMarketBestPairChanged(object sender, MarketBestPairChangedEventArgs e)
{
if (e == null)
throw new ArgumentNullException(nameof(e));
// get current opened orders by token
var openOrdersResponse = await GetOpenedOrdersAsync(Symbol);
// cancel already opened orders (if necessary)
if (openOrdersResponse != null) await CancelOrdersAsync(openOrdersResponse);
// find new market position
Quote q = MarketStrategy.Process(e.MarketBestPair);
// if position found then create order
if (q != null)
{
var newOrderRequest = new CreateOrderRequest
{
// general
Symbol = Symbol,
Side = q.Direction,
OrderType = SpotOrderType.Limit,
// price-quantity
Price = Decimal.Round(q.Price, decimals: MarketStrategy.Config.PricePrecision),
Quantity = Decimal.Round(q.Volume, decimals: MarketStrategy.Config.BaseAssetPrecision),
// metadata
NewClientOrderId = $"market-bot-{Guid.NewGuid():N}".Substring(0, 36),
TimeInForce = TimeInForce.GoodTillCanceled,
RecvWindow = (int)MarketStrategy.Config.ReceiveWindow.TotalMilliseconds
};
var createOrderResponse = await CreateOrderAsync(newOrderRequest);
if (createOrderResponse != null)
Logger.Warn($"Limit order was created. Price: {createOrderResponse.Price}. Volume: {createOrderResponse.Quantity}");
}
}
#endregion
#region Stop/dispose bot section
public override void Stop()
{
Logger.Warn("Bot was stopped");
Dispose();
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
_webSocketClient.Dispose();
}
#endregion
}
}