Browse Source

对接交易所交易对

master
shanji 3 years ago
parent
commit
e191b6606d
  1. 6
      .editorconfig
  2. 19
      Binance.TradeRobot.API/Binance.TradeRobot.API.xml
  3. 31
      Binance.TradeRobot.API/Controllers/ExchangeAccountController.cs
  4. 4
      Binance.TradeRobot.API/Controllers/SingalController.cs
  5. 56
      Binance.TradeRobot.Business/Business/ExchangeBusiness.cs
  6. 20
      Binance.TradeRobot.Business/Business/RobotBusiness.cs
  7. 2
      Binance.TradeRobot.Business/Business/SingalBusiness.cs
  8. 55
      Binance.TradeRobot.Common/Http/RestAPIService.cs
  9. 6
      Binance.TradeRobot.Model/Base/MappingProfiles.cs
  10. 10
      Binance.TradeRobot.Model/Binance.TradeRobot.Model.xml
  11. 32
      Binance.TradeRobot.Model/Db/Exchange/SymbolInfo.cs
  12. 11
      Binance.TradeRobot.Model/Db/Robot/Robot.cs
  13. 2
      Binance.TradeRobot.Model/Db/代码生成/__重新生成.bat
  14. 8
      Binance.TradeRobot.Model/Dto/Response/Exchange/SymbolInfoResponse.cs
  15. 2
      Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs

6
.editorconfig

@ -14,3 +14,9 @@ dotnet_diagnostic.CS8602.severity = none
# CS8603: 可能返回 null 引用。
dotnet_diagnostic.CS8603.severity = none
# CS8600: 将 null 字面量或可能为 null 的值转换为非 null 类型。
dotnet_diagnostic.CS8600.severity = none
# CS8601: 引用类型赋值可能为 null。
dotnet_diagnostic.CS8601.severity = none

19
Binance.TradeRobot.API/Binance.TradeRobot.API.xml

@ -4,6 +4,23 @@
<name>Binance.TradeRobot.API</name>
</assembly>
<members>
<member name="M:Binance.TradeRobot.API.Controllers.ExchangeAccountController.SyncBinanceSymbol">
<summary>
同步币安交易对
</summary>
</member>
<member name="M:Binance.TradeRobot.API.Controllers.ExchangeAccountController.SyncGateIOSymbol">
<summary>
同步Gate.IO交易对
</summary>
</member>
<member name="M:Binance.TradeRobot.API.Controllers.ExchangeAccountController.GetSymbolList(Binance.TradeRobot.Model.Base.Enums.Exchange)">
<summary>
获取交易对列表
</summary>
<param name="exchange"></param>
<returns></returns>
</member>
<member name="M:Binance.TradeRobot.API.Controllers.ExchangeAccountController.AddExchangeAccount(Binance.TradeRobot.Model.Dto.AddExchangeAccountRequest)">
<summary>
添加交易所账号
@ -61,7 +78,7 @@
</summary>
<returns></returns>
</member>
<member name="M:Binance.TradeRobot.API.Controllers.SingalController.D21Singnal(Binance.TradeRobot.Model.Dto.D21SingalRequest)">
<member name="M:Binance.TradeRobot.API.Controllers.SingalController.D21Singal(Binance.TradeRobot.Model.Dto.D21SingalRequest)">
<summary>
D21杠杆/合约信号接口
</summary>

31
Binance.TradeRobot.API/Controllers/ExchangeAccountController.cs

@ -18,6 +18,37 @@ namespace Binance.TradeRobot.API.Controllers
this.exchangeBusiness = exchangeBusiness;
}
/// <summary>
/// 同步币安交易对
/// </summary>
[HttpPost]
[AllowAnonymous]
public void SyncBinanceSymbol()
{
exchangeBusiness.SyncBinanceSymbol();
}
/// <summary>
/// 同步Gate.IO交易对
/// </summary>
[HttpPost]
[AllowAnonymous]
public void SyncGateIOSymbol()
{
exchangeBusiness.SyncGateIOSymbol();
}
/// <summary>
/// 获取交易对列表
/// </summary>
/// <param name="exchange"></param>
/// <returns></returns>
[HttpGet("{exchange}")]
public IList<SymbolInfoResponse> GetSymbolList(Enums.Exchange exchange)
{
return exchangeBusiness.GetSymbolList(exchange);
}
/// <summary>
/// 添加交易所账号
/// </summary>

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

