Browse Source

对接回调

master
shanji 3 years ago
parent
commit
9d83872e1e
  1. 3
      .editorconfig
  2. 4
      Binance.TradeRobot.API/Controllers/OrderController.cs
  3. 4
      Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj
  4. 20
      Binance.TradeRobot.Business/Business/OrderPublishBusiness/Spot/BaseSpotOrderPublishTradeBusiness.cs
  5. 10
      Binance.TradeRobot.Business/Business/OrderPublishBusiness/Spot/D21OrderPublishTradeBusiness.cs
  6. 10
      Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs
  7. 4
      Binance.TradeRobot.Business/Business/TradeBusiness/Spot/D21TradeBusiness.cs
  8. 20
      Binance.TradeRobot.Business/GlobalContext.cs
  9. 18
      Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs
  10. 8
      SDKAdapter/Model/SpotOrderTradePublishInfo.cs
  11. 2
      SDKAdapter/SDKAdapter.csproj
  12. 183
      SDKAdapter/WebSockets/Order/Spot/BinanceSpotOrderWebSocketClient.cs
  13. 7
      SDKAdapter/WebSockets/Order/Spot/SpotOrderWebSocketClient.cs
  14. 112
      SDKAdapter/WebSockets/Order/SpotOrder/BinanceSpotOrderWebSocketClient.cs

3
.editorconfig

@ -20,3 +20,6 @@ dotnet_diagnostic.CS8600.severity = none
# CS8601: 引用类型赋值可能为 null。
dotnet_diagnostic.CS8601.severity = none
# CS8629: 可为 null 的值类型可为 null。
dotnet_diagnostic.CS8629.severity = none

4
Binance.TradeRobot.API/Controllers/OrderController.cs

@ -17,7 +17,7 @@ namespace Binance.TradeRobot.API.Controllers
}
/// <summary>
/// 获取现货/逐仓杠杆订单记录
/// 获取现货/逐仓杠杆订单记录(最近20条)
/// </summary>
/// <param name="robotId"></param>
/// <returns></returns>
@ -28,7 +28,7 @@ namespace Binance.TradeRobot.API.Controllers
}
/// <summary>
/// 获取执行日志记录
/// 获取执行日志记录(最近50条)
/// </summary>
/// <param name="robotId"></param>
/// <returns></returns>

4
Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj

@ -24,4 +24,8 @@
<ProjectReference Include="..\SDKAdapter\SDKAdapter.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Business\OrderPublishBusiness\UPrep\" />
</ItemGroup>
</Project>

20
Binance.TradeRobot.Business/Business/OrderPublishBusiness/Spot/BaseSpotOrderPublishTradeBusiness.cs

@ -0,0 +1,20 @@
using Microsoft.Extensions.Caching.Memory;
using SDKAdapter.Model;
using System;
using Yitter.IdGenerator;
namespace Binance.TradeRobot.Business
{
public class BaseSpotOrderPublishTradeBusiness : BaseBusiness
{
public BaseSpotOrderPublishTradeBusiness(IFreeSql fsql,
NLogManager logManager,
IIdGenerator idGenerator,
IMemoryCache memoryCache) : base(fsql, logManager, idGenerator, memoryCache) { }
public virtual void OnSpotOrderPublish(SpotOrderTradePublishInfo spotOrderTradePublishInfo)
{
throw new NotImplementedException();
}
}
}

10
Binance.TradeRobot.Business/Business/OrderPublishBusiness/Spot/D21OrderPublishTradeBusiness.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Binance.TradeRobot.Business.Business.OrderPublishBusiness.Spot
{
internal class D21OrderPublishTradeBusiness
{
}
}

10
Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs

@ -41,11 +41,17 @@ namespace Binance.TradeRobot.Business
dingBusiness.Send($"{errorMsg} {ex.Message}");
}
protected string CreateClientOrderId(long robotId)
/// <summary>
/// 创建客户端订单号
/// </summary>
/// <param name="robotId"></param>
/// <param name="tradePolicy"></param>
/// <returns></returns>
protected string CreateClientOrderId(long robotId, Enums.TradePolicy tradePolicy)
{
var guid = Guid.NewGuid();
var random = new Random(guid.GetHashCode());
return $"{Convert.ToChar(random.Next(97, 123))}{guid.ToString().Substring(0, 4)}_{robotId}";
return $"{Convert.ToChar(random.Next(97, 123))}{guid.ToString().Substring(0, 4)}_{robotId}_{(int)tradePolicy}";
}
}
}

