LCOV - code coverage report
Current view: top level - src/client - client_app.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 21.5 % 363 78
Test Date: 2025-12-19 03:13:09 Functions: 32.0 % 25 8

            Line data    Source code
       1              : /**
       2              :  * @file client_app.cpp
       3              :  * @brief 客户端 FIX Application 实现
       4              :  */
       5              : 
       6              : #include "client_app.hpp"
       7              : #include "fix/fix_tags.hpp"
       8              : #include "base/logger.hpp"
       9              : #include <sstream>
      10              : #include <iomanip>
      11              : #include <chrono>
      12              : 
      13              : namespace fix40::client {
      14              : 
      15            2 : ClientApp::ClientApp(std::shared_ptr<ClientState> state, const std::string& userId)
      16            2 :     : state_(std::move(state))
      17            4 :     , userId_(userId) {
      18            2 :     clOrdIdPrefixMs_ = std::chrono::duration_cast<std::chrono::milliseconds>(
      19            4 :         std::chrono::system_clock::now().time_since_epoch()).count();
      20            2 : }
      21              : 
      22              : // ============================================================================
      23              : // Application 接口实现
      24              : // ============================================================================
      25              : 
      26            0 : void ClientApp::onLogon(const SessionID& sessionID) {
      27            0 :     LOG() << "[ClientApp] Logged on: " << sessionID.to_string();
      28            0 :     state_->setConnectionState(ConnectionState::LOGGED_IN);
      29            0 :     state_->setUserId(userId_);
      30            0 :     state_->addMessage("登录成功");
      31              :     
      32              :     // 登录后自动查询资金和持仓
      33            0 :     queryBalance();
      34            0 :     queryPositions();
      35            0 :     queryOrderHistory();
      36            0 : }
      37              : 
      38            0 : void ClientApp::onLogout(const SessionID& sessionID) {
      39            0 :     LOG() << "[ClientApp] Logged out: " << sessionID.to_string();
      40            0 :     state_->setConnectionState(ConnectionState::DISCONNECTED);
      41            0 :     state_->addMessage("已登出");
      42            0 : }
      43              : 
      44            1 : void ClientApp::fromApp(const FixMessage& msg, const SessionID& sessionID) {
      45            1 :     std::string msgType = msg.get_string(tags::MsgType);
      46            1 :     LOG() << "[ClientApp] Received MsgType=" << msgType << " from " << sessionID.to_string();
      47              :     
      48            1 :     if (msgType == "8") {
      49            0 :         handleExecutionReport(msg);
      50            1 :     } else if (msgType == "U2") {
      51            0 :         handleBalanceResponse(msg);
      52            1 :     } else if (msgType == "U4") {
      53            0 :         handlePositionResponse(msg);
      54            1 :     } else if (msgType == "U5") {
      55            1 :         handleAccountUpdate(msg);
      56            0 :     } else if (msgType == "U6") {
      57            0 :         handlePositionUpdate(msg);
      58            0 :     } else if (msgType == "U8") {
      59            0 :         handleInstrumentSearchResponse(msg);
      60            0 :     } else if (msgType == "U10") {
      61            0 :         handleOrderHistoryResponse(msg);
      62            0 :     } else if (msgType == "j") {
      63              :         // BusinessMessageReject
      64            0 :         std::string text = msg.has(tags::Text) ? msg.get_string(tags::Text) : "Unknown error";
      65            0 :         state_->setLastError(text);
      66            0 :         state_->addMessage("业务拒绝: " + text);
      67            0 :     } else {
      68            0 :         LOG() << "[ClientApp] Unknown message type: " << msgType;
      69              :     }
      70            1 : }
      71              : 
      72            2 : void ClientApp::toApp(FixMessage& msg, const SessionID& sessionID) {
      73            2 :     std::string msgType = msg.get_string(tags::MsgType);
      74            2 :     LOG() << "[ClientApp] Sending MsgType=" << msgType << " via " << sessionID.to_string();
      75            2 : }
      76              : 
      77            1 : void ClientApp::setSession(std::shared_ptr<Session> session) {
      78            1 :     session_ = session;
      79            1 : }
      80              : 
      81              : // ============================================================================
      82              : // 业务操作
      83              : // ============================================================================
      84              : 
      85            2 : std::string ClientApp::sendNewOrder(const std::string& symbol, const std::string& side,
      86              :                                      int64_t qty, double price, const std::string& ordType) {
      87            2 :     auto session = session_.lock();
      88            2 :     if (!session) {
      89            0 :         state_->setLastError("未连接");
      90            0 :         return "";
      91              :     }
      92              :     
      93            2 :     std::string clOrdID = generateClOrdID();
      94              :     
      95            2 :     FixMessage msg;
      96            2 :     msg.set(tags::MsgType, "D");
      97            2 :     msg.set(tags::ClOrdID, clOrdID);
      98            2 :     msg.set(tags::HandlInst, "1");  // Automated execution
      99            2 :     msg.set(tags::Symbol, symbol);
     100            2 :     msg.set(tags::Side, side);
     101            2 :     msg.set(tags::OrderQty, static_cast<int>(qty));
     102            2 :     msg.set(tags::OrdType, ordType);
     103              :     
     104            2 :     if (ordType == "2") {  // Limit order
     105            2 :         std::ostringstream oss;
     106            2 :         oss << std::fixed << std::setprecision(2) << price;
     107            2 :         msg.set(tags::Price, oss.str());
     108            2 :     }
     109              :     
     110            2 :     msg.set(tags::TimeInForce, "0");  // Day
     111              :     
     112              :     // TransactTime
     113            2 :     auto now = std::chrono::system_clock::now();
     114            2 :     auto time = std::chrono::system_clock::to_time_t(now);
     115            2 :     std::ostringstream timeOss;
     116            2 :     timeOss << std::put_time(std::gmtime(&time), "%Y%m%d-%H:%M:%S");
     117            2 :     msg.set(tags::TransactTime, timeOss.str());
     118              :     
     119            2 :     session->send_app_message(msg);
     120              :     
     121              :     // 添加到本地订单列表
     122            2 :     OrderInfo order;
     123            2 :     order.clOrdID = clOrdID;
     124            2 :     order.symbol = symbol;
     125            2 :     order.side = (side == "1") ? "BUY" : "SELL";
     126            2 :     order.price = price;
     127            2 :     order.orderQty = qty;
     128            2 :     order.state = OrderState::PENDING_NEW;
     129            2 :     state_->addOrder(order);
     130              :     
     131            2 :     state_->addMessage("下单: " + symbol + " " + order.side + " " + std::to_string(qty) + "@" + std::to_string(price));
     132              :     
     133            2 :     return clOrdID;
     134            2 : }
     135              : 
     136            0 : void ClientApp::sendCancelOrder(const std::string& origClOrdID,
     137              :                                  const std::string& symbol, const std::string& side) {
     138            0 :     auto session = session_.lock();
     139            0 :     if (!session) {
     140            0 :         state_->setLastError("未连接");
     141            0 :         return;
     142              :     }
     143              :     
     144            0 :     std::string clOrdID = generateClOrdID();
     145              :     
     146            0 :     FixMessage msg;
     147            0 :     msg.set(tags::MsgType, "F");
     148            0 :     msg.set(tags::ClOrdID, clOrdID);
     149            0 :     msg.set(tags::OrigClOrdID, origClOrdID);
     150            0 :     msg.set(tags::Symbol, symbol);
     151            0 :     msg.set(tags::Side, side);
     152            0 :     msg.set(tags::CxlType, "F");  // Full cancel
     153              :     
     154            0 :     auto now = std::chrono::system_clock::now();
     155            0 :     auto time = std::chrono::system_clock::to_time_t(now);
     156            0 :     std::ostringstream timeOss;
     157            0 :     timeOss << std::put_time(std::gmtime(&time), "%Y%m%d-%H:%M:%S");
     158            0 :     msg.set(tags::TransactTime, timeOss.str());
     159              :     
     160            0 :     session->send_app_message(msg);
     161            0 :     state_->addMessage("撤单: " + origClOrdID);
     162            0 : }
     163              : 
     164            0 : void ClientApp::queryBalance() {
     165            0 :     auto session = session_.lock();
     166            0 :     if (!session) return;
     167              :     
     168            0 :     FixMessage msg;
     169            0 :     msg.set(tags::MsgType, "U1");
     170            0 :     msg.set(tags::RequestID, std::to_string(requestIdCounter_++));
     171              :     
     172            0 :     session->send_app_message(msg);
     173            0 : }
     174              : 
     175            0 : void ClientApp::queryPositions() {
     176            0 :     auto session = session_.lock();
     177            0 :     if (!session) return;
     178              :     
     179            0 :     FixMessage msg;
     180            0 :     msg.set(tags::MsgType, "U3");
     181            0 :     msg.set(tags::RequestID, std::to_string(requestIdCounter_++));
     182              :     
     183            0 :     session->send_app_message(msg);
     184            0 : }
     185              : 
     186            0 : void ClientApp::queryOrderHistory() {
     187            0 :     auto session = session_.lock();
     188            0 :     if (!session) return;
     189              : 
     190            0 :     FixMessage msg;
     191            0 :     msg.set(tags::MsgType, "U9");
     192            0 :     msg.set(tags::RequestID, std::to_string(requestIdCounter_++));
     193            0 :     session->send_app_message(msg);
     194            0 : }
     195              : 
     196            0 : void ClientApp::searchInstruments(const std::string& pattern, int maxResults) {
     197            0 :     auto session = session_.lock();
     198            0 :     if (!session) return;
     199              :     
     200            0 :     FixMessage msg;
     201            0 :     msg.set(tags::MsgType, "U7");
     202            0 :     msg.set(tags::RequestID, std::to_string(requestIdCounter_++));
     203            0 :     msg.set(tags::SearchPattern, pattern);
     204            0 :     msg.set(tags::MaxResults, maxResults);
     205              :     
     206            0 :     session->send_app_message(msg);
     207            0 : }
     208              : 
     209              : // ============================================================================
     210              : // 消息处理
     211              : // ============================================================================
     212              : 
     213            0 : void ClientApp::handleExecutionReport(const FixMessage& msg) {
     214            0 :     OrderInfo order;
     215            0 :     order.clOrdID = msg.get_string(tags::ClOrdID);
     216              :     
     217            0 :     if (msg.has(tags::OrderID)) {
     218            0 :         order.orderId = msg.get_string(tags::OrderID);
     219              :     }
     220            0 :     if (msg.has(tags::Symbol)) {
     221            0 :         order.symbol = msg.get_string(tags::Symbol);
     222              :     }
     223            0 :     if (msg.has(tags::Side)) {
     224            0 :         order.side = (msg.get_string(tags::Side) == "1") ? "BUY" : "SELL";
     225              :     }
     226            0 :     if (msg.has(tags::Price)) {
     227              :         try {
     228            0 :             order.price = std::stod(msg.get_string(tags::Price));
     229            0 :         } catch (...) { order.price = 0.0; }
     230              :     }
     231            0 :     if (msg.has(tags::OrderQty)) {
     232              :         try {
     233            0 :             order.orderQty = std::stoll(msg.get_string(tags::OrderQty));
     234            0 :         } catch (...) { order.orderQty = 0; }
     235              :     }
     236            0 :     if (msg.has(tags::CumQty)) {
     237              :         try {
     238            0 :             order.filledQty = std::stoll(msg.get_string(tags::CumQty));
     239            0 :         } catch (...) { order.filledQty = 0; }
     240              :     }
     241            0 :     if (msg.has(tags::AvgPx)) {
     242              :         try {
     243            0 :             order.avgPx = std::stod(msg.get_string(tags::AvgPx));
     244            0 :         } catch (...) { order.avgPx = 0.0; }
     245              :     }
     246            0 :     if (msg.has(tags::Text)) {
     247            0 :         order.text = msg.get_string(tags::Text);
     248              :     }
     249              :     
     250              :     // 解析订单状态
     251            0 :     if (msg.has(tags::OrdStatus)) {
     252            0 :         std::string status = msg.get_string(tags::OrdStatus);
     253            0 :         if (status == "0") order.state = OrderState::NEW;
     254            0 :         else if (status == "1") order.state = OrderState::PARTIALLY_FILLED;
     255            0 :         else if (status == "2") order.state = OrderState::FILLED;
     256            0 :         else if (status == "4") order.state = OrderState::CANCELED;
     257            0 :         else if (status == "8") order.state = OrderState::REJECTED;
     258            0 :     }
     259              :     
     260            0 :     state_->updateOrder(order.clOrdID, order);
     261              :     
     262              :     // 生成消息
     263            0 :     std::string stateStr;
     264            0 :     switch (order.state) {
     265            0 :         case OrderState::NEW: stateStr = "已确认"; break;
     266            0 :         case OrderState::PARTIALLY_FILLED: stateStr = "部分成交"; break;
     267            0 :         case OrderState::FILLED: stateStr = "全部成交"; break;
     268            0 :         case OrderState::CANCELED: stateStr = "已撤销"; break;
     269            0 :         case OrderState::REJECTED: stateStr = "已拒绝"; break;
     270            0 :         default: stateStr = "未知"; break;
     271              :     }
     272              :     
     273              :     // 生成消息(拒绝时显示原因)
     274            0 :     if (order.state == OrderState::REJECTED && !order.text.empty()) {
     275            0 :         state_->addMessage("订单 " + order.clOrdID + " " + stateStr + ": " + order.text);
     276            0 :         state_->setLastError(order.text);
     277              :     } else {
     278            0 :         state_->addMessage("订单 " + order.clOrdID + " " + stateStr);
     279              :     }
     280              :     
     281              :     // 成交后刷新资金和持仓
     282            0 :     if (order.state == OrderState::FILLED || order.state == OrderState::PARTIALLY_FILLED) {
     283            0 :         queryBalance();
     284            0 :         queryPositions();
     285              :     }
     286            0 : }
     287              : 
     288            0 : void ClientApp::handleBalanceResponse(const FixMessage& msg) {
     289            0 :     AccountInfo info;
     290              :     
     291            0 :     auto safeStod = [](const std::string& s) -> double {
     292            0 :         try { return std::stod(s); } catch (...) { return 0.0; }
     293              :     };
     294              :     
     295            0 :     if (msg.has(tags::Balance)) {
     296            0 :         info.balance = safeStod(msg.get_string(tags::Balance));
     297              :     }
     298            0 :     if (msg.has(tags::Available)) {
     299            0 :         info.available = safeStod(msg.get_string(tags::Available));
     300              :     }
     301            0 :     if (msg.has(tags::FrozenMargin)) {
     302            0 :         info.frozenMargin = safeStod(msg.get_string(tags::FrozenMargin));
     303              :     }
     304            0 :     if (msg.has(tags::UsedMargin)) {
     305            0 :         info.usedMargin = safeStod(msg.get_string(tags::UsedMargin));
     306              :     }
     307            0 :     if (msg.has(tags::PositionProfit)) {
     308            0 :         info.positionProfit = safeStod(msg.get_string(tags::PositionProfit));
     309              :     }
     310            0 :     if (msg.has(tags::CloseProfit)) {
     311            0 :         info.closeProfit = safeStod(msg.get_string(tags::CloseProfit));
     312              :     }
     313            0 :     if (msg.has(tags::DynamicEquity)) {
     314            0 :         info.dynamicEquity = safeStod(msg.get_string(tags::DynamicEquity));
     315              :     }
     316            0 :     if (msg.has(tags::RiskRatio)) {
     317            0 :         info.riskRatio = safeStod(msg.get_string(tags::RiskRatio));
     318              :     }
     319              :     
     320            0 :     state_->updateAccount(info);
     321            0 : }
     322              : 
     323            0 : void ClientApp::handlePositionResponse(const FixMessage& msg) {
     324              :     // 简化处理:从 Text 字段解析持仓
     325              :     // 格式: "IF2601:L10@4000.00,S5@4100.00;IC2601:L20@5000.00,S0@0.00;"
     326            0 :     if (!msg.has(tags::Text)) {
     327            0 :         state_->clearPositions();
     328            0 :         return;
     329              :     }
     330              :     
     331            0 :     std::string text = msg.get_string(tags::Text);
     332            0 :     std::vector<PositionInfo> positions;
     333              :     
     334            0 :     std::istringstream iss(text);
     335            0 :     std::string item;
     336            0 :     while (std::getline(iss, item, ';')) {
     337            0 :         if (item.empty()) continue;
     338              :         
     339            0 :         auto colonPos = item.find(':');
     340            0 :         if (colonPos == std::string::npos) continue;
     341              :         
     342            0 :         PositionInfo pos;
     343            0 :         pos.instrumentId = item.substr(0, colonPos);
     344              :         
     345            0 :         std::string rest = item.substr(colonPos + 1);
     346              :         // 解析 L10@4000.00,S5@4100.00
     347            0 :         std::istringstream restIss(rest);
     348            0 :         std::string part;
     349            0 :         while (std::getline(restIss, part, ',')) {
     350            0 :             if (part.empty()) continue;
     351            0 :             char dir = part[0];
     352            0 :             auto atPos = part.find('@');
     353            0 :             if (atPos == std::string::npos) continue;
     354              :             
     355              :             try {
     356            0 :                 int64_t qty = std::stoll(part.substr(1, atPos - 1));
     357            0 :                 double price = std::stod(part.substr(atPos + 1));
     358              :                 
     359            0 :                 if (dir == 'L') {
     360            0 :                     pos.longPosition = qty;
     361            0 :                     pos.longAvgPrice = price;
     362            0 :                 } else if (dir == 'S') {
     363            0 :                     pos.shortPosition = qty;
     364            0 :                     pos.shortAvgPrice = price;
     365              :                 }
     366            0 :             } catch (...) {
     367              :                 // 解析失败,跳过该字段
     368            0 :             }
     369              :         }
     370              :         
     371            0 :         if (pos.longPosition > 0 || pos.shortPosition > 0) {
     372            0 :             positions.push_back(pos);
     373              :         }
     374            0 :     }
     375              :     
     376            0 :     state_->setPositions(positions);
     377            0 : }
     378              : 
     379            1 : void ClientApp::handleAccountUpdate(const FixMessage& msg) {
     380              :     // 与 handleBalanceResponse 基本相同,但不触发额外的查询。
     381              :     // 注意:U5 作为推送消息,可能只包含部分字段;因此这里以当前状态为基准做“合并更新”,
     382              :     // 避免缺失字段被默认值(例如 closeProfit=0)覆盖。
     383            1 :     AccountInfo info = state_->getAccount();
     384              :     
     385            1 :     auto safeStod = [](const std::string& s) -> double {
     386            1 :         try { return std::stod(s); } catch (...) { return 0.0; }
     387              :     };
     388              :     
     389            1 :     if (msg.has(tags::Balance)) {
     390            1 :         info.balance = safeStod(msg.get_string(tags::Balance));
     391              :     }
     392            1 :     if (msg.has(tags::Available)) {
     393            0 :         info.available = safeStod(msg.get_string(tags::Available));
     394              :     }
     395            1 :     if (msg.has(tags::FrozenMargin)) {
     396            0 :         info.frozenMargin = safeStod(msg.get_string(tags::FrozenMargin));
     397              :     }
     398            1 :     if (msg.has(tags::UsedMargin)) {
     399            0 :         info.usedMargin = safeStod(msg.get_string(tags::UsedMargin));
     400              :     }
     401            1 :     if (msg.has(tags::PositionProfit)) {
     402            0 :         info.positionProfit = safeStod(msg.get_string(tags::PositionProfit));
     403              :     }
     404            1 :     if (msg.has(tags::CloseProfit)) {
     405            0 :         info.closeProfit = safeStod(msg.get_string(tags::CloseProfit));
     406              :     }
     407            1 :     if (msg.has(tags::DynamicEquity)) {
     408            0 :         info.dynamicEquity = safeStod(msg.get_string(tags::DynamicEquity));
     409              :     }
     410            1 :     if (msg.has(tags::RiskRatio)) {
     411            0 :         info.riskRatio = safeStod(msg.get_string(tags::RiskRatio));
     412              :     }
     413              :     
     414            1 :     state_->updateAccount(info);
     415            1 : }
     416              : 
     417            0 : void ClientApp::handlePositionUpdate(const FixMessage& msg) {
     418            0 :     PositionInfo pos;
     419              :     
     420            0 :     auto safeStod = [](const std::string& s) -> double {
     421            0 :         try { return std::stod(s); } catch (...) { return 0.0; }
     422              :     };
     423              :     
     424            0 :     if (msg.has(tags::InstrumentID)) {
     425            0 :         pos.instrumentId = msg.get_string(tags::InstrumentID);
     426              :     }
     427            0 :     pos.quantitiesValid = msg.has(tags::LongPosition) || msg.has(tags::ShortPosition);
     428            0 :     if (msg.has(tags::LongPosition)) {
     429            0 :         pos.longPosition = msg.get_int(tags::LongPosition);
     430              :     }
     431            0 :     if (msg.has(tags::LongAvgPrice)) {
     432            0 :         pos.longAvgPrice = safeStod(msg.get_string(tags::LongAvgPrice));
     433              :     }
     434            0 :     if (msg.has(tags::ShortPosition)) {
     435            0 :         pos.shortPosition = msg.get_int(tags::ShortPosition);
     436              :     }
     437            0 :     if (msg.has(tags::ShortAvgPrice)) {
     438            0 :         pos.shortAvgPrice = safeStod(msg.get_string(tags::ShortAvgPrice));
     439              :     }
     440            0 :     if (msg.has(tags::PositionProfit)) {
     441            0 :         pos.profit = safeStod(msg.get_string(tags::PositionProfit));
     442              :     }
     443              :     
     444            0 :     state_->updatePosition(pos);
     445            0 : }
     446              : 
     447            0 : void ClientApp::handleInstrumentSearchResponse(const FixMessage& msg) {
     448            0 :     std::vector<std::string> results;
     449              :     
     450            0 :     if (msg.has(tags::InstrumentList)) {
     451            0 :         std::string list = msg.get_string(tags::InstrumentList);
     452            0 :         std::istringstream iss(list);
     453            0 :         std::string item;
     454            0 :         while (std::getline(iss, item, ',')) {
     455            0 :             if (!item.empty()) {
     456            0 :                 results.push_back(item);
     457              :             }
     458              :         }
     459            0 :     }
     460              :     
     461            0 :     state_->setSearchResults(results);
     462            0 : }
     463              : 
     464            0 : void ClientApp::handleOrderHistoryResponse(const FixMessage& msg) {
     465              :     // Text 字段包含序列化订单列表(与 ClientState::saveOrders 的格式对齐):
     466              :     // clOrdID|orderId|symbol|side|price|orderQty|filledQty|avgPx|state|text|updateTime
     467            0 :     if (!msg.has(tags::Text)) {
     468            0 :         state_->clearOrders();
     469            0 :         state_->setLastError("订单历史响应格式无效:缺少 Text 字段");
     470            0 :         state_->addMessage("订单历史响应格式无效");
     471            0 :         return;
     472              :     }
     473              : 
     474            0 :     std::string text = msg.get_string(tags::Text);
     475            0 :     if (text.empty()) {
     476            0 :         state_->clearOrders();
     477            0 :         state_->addMessage("订单历史为空");
     478            0 :         return;
     479              :     }
     480              : 
     481            0 :     std::vector<OrderInfo> orders;
     482            0 :     std::istringstream iss(text);
     483            0 :     std::string line;
     484              : 
     485            0 :     while (std::getline(iss, line)) {
     486            0 :         if (line.empty()) continue;
     487              : 
     488            0 :         std::vector<std::string> fields;
     489            0 :         std::istringstream lineIss(line);
     490            0 :         std::string field;
     491            0 :         while (std::getline(lineIss, field, '|')) {
     492            0 :             fields.push_back(field);
     493              :         }
     494              : 
     495              :         // 协议/格式兼容策略:
     496              :         // - 至少需要到 state 字段(第 9 个字段)才能构造 OrderInfo;
     497              :         // - 如未来服务端增加字段,客户端忽略多余字段以保持兼容性。
     498            0 :         if (fields.size() < 9) {
     499            0 :             continue;
     500              :         }
     501              : 
     502            0 :         OrderInfo order;
     503            0 :         order.clOrdID = fields[0];
     504            0 :         order.orderId = fields[1];
     505            0 :         order.symbol = fields[2];
     506            0 :         order.side = fields[3];
     507              : 
     508            0 :         auto safeStod = [](const std::string& s) -> double {
     509            0 :             try { return std::stod(s); } catch (...) { return 0.0; }
     510              :         };
     511            0 :         auto safeStoll = [](const std::string& s) -> int64_t {
     512            0 :             try { return std::stoll(s); } catch (...) { return 0; }
     513              :         };
     514              : 
     515            0 :         order.price = safeStod(fields[4]);
     516            0 :         order.orderQty = safeStoll(fields[5]);
     517            0 :         order.filledQty = safeStoll(fields[6]);
     518            0 :         order.avgPx = safeStod(fields[7]);
     519              : 
     520            0 :         int stateVal = 0;
     521              :         try {
     522            0 :             stateVal = std::stoi(fields[8]);
     523            0 :         } catch (...) {
     524            0 :             stateVal = 0;
     525            0 :         }
     526            0 :         if (stateVal < 0 || stateVal > static_cast<int>(OrderState::REJECTED)) {
     527            0 :             stateVal = 0;
     528              :         }
     529            0 :         order.state = static_cast<OrderState>(stateVal);
     530              : 
     531            0 :         if (fields.size() >= 10) {
     532            0 :             order.text = fields[9];
     533              :         }
     534            0 :         if (fields.size() >= 11) {
     535            0 :             order.updateTime = fields[10];
     536              :         }
     537              : 
     538            0 :         if (!order.clOrdID.empty()) {
     539            0 :             orders.push_back(std::move(order));
     540              :         }
     541            0 :     }
     542              : 
     543            0 :     state_->setOrders(orders);
     544            0 :     state_->addMessage("订单历史已刷新 (" + std::to_string(orders.size()) + ")");
     545            0 : }
     546              : 
     547            2 : std::string ClientApp::generateClOrdID() {
     548            2 :     std::ostringstream oss;
     549              :     // ClOrdID 必须唯一:如果仅用自增计数器,客户端重启后计数器会从 1 重新开始,
     550              :     // 会导致服务端/客户端用相同 ClOrdID 覆盖历史订单(orders_ map 也会覆盖)。
     551              :     // 这里加入“本次运行启动时间(ms)”前缀,并使用 fetch_add 确保在 C++17 下行为明确。
     552            2 :     const uint64_t seq = orderIdCounter_.fetch_add(1, std::memory_order_relaxed);
     553            2 :     oss << userId_ << "-" << clOrdIdPrefixMs_ << "-" << std::setfill('0') << std::setw(6) << seq;
     554            4 :     return oss.str();
     555            2 : }
     556              : 
     557              : } // namespace fix40::client
        

Generated by: LCOV version 2.0-1