@ -18,9 +18,9 @@ namespace Binance.TradeRobot.API.Controllers
/// </summary>
/// <param name="d21SingalRequest"></param>
[HttpPost]
public void D21Singnal([FromBody] D21SingalRequest d21SingalRequest)
public void D21Singal([FromBody] D21SingalRequest d21SingalRequest)
{
singalBusiness.D21Singnal(d21SingalRequest);
singalBusiness.D21Singal(d21SingalRequest);
}
}
}

56
Binance.TradeRobot.Business/Business/ExchangeBusiness.cs

@ -1,14 +1,17 @@
using Binance.TradeRobot.Business.Extensions;
using Binance.TradeRobot.Common.DI;
using Binance.TradeRobot.Common.Extensions;
using Binance.TradeRobot.Common.Http;
using Binance.TradeRobot.Model.Base;
using Binance.TradeRobot.Model.Db;
using Binance.TradeRobot.Model.Dto;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Yitter.IdGenerator;
@ -18,7 +21,58 @@ namespace Binance.TradeRobot.Business.Exchange
[BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Self)]
public class ExchangeBusiness : BaseBusiness
{
public ExchangeBusiness(IFreeSql fsql, NLogManager logManager, IIdGenerator idGenerator, IMemoryCache memoryCache) : base(fsql, logManager, idGenerator, memoryCache) { }
private RestApiService restApiService;
public ExchangeBusiness(IFreeSql fsql,
NLogManager logManager,
IIdGenerator idGenerator,
IMemoryCache memoryCache,
RestApiService restApiService) : base(fsql, logManager, idGenerator, memoryCache)
{
this.restApiService = restApiService;
}
public void SyncBinanceSymbol()
{
var apiResult = restApiService.SendRequest("https://api.binance.com/", "api/v3/exchangeInfo", null, null, HttpMethod.Get);
if (apiResult.StatusCode != System.Net.HttpStatusCode.OK)
throw new BusinessException($"同步币安交易对失败 StatusCode {apiResult.StatusCode} {apiResult.Content}");
var jobject = JObject.Parse(apiResult.Content);
var jarray_symbols = (JArray)jobject["symbols"];
var symbolList = new List<SymbolInfo>();
foreach (JToken jtoken_symbol in jarray_symbols)
{
var symbol = jtoken_symbol.Value<string>("symbol").ToUpper();
if (!symbol.EndsWith("USDT"))
continue;
var stepSize = jtoken_symbol["filters"]?.FirstOrDefault(jtoken_filters => jtoken_filters.Value<string>("filterType") == "LOT_SIZE")?.Value<decimal>("stepSize") ?? 0M;
var saleAccuracy = stepSize != 0 ? (1 / stepSize).ToString().Length - 1 : 0;
var symbolInfo = new SymbolInfo()
{
Id = idGenerator.NewLong(),
ExchangeId = Enums.Exchange.Binance,
CreateTime = DateTime.Now,
Symbol = symbol,
SaleQuantityAccuracy = saleAccuracy
};
symbolList.Add(symbolInfo);
}
fsql.Transaction(() =>
{
fsql.Delete<SymbolInfo>().Where(s => s.ExchangeId == Enums.Exchange.Binance).ExecuteAffrows();
fsql.Insert(symbolList).ExecuteAffrows();
});
}
public void SyncGateIOSymbol()
{
}
public IList<SymbolInfoResponse> GetSymbolList(Enums.Exchange exchange)
{
return fsql.Select<SymbolInfo>().Where(s => s.ExchangeId == exchange).ToList<SymbolInfoResponse>();
}
public void AddExchangeAccount(AddExchangeAccountRequest addExchangeAccountRequest)
{

20
Binance.TradeRobot.Business/Business/RobotBusiness.cs

@ -199,14 +199,15 @@ namespace Binance.TradeRobot.Business
bool isLoadRecentTradeProfit = true,
bool isLoadAPIKey = false)
{
var robotList = fsql.Select<Robot, RobotAccount, D21Policy, ExchangeAPIKey>().InnerJoin((r, ra, d, e) => r.Id == ra.RobotId)
.InnerJoin((r, ra, d, e) => r.Id == d.RobotId)
.InnerJoin((r, ra, d, e) => r.Id == e.RobotId)
.WhereIf(robotState != null, (r, ra, d, e) => r.State == robotState)
.WhereIf(signalPeriod != null, (r, ra, d, e) => d.PeriodicSignal == signalPeriod)
.WhereIf(!string.IsNullOrEmpty(symbol), (r, ra, d, e) => r.Symbol == symbol)
.Where((r, ra, d, e) => r.TradePolicy == Enums.TradePolicy.D21)
.ToList((r, ra, d, e) => new Robot()
var robotList = fsql.Select<Robot, SymbolInfo, RobotAccount, D21Policy, ExchangeAPIKey>().InnerJoin((r, s, ra, d, e) => r.Id == ra.RobotId)
.InnerJoin((r, s, ra, d, e) => r.Symbol == s.Symbol && r.ExchangeId == s.ExchangeId)
.InnerJoin((r, s, ra, d, e) => r.Id == d.RobotId)
.InnerJoin((r, s, ra, d, e) => r.Id == e.RobotId)
.WhereIf(robotState != null, (r, s, ra, d, e) => r.State == robotState)
.WhereIf(signalPeriod != null, (r, s, ra, d, e) => d.PeriodicSignal == signalPeriod)
.WhereIf(!string.IsNullOrEmpty(symbol), (r, s, ra, d, e) => r.Symbol == symbol)
.Where((r, s, ra, d, e) => r.TradePolicy == Enums.TradePolicy.D21)
.ToList((r, s, ra, d, e) => new Robot()
{
Id = r.Id,
BusinessType = r.BusinessType,
@ -217,6 +218,9 @@ namespace Binance.TradeRobot.Business
CreateTime = r.CreateTime,
TradePolicy = r.TradePolicy,
SymbolId = s.Id,
SymbolSaleQuantityAccuracy = s.SaleQuantityAccuracy,
RobotAccountId = ra.Id,
ClosePositionCount = ra.ClosePositionCount,
WinCount = ra.WinCount,

2
Binance.TradeRobot.Business/Business/SingalBusiness.cs

@ -27,7 +27,7 @@ namespace Binance.TradeRobot.Business.Business
this.tradeBusinessList = tradeBusinessList;
}
public void D21Singnal(D21SingalRequest d21SingalRequest)
public void D21Singal(D21SingalRequest d21SingalRequest)
{
//logManager.GetLogger("D21").Info(JsonConvert.SerializeObject(d21SingalRequest));
var robotList = robotBusiness.GetD21PolicyRobotList(Enums.RobotState.Runing, d21SingalRequest.KLinePeriodic, d21SingalRequest.Symbol, false, true);

55
Binance.TradeRobot.Common/Http/RestAPIService.cs

@ -2,7 +2,9 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
namespace Binance.TradeRobot.Common.Http
@ -26,27 +28,34 @@ namespace Binance.TradeRobot.Common.Http
/// <param name="apiHost"></param>
/// <param name="apiPath"></param>
/// <param name="param"></param>
/// <param name="headers"></param>
/// <param name="requestHeaders"></param>
/// <param name="httpMethod"></param>
/// <param name="contentType"></param>
/// <param name="paramPosition"></param>
/// <param name="enableRandomTimeStamp"></param>
/// <param name="useRandomTimeStamp"></param>
/// <param name="getResponseHeader"></param>
/// <param name="httpCompletionOption"></param>
/// <param name="httpClientName"></param>
/// <returns></returns>
public string SendRequest(string apiHost,
public RestApiResult SendRequest(string apiHost,
string apiPath,
object param,
IDictionary<string, string> headers,
IDictionary<string, string> requestHeaders,
HttpMethod httpMethod,
string contentType = ContentType_Json,
ParamPosition paramPosition = ParamPosition.Body,
bool enableRandomTimeStamp = false)
bool useRandomTimeStamp = false,
bool getResponseHeader = false,
HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead,
string httpClientName = "")
{
//Get和Delete强制使用QueryString形式传参
if (httpMethod == HttpMethod.Get || httpMethod == HttpMethod.Delete)
paramPosition = ParamPosition.Query;
//拼接Url
var url = $"{apiHost}{(apiHost.EndsWith("/") ? string.Empty : "/")}{(apiPath.StartsWith("/") ? apiPath.Substring(1) : apiPath)}";
var url = string.IsNullOrEmpty(apiPath) ? apiHost :
$"{apiHost.TrimEnd('/')}/{apiPath.TrimStart('/')}";
var isCombineParam = false;
if (paramPosition == ParamPosition.Query && param != null)
{
@ -55,8 +64,8 @@ namespace Binance.TradeRobot.Common.Http
}
//使用时间戳绕过CDN
if (enableRandomTimeStamp)
url = $"{url}{(isCombineParam ? "&" : "?")}t={DateTime.Now.DateTimeToStamp()}";
if (useRandomTimeStamp)
url = $"{url}{(isCombineParam ? "&" : "?")}t={DateTime.Now.ToFileTime()}";
using (var httpClient = httpClientFactory.CreateClient())
@ -64,22 +73,38 @@ namespace Binance.TradeRobot.Common.Http
httpClient.Timeout = TimeOut;
using (var request = new HttpRequestMessage(httpMethod, url))
{
if (headers != null && headers.Count > 0)
foreach (var key in headers.Keys)
request.Headers.Add(key, headers[key]);
if (requestHeaders != null && requestHeaders.Count > 0)
foreach (var key in requestHeaders.Keys)
request.Headers.Add(key, requestHeaders[key]);
if (paramPosition == ParamPosition.Body && param != null)
request.Content = new StringContent(contentType == ContentType_Json ? JsonConvert.SerializeObject(param) : param.ToString(), Encoding.UTF8, contentType);
using (var response = httpClient.SendAsync(request).Result)
using (var response = httpClient.SendAsync(request, httpCompletionOption).Result)
{
if (!response.IsSuccessStatusCode)
throw new Exception($"HttpCode {response.StatusCode}");
return response.Content.ReadAsStringAsync().Result;
if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.Redirect && response.StatusCode != HttpStatusCode.Moved)
throw new Exception($"Reuqest {url} HttpCode {response.StatusCode}");
return new RestApiResult()
{
StatusCode = response.StatusCode,
Content = httpCompletionOption == HttpCompletionOption.ResponseContentRead ? response.Content.ReadAsStringAsync().Result :
string.Empty,
Headers = getResponseHeader ? response.Headers : null
};
}
}
}
}
}
public class RestApiResult
{
public HttpStatusCode StatusCode { get; set; }
public string Content { get; set; }
public HttpResponseHeaders Headers { get; set; }
}
/// <summary>

6
Binance.TradeRobot.Model/Base/MappingProfiles.cs

@ -26,7 +26,11 @@ namespace Binance.TradeRobot.Model.Base
.ForPath(t => t.RobotAccount.WinCount, opt => opt.MapFrom(f => f.WinCount))
.ForPath(t => t.ExchangeAPIKey.AccountId, opt => opt.MapFrom(f => f.ExchangeAccountId))
.ForPath(t => t.ExchangeAPIKey.APIKey, opt => opt.MapFrom(f => f.ExchangeAPIKey))
.ForPath(t => t.ExchangeAPIKey.SecretKey, opt => opt.MapFrom(f => f.ExchangeSecretKey));
.ForPath(t => t.ExchangeAPIKey.SecretKey, opt => opt.MapFrom(f => f.ExchangeSecretKey))
.ForPath(t => t.SymbolInfo.Id, opt => opt.MapFrom(f => f.SymbolId))
.ForPath(t => t.SymbolInfo.Symbol, opt => opt.MapFrom(f => f.Symbol))
.ForPath(t => t.SymbolInfo.SaleQuantityAccuracy, opt => opt.MapFrom(f => f.SymbolSaleQuantityAccuracy))
.ForPath(t => t.SymbolInfo.ExchangeId, opt => opt.MapFrom(f => f.ExchangeId));
CreateMap<Robot, D21PolicyRobotResponse>().IncludeBase<Robot, RobotResponse>()
.ForPath(t => t.D21Policy.Id, opt => opt.MapFrom(f => f.D21PolicyId))
.ForPath(t => t.D21Policy.RobotId, opt => opt.MapFrom(f => f.Id))

10
Binance.TradeRobot.Model/Binance.TradeRobot.Model.xml

@ -169,6 +169,11 @@
关联机器人Id
</summary>
</member>
<member name="P:Binance.TradeRobot.Model.Db.SymbolInfo.SaleQuantityAccuracy">
<summary>
基础币卖出数量精度
</summary>
</member>
<member name="P:Binance.TradeRobot.Model.Db.ExecutionLog.SourceSingal">
<summary>
来源信号
@ -339,6 +344,11 @@
运行时长(s)
</summary>
</member>
<member name="P:Binance.TradeRobot.Model.Db.Robot.SymbolId">
<summary>
卖币精度
</summary>
</member>
<member name="P:Binance.TradeRobot.Model.Db.Robot.ClosePositionCount">
<summary>
平仓次数

32
Binance.TradeRobot.Model/Db/Exchange/SymbolInfo.cs

@ -0,0 +1,32 @@
using Binance.TradeRobot.Model.Base;
using FreeSql.DataAnnotations;
using System;
namespace Binance.TradeRobot.Model.Db
{
[Table(Name = "symbolinfo", DisableSyncStructure = true)]
public partial class SymbolInfo
{
[Column(DbType = "bigint", IsPrimary = true)]
public long Id { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
[Column(DbType = "int", MapType = typeof(int))]
public Enums.Exchange ExchangeId { get; set; }
/// <summary>
/// 基础币卖出数量精度
/// </summary>
[Column(DbType = "int")]
public int SaleQuantityAccuracy { get; set; }
[Column(StringLength = 50)]
public string Symbol { get; set; }
}
}

11
Binance.TradeRobot.Model/Db/Robot/Robot.cs

@ -36,6 +36,17 @@ namespace Binance.TradeRobot.Model.Db
[Column(MapType = typeof(int), DbType = "int")]
public Enums.Exchange ExchangeId { get; set; }
#region Symbol Extension
/// <summary>
/// 卖币精度
/// </summary>
[Column(IsIgnore = true)]
public long SymbolId { get; set; }
[Column(IsIgnore = true)]
public int SymbolSaleQuantityAccuracy { get; set; }
#endregion
#region RobotAccount Extension
[Column(IsIgnore = true)]

2
Binance.TradeRobot.Model/Db/代码生成/__重新生成.bat

@ -1 +1 @@
FreeSql.Generator -Razor 1 -NameOptions 1,0,0,0 -NameSpace Binance.TradeRobot.Model.Db -DB "MySql,data source=47.245.58.112;port=3306;user id=sa;password=rYn6re2AKhcDWcBi;initial catalog=tradedb;charset=utf8;sslmode=none;max pool size=2" -FileName "{name}.cs"
FreeSql.Generator -Razor 1 -NameOptions 1,0,0,0 -NameSpace Binance.TradeRobot.Model.Db -DB "MySql,data source=8.209.252.195;port=3306;user id=sa;password=rYn6re2AKhcDWcBi;initial catalog=tradedb;charset=utf8;sslmode=none;max pool size=2" -FileName "{name}.cs"

8
Binance.TradeRobot.Model/Dto/Response/Exchange/SymbolInfoResponse.cs

@ -0,0 +1,8 @@
using Binance.TradeRobot.Model.Db;
namespace Binance.TradeRobot.Model.Dto
{
public class SymbolInfoResponse : SymbolInfo
{
}
}

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

@ -26,6 +26,8 @@ namespace Binance.TradeRobot.Model.Dto
public virtual string KLineKey { get { return $"KLine-{ExchangeId}-{Symbol}"; } }
public SymbolInfoResponse SymbolInfo { get; set; }
/// <summary>
/// 机器人账户对象
/// </summary>

Loading…
Cancel
Save