4
Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs → Binance.TradeRobot.Business/Business/TradeBusiness/Spot/D21TradeBusiness.cs

@ -209,7 +209,7 @@ namespace Binance.TradeRobot.Business
#region 下单
step = "下单";
var clientOrderId = CreateClientOrderId(robot.Id);
var clientOrderId = CreateClientOrderId(robot.Id, robot.TradePolicy);
var orderId = apiClient.IsolatedMarginPlaceOrder(robot.Symbol,
Enums.TradeDirection.Buy,
Enums.OrderType.MARKET,
@ -313,7 +313,7 @@ namespace Binance.TradeRobot.Business
var newestPrice = globalContext.GetSpotNewestPrice(robot.KLineKey) ?? singalRequest.ClosePrice;
var apiClient = GetBaseAPIClient(robot.ExchangeId, robot.ExchangeAPIKey.AccountId, robot.ExchangeAPIKey.APIKey, robot.ExchangeAPIKey.SecretKey);
var clientOrderId = CreateClientOrderId(robot.Id);
var clientOrderId = CreateClientOrderId(robot.Id, robot.TradePolicy);
var orderId = apiClient.IsolatedMarginPlaceOrder(robot.Symbol,
Enums.TradeDirection.Sell,
Enums.OrderType.MARKET,

20
Binance.TradeRobot.Business/GlobalContext.cs

@ -1,8 +1,9 @@
using Binance.TradeRobot.Common.DI;
using Binance.TradeRobot.Model.Dto;
using Microsoft.Extensions.DependencyInjection;
using SDKAdapter.Model;
using SDKAdapter.WebSockets.Market;
using SDKAdapter.WebSockets.Order.SpotOrder;
using SDKAdapter.WebSockets.Order.Spot;
using System.Collections.Generic;
namespace Binance.TradeRobot.Business
@ -52,17 +53,17 @@ namespace Binance.TradeRobot.Business
/// <param name="robot"></param>
public void SubscribeOrderPublish(RobotResponse robot)
{
if (!spotOrderWebSocketClientDictionary.TryGetValue(robot.OrderPublishListenKey, out SpotOrderWebSocketClient spotOrderWebSocketClient))
if (!spotOrderWebSocketClientDictionary.TryGetValue(robot.OrderPublishKey, out SpotOrderWebSocketClient spotOrderWebSocketClient))
{
spotOrderWebSocketClient = SpotOrderWebSocketClient.Create(robot.BusinessType,
robot.ExchangeId,
robot.ExchangeAPIKey.AccountId,
robot.ExchangeAPIKey.APIKey,
robot.ExchangeAPIKey.SecretKey,
logManager.GetLogger(robot.OrderPublishListenKey),
(e) => { });
logManager.GetLogger(robot.OrderPublishKey),
OnSpotOrderPublish);
}
spotOrderWebSocketClient.Start();
spotOrderWebSocketClient.Start(robot.Symbol);
}
@ -73,8 +74,8 @@ namespace Binance.TradeRobot.Business
/// <param name="robot"></param>
public void UnSubscribeOrderPublish(RobotResponse robot)
{
if (spotOrderWebSocketClientDictionary.TryGetValue(robot.OrderPublishListenKey, out SpotOrderWebSocketClient spotOrderWebSocketClient))
spotOrderWebSocketClient.Stop();
if (spotOrderWebSocketClientDictionary.TryGetValue(robot.OrderPublishKey, out SpotOrderWebSocketClient spotOrderWebSocketClient))
spotOrderWebSocketClient.Stop(robot.Symbol);
}
/// <summary>
@ -88,5 +89,10 @@ namespace Binance.TradeRobot.Business
return spotMarketWebSocketClient.NewestPrice;
return null;
}
public void OnSpotOrderPublish(SpotOrderTradePublishInfo spotOrderTradePublishInfo)
{
}
}
}

18
Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs

