using CsvHelper; using Microsoft.Net.Http.Headers; using System.Globalization; using YLErp.Modules.ExchangeTradeModule; namespace YLErp.Modules.IntegrationModule.XiaMenXiangYu { /// /// 从金仕达日终交易接口同步场内交易 /// public class JinshidaEodTradeSyncService : YLBaseService { readonly DateTime _now; readonly IYcLogger _logger; readonly IHttpClientFactory _httpClientFactory; public JinshidaEodTradeSyncService(OptUserInfo userInfo, IHttpClientFactory httpClientFactory) : base(userInfo) { _now = DateTime.Now; _logger = LogFactory.GetLogger("金仕达场内交易同步"); _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); } public Task Execute(string url) { //15点之前和21点以后不再请求,每隔30分钟执行一次 if (_now.Hour < 15 || _now.Hour > 21 || YLErp.BLL.valuedateBLL.ValueDate > _now || YLErp.QdpModule.QdpCalendarHelper.IsHoliday(DateTime.Today)) { return Task.CompletedTask; } _logger.Info("开始执行"); if (string.IsNullOrEmpty(url)) { _logger.Info("url 配置为空,不能进行同步"); return Task.CompletedTask; } var uri = new Uri(url); var nv = YieldChain.Helpers.UrlHelper.ParseQueryString(uri.Query?.TrimStart('?')) ?? new System.Collections.Specialized.NameValueCollection(); nv["date"] = _now.ToString("yyyyMMdd"); if (string.IsNullOrWhiteSpace(nv["page_size"])) { nv["page_size"] = int.MaxValue.ToString(); } url = uri.GetLeftPart(UriPartial.Path) + "?" + YieldChain.Helpers.UrlHelper.ToQueryString(nv); var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url) { Headers = { { HeaderNames.Accept, "application/json" }, { HeaderNames.UserAgent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36" } } }; _logger.Info("发送请求:" + url); return InnerExecute(httpRequestMessage).ContinueWith(t => { if (t.Exception != null) { _logger.Error(t.Exception, "请求出错"); } }); } private async Task InnerExecute(HttpRequestMessage httpRequestMessage) { var httpClient = _httpClientFactory.CreateClient(); var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage); var content = await httpResponseMessage.Content.ReadAsStringAsync(); if (httpResponseMessage.IsSuccessStatusCode) { var result = JsonHelper.Deserialize(content); if (result.rows != null) { foreach (var item in result.rows) { try { item.SaveResult = SaveExchangeTrade(item); } catch (Exception ex) { item.SaveResult = "失败:" + ex.Message; _logger.Error(ex, $"保存交易(成交流水号:{item.DONENO_})失败"); } } } else { _logger.Info("请求返回结果:" + content); } try { var fileName = _now.ToString("yyyy_MM_dd_HH_mm"); var jsdPath = Path.Combine(AppContext.BaseDirectory, "log\\jsd"); Directory.CreateDirectory(jsdPath); var csvPath = Path.Combine(jsdPath, fileName + ".csv"); using (var writer = new StreamWriter(csvPath, false, System.Text.Encoding.UTF8)) using (var csv = new CsvWriter(writer, System.Globalization.CultureInfo.InvariantCulture)) { csv.WriteRecords(result.rows ?? Enumerable.Empty()); } //留存取到的json文本 var jsonPath = Path.Combine(jsdPath, fileName + ".json"); File.WriteAllText(jsonPath, content); } catch (Exception ex) { _logger.Error(ex, "保存csv文件失败"); } } else { _logger.Error($"请求失败,status code:{(int)httpResponseMessage.StatusCode} {httpResponseMessage.StatusCode};{content}"); } } public string SaveExchangeTrade(JinshidaEodTradeFields f) { string tradeType; if (f.DONENO_ == "0") { return "不支持此成交编号(DONENO)"; } switch (f.YWLX_) { case "1": tradeType = "商品期货"; break; case "6015": //商品期权买卖 case "6016": //商品期权行权 case "6022": //期权到期平仓 tradeType = "场内期权"; break; default: return "不支持此种业务类型(YWLX)"; } string tradeSide; switch (f.BSTP_) { case "0": tradeSide = "买入"; break; case "1": tradeSide = "卖出"; break; default: return "不支持此种买卖类型(BSTP)"; } switch (f.KPTP_) { case "0": //开仓 tradeSide = tradeSide == "买入" ? "多头开仓" : "空头开仓"; break; case "1": //平仓 case "2": //平今 tradeSide = tradeSide == "买入" ? "空头平仓" : "多头平仓"; break; default: return "不支持此种开平标记(KPTP)"; } var exchangeId = ConvertMarket(f.MKCODE_, out var isStock); if (string.IsNullOrEmpty(exchangeId)) { return "不支持此市场代码(MKCODE)"; } if (!double.TryParse(f.FSJG_, out var singlePrice)) { return "成交价(FSJG)解析失败"; } if (!double.TryParse(f.FSSL_, out var lots) || lots < 1e-6) { return "成交量(FSSL)解析失败"; } if (!DateTime.TryParseExact(f.DATE_, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var tradeDate)) { return "成交日期(DATE)解析失败:" + f.DATE_; } double.TryParse(f.FEE_, out var commission); var underlyingCode = f.STKCODE_; //暂时只有商品期货标的,所以可不处理股票标的后缀 var tradeNo = f.DONENO_ + f.BSTP_; //支持自成交 var req = new ExchangeTradeSaveApiReq { TradeNumber = tradeNo, UnderlyingCode = underlyingCode, TradeDate = tradeDate, BuySell = tradeSide, TradePrice = singlePrice, Lots = lots, TradeType = tradeType, ExchangeAccountCode = string.Concat(f.CELLID_, "#", f.PROFLID_), Commission = commission, IsHistory = true }; //金仕达trade api接口有些交易编号带日期前缀,但webapi又不带 //导致同一笔交易写入两笔数据到系统中,以下为解决这个问题 using (var db = DbContextFactory.GetYLDbContext()) { if (f.YWLX_ == "6016") { var option = db.exchange_list_option.Where(e => e.ContractCode == f.STKCODE_) .Select(n => new { n.Strike, n.OptionType, n.ContractSize }).FirstOrDefault(); if (option == null) { return "没有找到场内期权合约信息"; } var pnl = singlePrice - option.Strike; if (!ConsGlobal.CallPut.IsCall(option.OptionType)) { pnl = -pnl; } if (pnl < 1e-6) { pnl = 0; singlePrice = 0; } else { var volume = lots * (option.ContractSize < 1 ? 1 : option.ContractSize); singlePrice = pnl / volume; } req.TradePrice = singlePrice; } var reqList = new List { req.TradeNumber, f.DATE_ + req.TradeNumber }; if (req.TradeNumber.StartsWith("0")) { reqList.Add(req.TradeNumber.TrimStart('0')); } //20220415:金仕达接口给的商品期货数据,如果是来源于场内期权行权产生的,则会和场内期权交易编号一致 var query = from x in db.ExchangeTrade where x.TradeDate == req.TradeDate && x.TradeType == req.TradeType && reqList.Contains(x.TradeNumber) select x.TradeNumber; var resultList = query.ToArray(); if (resultList.Any() && !resultList.Contains(req.TradeNumber, StringComparer.OrdinalIgnoreCase)) { if (resultList.Contains(reqList[1], StringComparer.OrdinalIgnoreCase)) { req.TradeNumber = reqList[1]; } else if (reqList.Count > 2 && resultList.Contains(reqList[2], StringComparer.OrdinalIgnoreCase)) { req.TradeNumber = reqList[2]; } } } new ExchangeTradeSyncApiService(new OptUserInfo(0, "金仕达", OptUserFrom.System)).Save(req,"下单同步"); return "成功"; } //转换交易所编号 private static string ConvertMarket(string market_code, out bool isStockMarket) { isStockMarket = false; switch (market_code) { case "1"://上交所 isStockMarket = true; return "SH"; case "2"://深交所 isStockMarket = true; return "SZ"; case "3"://中金所 return "CFFEX"; case "4"://上期所 return "SHFE"; case "5": //郑商所 return "CZCE"; case "6"://大商所 return "DCE"; case "7": //上海黄金交易所 return "SGE"; case "8"://能源期货交易所 return "INE"; default: return string.Empty; } } } /// /// 金仕达接口数据回应 /// public class JinshidaEodTradeResponse { public int pageNo { get; set; } public int pageSize { get; set; } public int totalCount { get; set; } public IEnumerable rows { get; set; } } /// /// /// public class JinshidaEodTradeFields { /// /// 日期 /// public string DATE_ { get; set; } /// /// 流水号 /// public string SERIALID_ { get; set; } /// /// 资产账户编号 /// public string FUNDID_ { get; set; } /// /// 资产单元编号 /// public string CELLID_ { get; set; } /// /// 投资组合代号 /// public string PROFLID_ { get; set; } /// /// 席位号 /// public string SEATID_ { get; set; } /// /// 交易编码 /// public string HOLDERACC_ { get; set; } /// /// 市场代码 /// public string MKCODE_ { get; set; } /// /// 品种合约 /// public string STKCODE_ { get; set; } /// /// 品种名称 /// public string STKNAME_ { get; set; } /// /// 证券类型 [601-焦煤商品合约,626-沥青商品合约,631-动力煤商品合约,636-铁矿石商品合约,643-鸡蛋商品合约,309-新基金认购,652-动力煤商品合约,700-个股期权,701-豆粕期权,702-白糖期权,703-铜期权,704-沪深300股指期权,430-质押码,639-聚丙烯商品合约,640-胶合板商品合约] /// public string STKTP_ { get; set; } /// /// 投机套保标志 [投保标记-!,投机-0 ,保值(备兑)-1,套利-2] /// public string HEDGINGFLAG_ { get; set; } /// /// 业务类型[1-期货买卖,4201-基金收益,4021-红股登记,4175-净值归一拆分子基金,4176-净值归一合并母基金,4177-净值归一合并子基金,2-资金划入,3-资金划出,4-证券划入,5-证券划出,6- 手动资金冻结,4002-证券买入,2054-支票调出,2052-现金调出,4 036-ETF申购] /// public string YWLX_ { get; set; } /// /// 开平类型[!-开平标记,0-开仓,1-平仓,2-平今,3-空] /// public string KPTP_ { get; set; } /// /// 买卖类型[!-买卖标记,0-买,1-卖,6-转托管] /// public string BSTP_ { get; set; } /// /// 成交量 /// public string FSSL_ { get; set; } /// /// 成交价 /// public string FSJG_ { get; set; } /// /// 成交金额 /// public string FSJE_ { get; set; } /// /// 成交时间 /// public string CJTIME_ { get; set; } /// /// 手续费 /// public string FEE_ { get; set; } /// /// 手续费1 /// public string FEE1_ { get; set; } /// /// 手续费2 /// public string FEE2_ { get; set; } /// /// 成交流水号 /// public string DONENO_ { get; set; } ///// ///// 平仓盈亏(逐日盯市) ///// //public string SDPROFIT_ { get; set; } /// /// 平仓盈亏(逐笔对冲) /// public string SSPROFIT_ { get; set; } ///// ///// 交割配对盈亏 ///// //public string JGPDYK_ { get; set; } ///// ///// 开仓日期 ///// //public string KC_DATE_ { get; set; } ///// ///// 开仓流水号 ///// //public string KC_SERIALID_ { get; set; } ///// ///// 开仓成交流水号 ///// //public string KC_DONENO_ { get; set; } ///// ///// 开仓价 ///// //public string KC_PRICE_ { get; set; } ///// ///// 昨日结算 ///// //public string ZRJS_ { get; set; } ///// ///// 今日结算 ///// //public string JRJS_ { get; set; } ///// ///// 组合证券代码 ///// //public string VARICODE_ { get; set; } ///// ///// 权利金 ///// //public string QLJ_ { get; set; } ///// ///// 开仓权利金 ///// //public string KCQLJ_ { get; set; } public string SaveResult { get; set; } } }