@ -30,27 +30,27 @@ namespace Binance.TradeRobot.Model.Dto
/// 订单推送监听实例Key
/// <para>币安逐仓杠杆需要单独的运行实例</para>
/// </summary>
public virtual string OrderPublishListenKey
public virtual string OrderPublishKey
{
get
{
string key = $"{BusinessType}-{ExchangeAPIKey.AccountId}"; //币安现货,币安合约,同一个账户内不区分websocket实例
if (ExchangeId == Enums.Exchange.Binance && BusinessType == Enums.BusinessType.IsolateMargin)
key = $"{BusinessType}-{ExchangeAPIKey.AccountId}-{Symbol}"; //币安逐仓杠杆,同一个账户内的每个交易对需要区分websocket实例
return $"OrderPublish(Origin)-{ExchangeId}-{key}";
return $"OrderPublish-{ExchangeId}-{key}";
}
}
/// <summary>
/// 订单推送日志Key
/// </summary>
public virtual string OrderPublishLogKey
{
get
{
return $"OrderPublish-{ExchangeId}-{BusinessType}-{Symbol}";
}
}
//public virtual string OrderPublishLogKey
//{
// get
// {
// return $"OrderPublish-{ExchangeId}-{BusinessType}-{Symbol}";
// }
//}
public SymbolInfoResponse SymbolInfo { get; set; }

8
SDKAdapter/Model/SpotOrderTradePublishInfo.cs

@ -7,12 +7,16 @@ namespace SDKAdapter.Model
{
public class SpotOrderTradePublishInfo
{
public long OrderId { get; set; }
public long RobotId { get; set; }
public Enums.TradePolicy TradePolicy { get; set; }
public Enums.Exchange Exchange { get; set; }
public long AccountId { get; set; }
public long OrderId { get; set; }
public string ClientOrderId { get; set; }
public string Symbol { get; set; }

2
SDKAdapter/SDKAdapter.csproj

@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<Folder Include="WebSockets\Order\UPrepOrder\" />
<Folder Include="WebSockets\Order\UPrep\" />
</ItemGroup>
<ItemGroup>

183
SDKAdapter/WebSockets/Order/Spot/BinanceSpotOrderWebSocketClient.cs

@ -0,0 +1,183 @@
using Binance.Net.Clients;
using Binance.Net.Objects;
using Binance.TradeRobot.Model.Base;
using CryptoExchange.Net.Authentication;
using Newtonsoft.Json;
using SDKAdapter.Model;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
namespace SDKAdapter.WebSockets.Order.Spot
{
public class BinanceSpotOrderWebSocketClient : SpotOrderWebSocketClient
{
private BinanceSocketClient binanceSocketClient;
private BinanceClient binanceClient;
private CancellationTokenSource cancellationTokenSource;
private string listenKey;
private IList<Binance.Net.Enums.OrderStatus> ignoreOrderStateList;
public BinanceSpotOrderWebSocketClient(Enums.BusinessType businessType,
long accountId,
string apiKey,
string secret,
NLog.ILogger logger,
Action<SpotOrderTradePublishInfo> onOrderUpdated) : base(businessType,
accountId,
apiKey,
secret,
logger,
onOrderUpdated)
{
var spotClientOption = new BinanceApiClientOptions()
{
BaseAddress = "https://api.binance.com",
ApiCredentials = new ApiCredentials(apiKey, secret)
};
//var usdFuturesClientOption = new BinanceApiClientOptions()
//{
// BaseAddress = "https://fapi.binance.com",
// ApiCredentials = new ApiCredentials(apiKey, secret)
//};
binanceClient = new BinanceClient(new BinanceClientOptions()
{
//UsdFuturesApiOptions = usdFuturesClientOption,
SpotApiOptions = spotClientOption
});
binanceSocketClient = new BinanceSocketClient();
listenKey = string.Empty;
ignoreOrderStateList = new List<Binance.Net.Enums.OrderStatus>()
{
Binance.Net.Enums.OrderStatus.New,
Binance.Net.Enums.OrderStatus.PendingCancel,
Binance.Net.Enums.OrderStatus.Insurance,
Binance.Net.Enums.OrderStatus.Adl
};
}
public override void Start(string symbol = "")
{
if (IsConnected)
return;
IsConnected = true;
cancellationTokenSource = new CancellationTokenSource();
var getListenKeyResponse = binanceClient.SpotApi.Account.StartIsolatedMarginUserStreamAsync(symbol).Result;
if (!getListenKeyResponse.Success)
throw new Exception(getListenKeyResponse.Error?.Message ?? "");
listenKey = getListenKeyResponse.Data;
_ = binanceSocketClient.SpotStreams.SubscribeToUserDataUpdatesAsync(listenKey,
(e) =>
{
try
{
var originData = new StringBuilder();
var isStop = false;
if (ignoreOrderStateList.Contains(e.Data.Status))
{
originData.Append($"不支持的订单状态{e.Data.Status}");
isStop = true;
}
else if (string.IsNullOrEmpty(e.Data.ClientOrderId))
{
originData.Append($"缺少ClientOrderId");
isStop = true;
}
long? robotId = null;
Enums.TradePolicy? tradePolicy = null;
if (!isStop)
{
var match = Regex.Match(e.Data.ClientOrderId, @"^([a-z0-9]{5})_(\d{15,})_(\d{1,2})$");
if (!match.Success)
{
originData.Append($"非机器人交易的ClientOrderId");
isStop = true;
}
else
{
robotId = long.Parse(match.Groups[2].Value);
tradePolicy = (Enums.TradePolicy)int.Parse(match.Groups[3].Value);
}
}
originData.Append($" {JsonConvert.SerializeObject(e.Data)}");
logger.Info(originData);
if (isStop)
return;
var orderState = Enums.SpotOrderState.Unknow;
switch (e.Data.Status)
{
case Binance.Net.Enums.OrderStatus.PartiallyFilled:
case Binance.Net.Enums.OrderStatus.Filled:
case Binance.Net.Enums.OrderStatus.Canceled:
orderState = (Enums.SpotOrderState)(int)e.Data.Status;
break;
case Binance.Net.Enums.OrderStatus.Rejected:
orderState = Enums.SpotOrderState.Rejected;
break;
case Binance.Net.Enums.OrderStatus.Expired:
orderState = Enums.SpotOrderState.Expired;
break;
}
OnOrderUpdated?.Invoke(new SpotOrderTradePublishInfo()
{
OrderId = e.Data.Id,
RobotId = robotId.Value,
TradePolicy = tradePolicy.Value,
Symbol = e.Data.Symbol,
AccountId = this.AccountId,
OrderType = (Enums.OrderType)(int)e.Data.Type,
SpotOrderState = orderState,
TradeDirection = (Enums.TradeDirection)(int)e.Data.Side,
ClientOrderId = e.Data.ClientOrderId,
CummulativeTradeAmount = e.Data.QuoteQuantityFilled,
CummulativeTradeQuantity = e.Data.QuantityFilled,
Exchange = Enums.Exchange.Binance,
Fee = e.Data.Fee,
FeeUnit = e.Data.FeeAsset,
LastTradeAmount = e.Data.LastQuoteQuantity,
LastTradePrice = e.Data.LastPriceFilled,
LastTradeQuantity = e.Data.LastQuantityFilled,
LastTradeTime = e.Data.UpdateTime,
CreateTime = e.Data.CreateTime
});
}
catch (Exception ex)
{
logger.Error(ex);
}
},
(e) =>
{
},
(e) =>
{
},
(e) =>
{
},
cancellationTokenSource.Token);
}
public override void Stop(string symbol = "")
{
if (!IsConnected)
return;
IsConnected = false;
cancellationTokenSource.Cancel();
binanceSocketClient.SpotStreams.Dispose();
cancellationTokenSource = null;
_ = binanceClient.SpotApi.Account.CloseIsolatedMarginUserStreamAsync(symbol, listenKey).Result;
listenKey = string.Empty;
}
}
}

7
SDKAdapter/WebSockets/Order/SpotOrder/SpotOrderWebSocketClient.cs → SDKAdapter/WebSockets/Order/Spot/SpotOrderWebSocketClient.cs

@ -2,7 +2,7 @@
using SDKAdapter.Model;
using System;
namespace SDKAdapter.WebSockets.Order.SpotOrder
namespace SDKAdapter.WebSockets.Order.Spot
{
public class SpotOrderWebSocketClient
{
@ -19,17 +19,18 @@ namespace SDKAdapter.WebSockets.Order.SpotOrder
public static SpotOrderWebSocketClient Create(Enums.BusinessType businessType, Enums.Exchange exchange, long accountId, string apiKey, string secret, NLog.ILogger logger, Action<SpotOrderTradePublishInfo> onOrderUpdated)
{
if (exchange == Enums.Exchange.Binance)
return new BinanceSpotOrderWebSocketClient(businessType, accountId, apiKey, secret, logger);
return new BinanceSpotOrderWebSocketClient(businessType, accountId, apiKey, secret, logger, onOrderUpdated);
return null;
}
public SpotOrderWebSocketClient(Enums.BusinessType businessType, long accountId, string apiKey, string secret, NLog.ILogger logger)
public SpotOrderWebSocketClient(Enums.BusinessType businessType, long accountId, string apiKey, string secret, NLog.ILogger logger, Action<SpotOrderTradePublishInfo> onOrderUpdated)
{
this.BusinessType = businessType;
this.AccountId = accountId;
this.ApiKey = apiKey;
this.Secret = secret;
this.logger = logger;
this.OnOrderUpdated = onOrderUpdated;
}
public virtual void Start(string symbol = "")

112
SDKAdapter/WebSockets/Order/SpotOrder/BinanceSpotOrderWebSocketClient.cs

@ -1,112 +0,0 @@
using Binance.Net.Clients;
using Binance.Net.Objects;
using Binance.TradeRobot.Model.Base;
using CryptoExchange.Net.Authentication;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading;
namespace SDKAdapter.WebSockets.Order.SpotOrder
{
public class BinanceSpotOrderWebSocketClient : SpotOrderWebSocketClient
{
private BinanceSocketClient binanceSocketClient;
private BinanceClient binanceClient;
private CancellationTokenSource cancellationTokenSource;
private string listenKey;
private IList<Binance.Net.Enums.OrderStatus> unSupportStateList;
public BinanceSpotOrderWebSocketClient(Enums.BusinessType businessType, long accountId, string apiKey, string secret, NLog.ILogger logger)
: base(businessType, accountId, apiKey, secret, logger)
{
var spotClientOption = new BinanceApiClientOptions()
{
BaseAddress = "https://api.binance.com",
ApiCredentials = new ApiCredentials(apiKey, secret)
};
//var usdFuturesClientOption = new BinanceApiClientOptions()
//{
// BaseAddress = "https://fapi.binance.com",
// ApiCredentials = new ApiCredentials(apiKey, secret)
//};
binanceClient = new BinanceClient(new BinanceClientOptions()
{
//UsdFuturesApiOptions = usdFuturesClientOption,
SpotApiOptions = spotClientOption
});
binanceSocketClient = new BinanceSocketClient();
listenKey = string.Empty;
unSupportStateList = new List<Binance.Net.Enums.OrderStatus>()
{
Binance.Net.Enums.OrderStatus.PendingCancel,
Binance.Net.Enums.OrderStatus.Insurance,
Binance.Net.Enums.OrderStatus.Adl
};
}
public override void Start(string symbol = "")
{
if (IsConnected)
return;
IsConnected = true;
cancellationTokenSource = new CancellationTokenSource();
var getListenKeyResponse = binanceClient.SpotApi.Account.StartIsolatedMarginUserStreamAsync(symbol).Result;
if (!getListenKeyResponse.Success)
throw new Exception(getListenKeyResponse.Error?.Message ?? "");
listenKey = getListenKeyResponse.Data;
binanceSocketClient.SpotStreams.SubscribeToUserDataUpdatesAsync(listenKey,
(e) =>
{
logger.Info(JsonConvert.SerializeObject(e.Data));
if (unSupportStateList.Contains(e.Data.Status))
return;
OnOrderUpdated?.Invoke(new Model.SpotOrderTradePublishInfo()
{
OrderId = e.Data.Id,
Symbol = e.Data.Symbol,
AccountId = this.AccountId,
OrderType = (Enums.OrderType)(int)e.Data.Type,
SpotOrderState = (Enums.SpotOrderState)(int)e.Data.Status,
TradeDirection = (Enums.TradeDirection)(int)e.Data.Side,
ClientOrderId = e.Data.ClientOrderId,
CummulativeTradeAmount = e.Data.QuoteQuantityFilled,
CummulativeTradeQuantity = e.Data.QuantityFilled,
Exchange = Enums.Exchange.Binance,
Fee = e.Data.Fee,
FeeUnit = e.Data.FeeAsset,
LastTradeAmount = e.Data.LastQuoteQuantity,
LastTradePrice = e.Data.LastPriceFilled,
LastTradeQuantity = e.Data.LastQuantityFilled,
LastTradeTime = e.Data.UpdateTime,
CreateTime = e.Data.CreateTime
});
},
(e) =>
{
},
(e) =>
{
},
(e) =>
{
},
cancellationTokenSource.Token);
}
public override void Stop(string symbol = "")
{
if (!IsConnected)
return;
IsConnected = false;
cancellationTokenSource.Cancel();
binanceSocketClient.SpotStreams.Dispose();
cancellationTokenSource = null;
_ = binanceClient.SpotApi.Account.CloseIsolatedMarginUserStreamAsync(symbol, listenKey).Result;
listenKey = string.Empty;
}
}
}
Loading…
Cancel
Save