Line data Source code
1 : /**
2 : * @file simulation_app.cpp
3 : * @brief 模拟交易应用层实现
4 : *
5 : * 集成AccountManager、PositionManager、InstrumentManager、RiskManager,
6 : * 实现完整的模拟交易流程:
7 : * 1. 接收FIX订单消息
8 : * 2. 风控检查
9 : * 3. 提交撮合引擎
10 : * 4. 处理成交回报,更新账户和持仓
11 : */
12 :
13 : #include "app/simulation_app.hpp"
14 : #include "fix/fix_message_builder.hpp"
15 : #include "fix/fix_tags.hpp"
16 : #include "base/logger.hpp"
17 : #include "storage/store.hpp"
18 : #include <cstdlib>
19 : #include <sstream>
20 : #include <iomanip>
21 : #include <set>
22 : #include <optional>
23 : #include <algorithm>
24 :
25 : namespace fix40 {
26 :
27 : // ============================================================================
28 : // FIX 消息解析辅助函数
29 : // ============================================================================
30 :
31 : namespace {
32 :
33 : /**
34 : * @brief 将系统时间转换为 epoch 毫秒时间戳
35 : */
36 1 : int64_t to_epoch_millis(std::chrono::system_clock::time_point tp) {
37 1 : return std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch()).count();
38 : }
39 :
40 : /**
41 : * @brief 解析结果
42 : */
43 : struct ParseResult {
44 : bool success = false;
45 : std::string error;
46 : Order order;
47 : };
48 :
49 : /**
50 : * @brief 从 FIX 消息解析 Order 结构
51 : * @return ParseResult 包含解析结果和错误信息
52 : */
53 5 : ParseResult parseNewOrderSingle(const FixMessage& msg, const SessionID& sessionID) {
54 5 : ParseResult result;
55 5 : Order& order = result.order;
56 5 : order.sessionID = sessionID;
57 :
58 : // 必填字段:ClOrdID
59 5 : if (!msg.has(tags::ClOrdID)) {
60 0 : result.error = "Missing required field: ClOrdID(11)";
61 0 : return result;
62 : }
63 5 : order.clOrdID = msg.get_string(tags::ClOrdID);
64 :
65 : // 必填字段:Symbol
66 5 : if (!msg.has(tags::Symbol)) {
67 0 : result.error = "Missing required field: Symbol(55)";
68 0 : return result;
69 : }
70 5 : order.symbol = msg.get_string(tags::Symbol);
71 :
72 : // 必填字段:Side (1=Buy, 2=Sell)
73 5 : if (!msg.has(tags::Side)) {
74 0 : result.error = "Missing required field: Side(54)";
75 0 : return result;
76 : }
77 5 : std::string sideStr = msg.get_string(tags::Side);
78 5 : if (sideStr == "1") {
79 5 : order.side = OrderSide::BUY;
80 0 : } else if (sideStr == "2") {
81 0 : order.side = OrderSide::SELL;
82 : } else {
83 0 : result.error = "Invalid Side(54) value: " + sideStr + " (expected 1 or 2)";
84 0 : return result;
85 : }
86 :
87 : // 必填字段:OrderQty
88 5 : if (!msg.has(tags::OrderQty)) {
89 0 : result.error = "Missing required field: OrderQty(38)";
90 0 : return result;
91 : }
92 : try {
93 5 : order.orderQty = std::stoll(msg.get_string(tags::OrderQty));
94 5 : if (order.orderQty <= 0) {
95 0 : result.error = "Invalid OrderQty(38): must be positive";
96 0 : return result;
97 : }
98 0 : } catch (const std::exception& e) {
99 0 : result.error = "Invalid OrderQty(38) format: " + std::string(e.what());
100 0 : return result;
101 0 : }
102 :
103 : // 必填字段:OrdType (1=Market, 2=Limit)
104 5 : if (!msg.has(tags::OrdType)) {
105 0 : result.error = "Missing required field: OrdType(40)";
106 0 : return result;
107 : }
108 5 : std::string ordTypeStr = msg.get_string(tags::OrdType);
109 5 : if (ordTypeStr == "1") {
110 0 : order.ordType = OrderType::MARKET;
111 5 : } else if (ordTypeStr == "2") {
112 5 : order.ordType = OrderType::LIMIT;
113 : } else {
114 0 : LOG() << "[SimulationApp] Warning: Unknown OrdType(40)=" << ordTypeStr << ", defaulting to LIMIT";
115 0 : order.ordType = OrderType::LIMIT;
116 : }
117 :
118 : // Price (限价单必填)
119 5 : if (msg.has(tags::Price)) {
120 : try {
121 5 : order.price = std::stod(msg.get_string(tags::Price));
122 0 : } catch (const std::exception& e) {
123 0 : result.error = "Invalid Price(44) format: " + std::string(e.what());
124 0 : return result;
125 0 : }
126 0 : } else if (order.ordType == OrderType::LIMIT) {
127 0 : result.error = "Missing required field: Price(44) for limit order";
128 0 : return result;
129 : }
130 :
131 : // TimeInForce: 0=Day, 1=GTC, 3=IOC, 4=FOK (可选,默认 DAY)
132 5 : if (msg.has(tags::TimeInForce)) {
133 0 : std::string tifStr = msg.get_string(tags::TimeInForce);
134 0 : if (tifStr == "0") order.timeInForce = TimeInForce::DAY;
135 0 : else if (tifStr == "1") order.timeInForce = TimeInForce::GTC;
136 0 : else if (tifStr == "3") order.timeInForce = TimeInForce::IOC;
137 0 : else if (tifStr == "4") order.timeInForce = TimeInForce::FOK;
138 : else {
139 0 : LOG() << "[SimulationApp] Warning: Unknown TimeInForce(59)=" << tifStr << ", defaulting to DAY";
140 0 : order.timeInForce = TimeInForce::DAY;
141 : }
142 0 : }
143 :
144 : // 初始化执行状态
145 5 : order.status = OrderStatus::PENDING_NEW;
146 5 : order.cumQty = 0;
147 5 : order.leavesQty = order.orderQty;
148 5 : order.avgPx = 0.0;
149 5 : order.createTime = std::chrono::system_clock::now();
150 5 : order.updateTime = order.createTime;
151 :
152 5 : result.success = true;
153 5 : return result;
154 5 : }
155 :
156 : /**
157 : * @brief 从 FIX 消息解析 CancelRequest 结构
158 : */
159 1 : CancelRequest parseCancelRequest(const FixMessage& msg, const SessionID& sessionID) {
160 1 : CancelRequest req;
161 1 : req.sessionID = sessionID;
162 :
163 1 : req.clOrdID = msg.get_string(tags::ClOrdID);
164 1 : req.origClOrdID = msg.get_string(tags::OrigClOrdID);
165 :
166 1 : if (msg.has(tags::Symbol)) {
167 1 : req.symbol = msg.get_string(tags::Symbol);
168 : }
169 :
170 1 : return req;
171 0 : }
172 :
173 : } // anonymous namespace
174 :
175 : // ============================================================================
176 : // SimulationApp 实现
177 : // ============================================================================
178 :
179 16 : SimulationApp::SimulationApp()
180 16 : : store_(nullptr) {
181 16 : initializeManagers();
182 16 : }
183 :
184 4 : SimulationApp::SimulationApp(IStore* store)
185 4 : : accountManager_(store)
186 4 : , positionManager_(store)
187 8 : , store_(store) {
188 4 : initializeManagers();
189 4 : }
190 :
191 20 : void SimulationApp::initializeManagers() {
192 : // 撮合引擎只负责撮合与订单状态推进;账户/持仓/风控等业务权威逻辑由 SimulationApp 负责。
193 : // 撮合引擎仅需要合约信息用于行情驱动撮合相关的辅助更新(如涨跌停价格)。
194 20 : engine_.setInstrumentManager(&instrumentManager_);
195 :
196 : // 设置 ExecutionReport 回调
197 20 : engine_.setExecutionReportCallback(
198 20 : [this](const SessionID& sid, const ExecutionReport& rpt) {
199 2 : onExecutionReport(sid, rpt);
200 2 : });
201 :
202 : // 设置行情更新回调(用于账户价值重算和推送)
203 20 : engine_.setMarketDataUpdateCallback(
204 20 : [this](const std::string& instrumentId, double lastPrice) {
205 2 : onMarketDataUpdate(instrumentId, lastPrice);
206 2 : });
207 20 : }
208 :
209 20 : SimulationApp::~SimulationApp() {
210 20 : stop();
211 20 : }
212 :
213 10 : void SimulationApp::start() {
214 10 : engine_.start();
215 10 : }
216 :
217 30 : void SimulationApp::stop() {
218 30 : engine_.stop();
219 30 : }
220 :
221 1 : void SimulationApp::onLogon(const SessionID& sessionID) {
222 : // 身份绑定:使用 SenderCompID 作为用户ID
223 1 : std::string userId = extractAccountId(sessionID);
224 :
225 : // 安全检查:拒绝无效的用户ID
226 1 : if (userId.empty()) {
227 1 : LOG() << "[SimulationApp] Rejecting logon: invalid user ID for session "
228 1 : << sessionID.to_string();
229 1 : return;
230 : }
231 :
232 0 : LOG() << "[SimulationApp] Session logged on: " << sessionID.to_string()
233 0 : << " -> User: " << userId;
234 :
235 : // 自动开户:如果该用户不存在,初始化一个带初始资金的账户
236 0 : if (!accountManager_.hasAccount(userId)) {
237 0 : LOG() << "[SimulationApp] Initializing new account for user: " << userId;
238 0 : accountManager_.createAccount(userId, 1000000.0); // 默认 100 万
239 : }
240 :
241 0 : engine_.submit(OrderEvent{OrderEventType::SESSION_LOGON, sessionID});
242 1 : }
243 :
244 1 : void SimulationApp::onLogout(const SessionID& sessionID) {
245 1 : LOG() << "[SimulationApp] Session logged out: " << sessionID.to_string();
246 2 : engine_.submit(OrderEvent{OrderEventType::SESSION_LOGOUT, sessionID});
247 1 : }
248 :
249 8 : void SimulationApp::fromApp(const FixMessage& msg, const SessionID& sessionID) {
250 8 : const std::string msgType = msg.get_string(tags::MsgType);
251 :
252 : // =========================================================================
253 : // 1. 身份验证:从 Session 提取用户ID(安全路由的核心)
254 : // =========================================================================
255 : // 关键:不从消息体读取 Account 字段,而是使用 Session 绑定的身份
256 8 : std::string userId = extractAccountId(sessionID);
257 :
258 8 : LOG() << "[SimulationApp] Received MsgType=" << msgType
259 16 : << " from " << sessionID.to_string()
260 8 : << " (User: " << userId << ")";
261 :
262 : // =========================================================================
263 : // 2. 消息路由分发
264 : // =========================================================================
265 8 : if (msgType == "D") {
266 : // NewOrderSingle - 新订单
267 5 : handleNewOrderSingle(msg, sessionID, userId);
268 : }
269 3 : else if (msgType == "F") {
270 : // OrderCancelRequest - 撤单请求
271 1 : handleOrderCancelRequest(msg, sessionID, userId);
272 : }
273 2 : else if (msgType == "U1") {
274 : // BalanceQueryRequest - 资金查询(自定义)
275 0 : handleBalanceQuery(msg, sessionID, userId);
276 : }
277 2 : else if (msgType == "U3") {
278 : // PositionQueryRequest - 持仓查询(自定义)
279 0 : handlePositionQuery(msg, sessionID, userId);
280 : }
281 2 : else if (msgType == "U7") {
282 : // InstrumentSearchRequest - 合约搜索(自定义)
283 : // 注意:合约搜索不需要用户身份验证
284 0 : handleInstrumentSearch(msg, sessionID);
285 : }
286 2 : else if (msgType == "U9") {
287 : // OrderHistoryQueryRequest - 订单历史查询(自定义)
288 1 : handleOrderHistoryQuery(msg, sessionID, userId);
289 : }
290 : else {
291 : // 未知消息类型
292 1 : LOG() << "[SimulationApp] Unknown message type: " << msgType;
293 2 : sendBusinessReject(sessionID, msgType, "Unsupported message type");
294 : }
295 8 : }
296 :
297 1 : void SimulationApp::toApp(FixMessage& msg, const SessionID& sessionID) {
298 1 : const std::string msgType = msg.get_string(tags::MsgType);
299 1 : LOG() << "[SimulationApp] Sending business message: MsgType=" << msgType
300 1 : << " via " << sessionID.to_string();
301 1 : }
302 :
303 5 : void SimulationApp::onExecutionReport(const SessionID& sessionID, const ExecutionReport& report) {
304 10 : LOG() << "[SimulationApp] Sending ExecutionReport to " << sessionID.to_string()
305 5 : << " ClOrdID=" << report.clOrdID
306 5 : << " OrdStatus=" << static_cast<int>(report.ordStatus);
307 :
308 : // =========================================================================
309 : // 订单/成交持久化(Best effort)
310 : // =========================================================================
311 : // 说明:
312 : // - Order 在 NewOrderSingle 收到时已写入 orders 表(PENDING_NEW);
313 : // - 此处根据 ExecutionReport 更新订单状态,并在发生成交时写入 trades 表;
314 : // - 持久化失败不应影响撮合/回报链路,因此仅记录日志,不中断处理。
315 5 : if (store_ && !report.clOrdID.empty()) {
316 2 : Order orderUpdate;
317 2 : orderUpdate.clOrdID = report.clOrdID;
318 2 : orderUpdate.orderID = report.orderID;
319 2 : orderUpdate.cumQty = report.cumQty;
320 2 : orderUpdate.leavesQty = report.leavesQty;
321 2 : orderUpdate.avgPx = report.avgPx;
322 2 : orderUpdate.status = report.ordStatus;
323 :
324 2 : if (!store_->updateOrder(orderUpdate)) {
325 0 : LOG() << "[SimulationApp] Warning: Failed to persist order update: ClOrdID="
326 0 : << report.clOrdID;
327 : }
328 :
329 : // 成交(每次 fill 一条记录)
330 2 : if (report.lastShares > 0 && !report.execID.empty()) {
331 1 : StoredTrade trade;
332 1 : trade.tradeId = report.execID;
333 1 : trade.clOrdID = report.clOrdID;
334 1 : trade.symbol = report.symbol;
335 1 : trade.side = report.side;
336 1 : trade.price = report.lastPx;
337 1 : trade.quantity = report.lastShares;
338 1 : trade.timestamp = to_epoch_millis(report.transactTime);
339 : // 模拟撮合为单边撮合:当前系统没有真实的“对手方订单”概念。
340 : // 该字段为未来扩展双边撮合/对手方回报预留,因此暂固定为空。
341 1 : trade.counterpartyOrderId = "";
342 :
343 1 : if (!store_->saveTrade(trade)) {
344 0 : LOG() << "[SimulationApp] Warning: Failed to persist trade: ExecID="
345 0 : << report.execID << " ClOrdID=" << report.clOrdID;
346 : }
347 1 : }
348 2 : }
349 :
350 : // 获取账户ID
351 5 : std::string accountId;
352 : {
353 5 : std::lock_guard<std::mutex> lock(mapMutex_);
354 5 : auto it = orderAccountMap_.find(report.clOrdID);
355 5 : if (it != orderAccountMap_.end()) {
356 2 : accountId = it->second;
357 : } else {
358 3 : accountId = extractAccountId(sessionID);
359 : }
360 5 : }
361 :
362 : // 根据订单状态处理账户和持仓更新
363 5 : switch (report.ordStatus) {
364 1 : case OrderStatus::FILLED:
365 : case OrderStatus::PARTIALLY_FILLED:
366 1 : if (report.lastShares > 0) {
367 1 : handleFill(accountId, report);
368 : }
369 1 : break;
370 4 : case OrderStatus::REJECTED:
371 4 : handleReject(accountId, report);
372 4 : break;
373 0 : case OrderStatus::CANCELED:
374 0 : handleCancel(accountId, report);
375 0 : break;
376 0 : default:
377 0 : break;
378 : }
379 :
380 : // 清理已完成订单的映射
381 5 : if (report.ordStatus == OrderStatus::FILLED ||
382 4 : report.ordStatus == OrderStatus::REJECTED ||
383 0 : report.ordStatus == OrderStatus::CANCELED) {
384 5 : std::lock_guard<std::mutex> lock(mapMutex_);
385 5 : orderAccountMap_.erase(report.clOrdID);
386 5 : orderMarginInfoMap_.erase(report.clOrdID);
387 5 : }
388 :
389 : // 将 ExecutionReport 转换为 FIX 消息
390 10 : FixMessage msg = buildExecutionReport(report);
391 :
392 : // 通过 SessionManager 发送
393 5 : if (!sessionManager_.sendMessage(sessionID, msg)) {
394 10 : LOG() << "[SimulationApp] Failed to send ExecutionReport: session not found "
395 5 : << sessionID.to_string();
396 : }
397 5 : }
398 :
399 1 : void SimulationApp::handleFill(const std::string& accountId, const ExecutionReport& report) {
400 : // 获取合约信息
401 1 : const Instrument* instrument = instrumentManager_.getInstrument(report.symbol);
402 1 : if (!instrument) {
403 0 : LOG() << "[SimulationApp] handleFill: Instrument not found: " << report.symbol;
404 0 : return;
405 : }
406 :
407 : // 获取持仓信息
408 1 : Position position;
409 1 : auto posOpt = positionManager_.getPosition(accountId, report.symbol);
410 1 : if (posOpt) {
411 0 : position = *posOpt;
412 : } else {
413 1 : position.accountId = accountId;
414 1 : position.instrumentId = report.symbol;
415 : }
416 :
417 : // 计算可平仓数量和开仓数量
418 1 : int64_t closeQty = 0;
419 1 : int64_t openQty = report.lastShares;
420 :
421 1 : if (report.side == OrderSide::BUY && position.shortPosition > 0) {
422 : // 买单:优先平空仓
423 0 : closeQty = std::min(report.lastShares, position.shortPosition);
424 0 : openQty = report.lastShares - closeQty;
425 1 : } else if (report.side == OrderSide::SELL && position.longPosition > 0) {
426 : // 卖单:优先平多仓
427 0 : closeQty = std::min(report.lastShares, position.longPosition);
428 0 : openQty = report.lastShares - closeQty;
429 : }
430 :
431 : // 1. 先处理平仓部分
432 1 : if (closeQty > 0) {
433 0 : double closeProfit = positionManager_.closePosition(
434 0 : accountId, report.symbol, report.side,
435 0 : closeQty, report.lastPx, instrument->volumeMultiple);
436 :
437 : // 计算释放的保证金
438 0 : double releasedMargin = report.lastPx * closeQty *
439 0 : instrument->volumeMultiple * instrument->marginRate;
440 :
441 : // 释放占用保证金
442 0 : accountManager_.releaseMargin(accountId, releasedMargin);
443 :
444 : // 记录平仓盈亏
445 0 : accountManager_.addCloseProfit(accountId, closeProfit);
446 :
447 0 : LOG() << "[SimulationApp] Close position: " << report.symbol
448 0 : << " side=" << static_cast<int>(report.side)
449 0 : << " qty=" << closeQty
450 0 : << " price=" << report.lastPx
451 0 : << " profit=" << closeProfit;
452 : }
453 :
454 : // 2. 再处理开仓部分
455 1 : if (openQty > 0) {
456 1 : double margin = instrument->calculateMargin(report.lastPx, openQty);
457 :
458 : // 获取冻结的保证金(使用原始总量计算,避免部分成交累计误差)
459 1 : double frozenMargin = 0.0;
460 : {
461 1 : std::lock_guard<std::mutex> lock(mapMutex_);
462 1 : auto it = orderMarginInfoMap_.find(report.clOrdID);
463 1 : if (it != orderMarginInfoMap_.end()) {
464 : // 使用原始总冻结保证金按比例计算,避免累计误差
465 1 : frozenMargin = it->second.calculateReleaseAmount(openQty);
466 : }
467 1 : }
468 :
469 : // 冻结转占用
470 1 : accountManager_.confirmMargin(accountId, frozenMargin, margin);
471 :
472 : // 开仓
473 1 : positionManager_.openPosition(
474 1 : accountId, report.symbol, report.side,
475 1 : openQty, report.lastPx, margin);
476 :
477 2 : LOG() << "[SimulationApp] Open position: " << report.symbol
478 2 : << " side=" << static_cast<int>(report.side)
479 1 : << " qty=" << openQty
480 1 : << " price=" << report.lastPx
481 1 : << " margin=" << margin
482 1 : << " frozenReleased=" << frozenMargin;
483 : }
484 :
485 : // 3. 成交后立即刷新该账户的浮动盈亏并推送(reason=2=成交),避免 UI 依赖下一跳行情刷新。
486 : // 使用 report.lastPx 作为最新价:当无行情驱动更新时,至少保证“平仓后持仓变为0”的刷新能发生。
487 1 : positionManager_.updateProfit(accountId, report.symbol, report.lastPx, instrument->volumeMultiple);
488 1 : const double totalProfit = positionManager_.getTotalProfit(accountId);
489 1 : accountManager_.updatePositionProfit(accountId, totalProfit);
490 :
491 1 : pushAccountUpdate(accountId, 2);
492 1 : pushPositionUpdate(accountId, report.symbol, 2);
493 1 : }
494 :
495 4 : void SimulationApp::handleReject(const std::string& accountId, const ExecutionReport& report) {
496 : // 释放全部冻结的保证金(拒绝时释放原始总冻结金额)
497 4 : double frozenMargin = 0.0;
498 : {
499 4 : std::lock_guard<std::mutex> lock(mapMutex_);
500 4 : auto it = orderMarginInfoMap_.find(report.clOrdID);
501 4 : if (it != orderMarginInfoMap_.end()) {
502 : // 拒绝时释放全部原始冻结保证金
503 1 : frozenMargin = it->second.originalFrozenMargin;
504 : }
505 4 : }
506 :
507 4 : if (frozenMargin > 0) {
508 1 : accountManager_.unfreezeMargin(accountId, frozenMargin);
509 1 : LOG() << "[SimulationApp] Released frozen margin on reject: " << frozenMargin;
510 : }
511 4 : }
512 :
513 0 : void SimulationApp::handleCancel(const std::string& accountId, const ExecutionReport& report) {
514 : // 释放剩余未释放的冻结保证金(撤单时只释放未成交部分)
515 0 : double frozenMargin = 0.0;
516 : {
517 0 : std::lock_guard<std::mutex> lock(mapMutex_);
518 0 : auto it = orderMarginInfoMap_.find(report.clOrdID);
519 0 : if (it != orderMarginInfoMap_.end()) {
520 : // 撤单时释放剩余未释放的冻结保证金
521 0 : frozenMargin = it->second.getRemainingFrozen();
522 : }
523 0 : }
524 :
525 0 : if (frozenMargin > 0) {
526 0 : accountManager_.unfreezeMargin(accountId, frozenMargin);
527 0 : LOG() << "[SimulationApp] Released frozen margin on cancel: " << frozenMargin;
528 : }
529 0 : }
530 :
531 12 : std::string SimulationApp::extractAccountId(const SessionID& sessionID) const {
532 : // 通过 SessionManager 获取 Session 对象,提取真实的客户端标识
533 12 : auto session = sessionManager_.findSession(sessionID);
534 12 : if (session) {
535 4 : const std::string& clientId = session->get_client_comp_id();
536 4 : if (!clientId.empty()) {
537 4 : return clientId;
538 : }
539 : }
540 : // 安全策略:无法获取有效的 clientCompID 时返回空字符串
541 : // 调用方应检查返回值并拒绝处理
542 16 : LOG() << "[SimulationApp] Error: Could not extract clientCompID for session "
543 8 : << sessionID.to_string();
544 16 : return "";
545 12 : }
546 :
547 11 : Account SimulationApp::getOrCreateAccount(const std::string& accountId, double initialBalance) {
548 11 : auto accountOpt = accountManager_.getAccount(accountId);
549 11 : if (accountOpt) {
550 1 : return *accountOpt;
551 : }
552 10 : return accountManager_.createAccount(accountId, initialBalance);
553 11 : }
554 :
555 : // ============================================================================
556 : // 消息处理函数实现
557 : // ============================================================================
558 :
559 5 : void SimulationApp::handleNewOrderSingle(const FixMessage& msg, const SessionID& sessionID, const std::string& userId) {
560 : // 解析订单
561 5 : ParseResult result = parseNewOrderSingle(msg, sessionID);
562 5 : if (!result.success) {
563 0 : LOG() << "[SimulationApp] Parse failed: " << result.error;
564 0 : ExecutionReport reject;
565 0 : reject.clOrdID = msg.has(tags::ClOrdID) ? msg.get_string(tags::ClOrdID) : "";
566 0 : reject.symbol = msg.has(tags::Symbol) ? msg.get_string(tags::Symbol) : "";
567 0 : reject.ordStatus = OrderStatus::REJECTED;
568 0 : reject.execTransType = ExecTransType::NEW;
569 0 : reject.ordRejReason = 99;
570 0 : reject.text = result.error;
571 0 : reject.transactTime = std::chrono::system_clock::now();
572 0 : onExecutionReport(sessionID, reject);
573 0 : return;
574 0 : }
575 :
576 5 : Order& order = result.order;
577 :
578 : // =========================================================================
579 : // 订单持久化(Best effort)
580 : // =========================================================================
581 : // 将订单的初始状态写入存储(通常为 PENDING_NEW)。
582 : // 后续状态变化由 onExecutionReport() 驱动 updateOrder() 完成。
583 5 : if (store_) {
584 2 : if (!store_->saveOrderForAccount(order, userId)) {
585 0 : LOG() << "[SimulationApp] Warning: Failed to persist new order: ClOrdID="
586 0 : << order.clOrdID;
587 : }
588 : }
589 :
590 : // 关键:使用从 Session 提取的 userId,而非消息体中的 Account 字段
591 : // 这是安全路由的核心 - 防止用户伪造账户ID
592 :
593 : // 确保账户存在
594 5 : getOrCreateAccount(userId);
595 :
596 : // 获取合约信息
597 5 : const Instrument* instrument = instrumentManager_.getInstrument(order.symbol);
598 5 : if (!instrument) {
599 2 : LOG() << "[SimulationApp] Instrument not found: " << order.symbol;
600 2 : ExecutionReport reject;
601 2 : reject.clOrdID = order.clOrdID;
602 2 : reject.symbol = order.symbol;
603 2 : reject.side = order.side;
604 2 : reject.ordType = order.ordType;
605 2 : reject.orderQty = order.orderQty;
606 2 : reject.price = order.price;
607 2 : reject.ordStatus = OrderStatus::REJECTED;
608 2 : reject.execTransType = ExecTransType::NEW;
609 2 : reject.ordRejReason = static_cast<int>(RejectReason::INSTRUMENT_NOT_FOUND);
610 2 : reject.text = "Instrument not found: " + order.symbol;
611 2 : reject.transactTime = std::chrono::system_clock::now();
612 2 : onExecutionReport(sessionID, reject);
613 2 : return;
614 2 : }
615 :
616 : // 获取账户和持仓信息
617 3 : auto accountOpt = accountManager_.getAccount(userId);
618 3 : if (!accountOpt) {
619 0 : LOG() << "[SimulationApp] Account not found: " << userId;
620 0 : return;
621 : }
622 :
623 3 : Position position;
624 3 : auto posOpt = positionManager_.getPosition(userId, order.symbol);
625 3 : if (posOpt) {
626 0 : position = *posOpt;
627 : } else {
628 3 : position.accountId = userId;
629 3 : position.instrumentId = order.symbol;
630 : }
631 :
632 : // 获取行情快照
633 3 : MarketDataSnapshot snapshot;
634 3 : const MarketDataSnapshot* snapshotPtr = engine_.getMarketSnapshot(order.symbol);
635 3 : if (snapshotPtr) {
636 2 : snapshot = *snapshotPtr;
637 : } else {
638 1 : snapshot.instrumentId = order.symbol;
639 1 : snapshot.upperLimitPrice = instrument->upperLimitPrice;
640 1 : snapshot.lowerLimitPrice = instrument->lowerLimitPrice;
641 : }
642 :
643 : // 判断开平标志
644 3 : OffsetFlag offsetFlag = OffsetFlag::OPEN;
645 3 : if (order.side == OrderSide::BUY && position.shortPosition > 0) {
646 0 : offsetFlag = OffsetFlag::CLOSE;
647 3 : } else if (order.side == OrderSide::SELL && position.longPosition > 0) {
648 0 : offsetFlag = OffsetFlag::CLOSE;
649 : }
650 :
651 : // 风控检查
652 : CheckResult checkResult = riskManager_.checkOrder(
653 3 : order, *accountOpt, position, *instrument, snapshot, offsetFlag);
654 :
655 3 : if (!checkResult.passed) {
656 1 : LOG() << "[SimulationApp] Risk check failed: " << checkResult.rejectText;
657 1 : ExecutionReport reject;
658 1 : reject.clOrdID = order.clOrdID;
659 1 : reject.symbol = order.symbol;
660 1 : reject.side = order.side;
661 1 : reject.ordType = order.ordType;
662 1 : reject.orderQty = order.orderQty;
663 1 : reject.price = order.price;
664 1 : reject.ordStatus = OrderStatus::REJECTED;
665 1 : reject.execTransType = ExecTransType::NEW;
666 1 : reject.ordRejReason = static_cast<int>(checkResult.rejectReason);
667 1 : reject.text = checkResult.rejectText;
668 1 : reject.transactTime = std::chrono::system_clock::now();
669 1 : onExecutionReport(sessionID, reject);
670 1 : return;
671 1 : }
672 :
673 : // 开仓订单:冻结保证金
674 2 : if (offsetFlag == OffsetFlag::OPEN) {
675 2 : double requiredMargin = riskManager_.calculateRequiredMargin(order, *instrument);
676 2 : if (!accountManager_.freezeMargin(userId, requiredMargin)) {
677 0 : LOG() << "[SimulationApp] Failed to freeze margin: " << requiredMargin;
678 0 : ExecutionReport reject;
679 0 : reject.clOrdID = order.clOrdID;
680 0 : reject.symbol = order.symbol;
681 0 : reject.side = order.side;
682 0 : reject.ordType = order.ordType;
683 0 : reject.orderQty = order.orderQty;
684 0 : reject.price = order.price;
685 0 : reject.ordStatus = OrderStatus::REJECTED;
686 0 : reject.execTransType = ExecTransType::NEW;
687 0 : reject.ordRejReason = static_cast<int>(RejectReason::INSUFFICIENT_FUNDS);
688 0 : reject.text = "Failed to freeze margin";
689 0 : reject.transactTime = std::chrono::system_clock::now();
690 0 : onExecutionReport(sessionID, reject);
691 0 : return;
692 0 : }
693 :
694 : {
695 2 : std::lock_guard<std::mutex> lock(mapMutex_);
696 2 : orderMarginInfoMap_[order.clOrdID] = OrderMarginInfo(requiredMargin, order.orderQty);
697 2 : }
698 : }
699 :
700 : // 记录订单到账户的映射
701 : {
702 2 : std::lock_guard<std::mutex> lock(mapMutex_);
703 2 : orderAccountMap_[order.clOrdID] = userId;
704 2 : }
705 :
706 : // 提交到撮合引擎(传入真实的用户ID)
707 2 : engine_.submit(OrderEvent::newOrder(order, userId));
708 10 : }
709 :
710 1 : void SimulationApp::handleOrderCancelRequest(const FixMessage& msg, const SessionID& sessionID, const std::string& userId) {
711 1 : CancelRequest req = parseCancelRequest(msg, sessionID);
712 :
713 : // 安全检查:验证撤单请求是否属于当前用户
714 : {
715 1 : std::lock_guard<std::mutex> lock(mapMutex_);
716 1 : auto it = orderAccountMap_.find(req.origClOrdID);
717 1 : if (it == orderAccountMap_.end()) {
718 1 : LOG() << "[SimulationApp] Cancel rejected: order " << req.origClOrdID << " not found";
719 3 : sendBusinessReject(sessionID, "F", "Order not found: " + req.origClOrdID);
720 1 : return;
721 : }
722 0 : if (it->second != userId) {
723 0 : LOG() << "[SimulationApp] Cancel rejected: order " << req.origClOrdID
724 0 : << " belongs to " << it->second << ", not " << userId;
725 0 : sendBusinessReject(sessionID, "F", "Not authorized to cancel this order");
726 0 : return;
727 : }
728 1 : }
729 :
730 0 : engine_.submit(OrderEvent::cancelRequest(req, userId));
731 1 : }
732 :
733 0 : void SimulationApp::handleBalanceQuery(const FixMessage& msg, const SessionID& sessionID, const std::string& userId) {
734 0 : LOG() << "[SimulationApp] Processing balance query for user: " << userId;
735 :
736 : // 查询账户数据
737 0 : auto accountOpt = accountManager_.getAccount(userId);
738 0 : if (!accountOpt) {
739 0 : LOG() << "[SimulationApp] Account not found for balance query: " << userId;
740 0 : sendBusinessReject(sessionID, "U1", "Account not found");
741 0 : return;
742 : }
743 :
744 0 : const Account& account = *accountOpt;
745 :
746 : // 构造 U2 响应消息 (BalanceQueryResponse)
747 0 : FixMessage response;
748 0 : response.set(tags::MsgType, "U2");
749 :
750 : // 回填请求ID(如果客户端发了的话)
751 0 : if (msg.has(tags::RequestID)) {
752 0 : response.set(tags::RequestID, msg.get_string(tags::RequestID));
753 : }
754 :
755 : // 账户标识
756 0 : response.set(tags::Account, userId);
757 :
758 : // 资金信息(使用自定义 Tag)
759 : // 注意:FixMessage::set 只支持 string 和 int,需要将 double 转为 string
760 0 : std::ostringstream oss;
761 0 : oss << std::fixed << std::setprecision(2);
762 :
763 0 : oss.str(""); oss << account.balance;
764 0 : response.set(tags::Balance, oss.str());
765 :
766 0 : oss.str(""); oss << account.available;
767 0 : response.set(tags::Available, oss.str());
768 :
769 0 : oss.str(""); oss << account.frozenMargin;
770 0 : response.set(tags::FrozenMargin, oss.str());
771 :
772 0 : oss.str(""); oss << account.usedMargin;
773 0 : response.set(tags::UsedMargin, oss.str());
774 :
775 0 : oss.str(""); oss << account.positionProfit;
776 0 : response.set(tags::PositionProfit, oss.str());
777 :
778 0 : oss.str(""); oss << account.closeProfit;
779 0 : response.set(tags::CloseProfit, oss.str());
780 :
781 0 : oss.str(""); oss << account.getDynamicEquity();
782 0 : response.set(tags::DynamicEquity, oss.str());
783 :
784 0 : oss.str(""); oss << account.getRiskRatio();
785 0 : response.set(tags::RiskRatio, oss.str());
786 :
787 : // 发送响应
788 0 : if (!sessionManager_.sendMessage(sessionID, response)) {
789 0 : LOG() << "[SimulationApp] Failed to send balance response to " << sessionID.to_string();
790 : } else {
791 0 : LOG() << "[SimulationApp] Sent balance response to " << userId
792 0 : << " Balance=" << account.balance
793 0 : << " Available=" << account.available;
794 : }
795 0 : }
796 :
797 0 : void SimulationApp::handlePositionQuery(const FixMessage& msg, const SessionID& sessionID, const std::string& userId) {
798 0 : LOG() << "[SimulationApp] Processing position query for user: " << userId;
799 :
800 : // 获取用户的所有持仓
801 0 : auto positions = positionManager_.getPositionsByAccount(userId);
802 :
803 : // 构造 U4 响应消息 (PositionQueryResponse)
804 0 : FixMessage response;
805 0 : response.set(tags::MsgType, "U4");
806 :
807 : // 回填请求ID
808 0 : if (msg.has(tags::RequestID)) {
809 0 : response.set(tags::RequestID, msg.get_string(tags::RequestID));
810 : }
811 :
812 0 : response.set(tags::Account, userId);
813 0 : response.set(tags::NoPositions, static_cast<int>(positions.size()));
814 :
815 : // 注意:FIX 协议的 Repeating Group 处理比较复杂
816 : // 这里简化处理:将持仓信息序列化为文本格式放在 Text 字段
817 : // 实际生产环境应该使用标准的 Repeating Group 格式
818 0 : if (!positions.empty()) {
819 0 : std::ostringstream posText;
820 0 : posText << std::fixed << std::setprecision(2);
821 0 : for (const auto& pos : positions) {
822 0 : posText << pos.instrumentId << ":"
823 0 : << "L" << pos.longPosition << "@" << pos.longAvgPrice << ","
824 0 : << "S" << pos.shortPosition << "@" << pos.shortAvgPrice << ";";
825 : }
826 0 : response.set(tags::Text, posText.str());
827 0 : }
828 :
829 : // 发送响应
830 0 : if (!sessionManager_.sendMessage(sessionID, response)) {
831 0 : LOG() << "[SimulationApp] Failed to send position response to " << sessionID.to_string();
832 : } else {
833 0 : LOG() << "[SimulationApp] Sent position response to " << userId
834 0 : << " with " << positions.size() << " positions";
835 : }
836 0 : }
837 :
838 2 : void SimulationApp::sendBusinessReject(const SessionID& sessionID, const std::string& refMsgType, const std::string& reason) {
839 : // 构造 BusinessMessageReject (MsgType = j)
840 2 : FixMessage reject;
841 2 : reject.set(tags::MsgType, "j");
842 2 : reject.set(tags::Text, reason);
843 : // RefMsgType 标准 Tag 是 372,这里简化处理放在 Text 中
844 :
845 4 : LOG() << "[SimulationApp] Sending BusinessReject for MsgType=" << refMsgType
846 2 : << " reason: " << reason;
847 :
848 2 : if (!sessionManager_.sendMessage(sessionID, reject)) {
849 2 : LOG() << "[SimulationApp] Failed to send BusinessReject to " << sessionID.to_string();
850 : }
851 2 : }
852 :
853 : // ============================================================================
854 : // 行情驱动账户更新实现
855 : // ============================================================================
856 :
857 2 : void SimulationApp::onMarketDataUpdate(const std::string& instrumentId, double lastPrice) {
858 : // 获取合约信息
859 2 : const Instrument* instrument = instrumentManager_.getInstrument(instrumentId);
860 2 : if (!instrument) {
861 0 : return;
862 : }
863 :
864 : // 获取该合约的所有持仓
865 2 : auto allPositions = positionManager_.getAllPositions();
866 :
867 : // 收集受影响的账户
868 2 : std::set<std::string> affectedAccounts;
869 :
870 2 : for (const auto& pos : allPositions) {
871 0 : if (pos.instrumentId == instrumentId && pos.hasPosition()) {
872 : // 更新持仓浮动盈亏
873 0 : double newProfit = positionManager_.updateProfit(
874 0 : pos.accountId, instrumentId, lastPrice, instrument->volumeMultiple);
875 :
876 0 : affectedAccounts.insert(pos.accountId);
877 :
878 0 : LOG() << "[SimulationApp] Updated position profit for " << pos.accountId
879 0 : << " " << instrumentId << " profit=" << newProfit;
880 : }
881 : }
882 :
883 : // 更新受影响账户的持仓盈亏总额,并推送给 Client
884 2 : for (const auto& accountId : affectedAccounts) {
885 0 : double totalProfit = positionManager_.getTotalProfit(accountId);
886 0 : accountManager_.updatePositionProfit(accountId, totalProfit);
887 :
888 : // 推送账户更新给 Client(行情变化触发)
889 0 : pushAccountUpdate(accountId, 1); // 1 = 行情变化
890 :
891 : // 同时推送持仓更新(包含最新盈亏)
892 0 : pushPositionUpdate(accountId, instrumentId, 1); // 1 = 行情变化
893 : }
894 2 : }
895 :
896 2 : void SimulationApp::pushAccountUpdate(const std::string& userId, int reason) {
897 : // 查找用户对应的 Session
898 2 : auto sessionOpt = findSessionByUserId(userId);
899 2 : if (!sessionOpt) {
900 0 : return;
901 : }
902 :
903 : // 获取账户数据
904 2 : auto accountOpt = accountManager_.getAccount(userId);
905 2 : if (!accountOpt) {
906 0 : return;
907 : }
908 :
909 2 : const Account& account = *accountOpt;
910 :
911 : // 构造 U5 推送消息 (AccountUpdateNotification)
912 2 : FixMessage msg;
913 2 : msg.set(tags::MsgType, "U5");
914 2 : msg.set(tags::Account, userId);
915 2 : msg.set(tags::UpdateType, 1); // 1 = 账户更新
916 2 : msg.set(tags::UpdateReason, reason);
917 :
918 2 : std::ostringstream oss;
919 2 : oss << std::fixed << std::setprecision(2);
920 :
921 4 : oss.str(""); oss << account.balance;
922 2 : msg.set(tags::Balance, oss.str());
923 :
924 4 : oss.str(""); oss << account.available;
925 2 : msg.set(tags::Available, oss.str());
926 :
927 4 : oss.str(""); oss << account.frozenMargin;
928 2 : msg.set(tags::FrozenMargin, oss.str());
929 :
930 4 : oss.str(""); oss << account.usedMargin;
931 2 : msg.set(tags::UsedMargin, oss.str());
932 :
933 4 : oss.str(""); oss << account.positionProfit;
934 2 : msg.set(tags::PositionProfit, oss.str());
935 :
936 4 : oss.str(""); oss << account.closeProfit;
937 2 : msg.set(tags::CloseProfit, oss.str());
938 :
939 4 : oss.str(""); oss << account.getDynamicEquity();
940 2 : msg.set(tags::DynamicEquity, oss.str());
941 :
942 4 : oss.str(""); oss << account.getRiskRatio();
943 2 : msg.set(tags::RiskRatio, oss.str());
944 :
945 : // 发送推送
946 2 : if (!sessionManager_.sendMessage(*sessionOpt, msg)) {
947 : // 用户可能已离线/会话已不可用:推送属于“尽力而为”,失败时静默丢弃即可。
948 : }
949 2 : }
950 :
951 1 : void SimulationApp::pushPositionUpdate(const std::string& userId, const std::string& instrumentId, int reason) {
952 : // 查找用户对应的 Session
953 1 : auto sessionOpt = findSessionByUserId(userId);
954 1 : if (!sessionOpt) {
955 0 : return;
956 : }
957 :
958 : // 获取持仓数据
959 1 : auto posOpt = positionManager_.getPosition(userId, instrumentId);
960 1 : if (!posOpt) {
961 0 : return;
962 : }
963 :
964 1 : const Position& pos = *posOpt;
965 :
966 : // 构造 U6 推送消息 (PositionUpdateNotification)
967 1 : FixMessage msg;
968 1 : msg.set(tags::MsgType, "U6");
969 1 : msg.set(tags::Account, userId);
970 1 : msg.set(tags::UpdateType, 2); // 2 = 持仓更新
971 1 : msg.set(tags::UpdateReason, reason);
972 1 : msg.set(tags::InstrumentID, instrumentId);
973 :
974 1 : std::ostringstream oss;
975 1 : oss << std::fixed << std::setprecision(2);
976 :
977 1 : msg.set(tags::LongPosition, static_cast<int>(pos.longPosition));
978 :
979 2 : oss.str(""); oss << pos.longAvgPrice;
980 1 : msg.set(tags::LongAvgPrice, oss.str());
981 :
982 1 : msg.set(tags::ShortPosition, static_cast<int>(pos.shortPosition));
983 :
984 2 : oss.str(""); oss << pos.shortAvgPrice;
985 1 : msg.set(tags::ShortAvgPrice, oss.str());
986 :
987 2 : oss.str(""); oss << pos.getTotalProfit();
988 1 : msg.set(tags::PositionProfit, oss.str());
989 :
990 : // 发送推送
991 1 : if (!sessionManager_.sendMessage(*sessionOpt, msg)) {
992 : // 用户可能已离线/会话已不可用:推送属于“尽力而为”,失败时静默丢弃即可。
993 : }
994 1 : }
995 :
996 3 : std::optional<SessionID> SimulationApp::findSessionByUserId(const std::string& userId) const {
997 : // 遍历所有 Session,找到 clientCompID == userId 的那个
998 3 : std::optional<SessionID> result;
999 :
1000 3 : sessionManager_.forEachSession([&](const SessionID& sid, std::shared_ptr<Session> session) {
1001 : // 已找到则跳过后续遍历
1002 3 : if (result.has_value()) {
1003 0 : return;
1004 : }
1005 : // 使用 Session::get_client_comp_id() 获取真实的客户端标识
1006 3 : if (session && session->get_client_comp_id() == userId) {
1007 3 : result = sid;
1008 : }
1009 : });
1010 :
1011 3 : return result;
1012 0 : }
1013 :
1014 : // ============================================================================
1015 : // 合约搜索实现
1016 : // ============================================================================
1017 :
1018 0 : void SimulationApp::handleInstrumentSearch(const FixMessage& msg, const SessionID& sessionID) {
1019 : // 获取搜索参数
1020 0 : std::string pattern;
1021 0 : if (msg.has(tags::SearchPattern)) {
1022 0 : pattern = msg.get_string(tags::SearchPattern);
1023 : }
1024 :
1025 0 : size_t maxResults = 10; // 默认返回 10 条
1026 0 : if (msg.has(tags::MaxResults)) {
1027 0 : maxResults = static_cast<size_t>(msg.get_int(tags::MaxResults));
1028 0 : if (maxResults > 50) maxResults = 50; // 限制最大返回数量
1029 : }
1030 :
1031 0 : LOG() << "[SimulationApp] Processing instrument search: pattern=" << pattern
1032 0 : << " maxResults=" << maxResults;
1033 :
1034 : // 搜索合约
1035 0 : auto results = instrumentManager_.searchByPrefix(pattern, maxResults);
1036 :
1037 : // 构造 U8 响应消息 (InstrumentSearchResponse)
1038 0 : FixMessage response;
1039 0 : response.set(tags::MsgType, "U8");
1040 :
1041 : // 回填请求ID
1042 0 : if (msg.has(tags::RequestID)) {
1043 0 : response.set(tags::RequestID, msg.get_string(tags::RequestID));
1044 : }
1045 :
1046 0 : response.set(tags::SearchPattern, pattern);
1047 0 : response.set(tags::ResultCount, static_cast<int>(results.size()));
1048 :
1049 : // 将合约列表序列化为逗号分隔的字符串
1050 0 : if (!results.empty()) {
1051 0 : std::ostringstream oss;
1052 0 : for (size_t i = 0; i < results.size(); ++i) {
1053 0 : if (i > 0) oss << ",";
1054 0 : oss << results[i];
1055 : }
1056 0 : response.set(tags::InstrumentList, oss.str());
1057 0 : }
1058 :
1059 : // 发送响应
1060 0 : if (!sessionManager_.sendMessage(sessionID, response)) {
1061 0 : LOG() << "[SimulationApp] Failed to send instrument search response to "
1062 0 : << sessionID.to_string();
1063 : } else {
1064 0 : LOG() << "[SimulationApp] Sent instrument search response: " << results.size() << " results";
1065 : }
1066 0 : }
1067 :
1068 1 : void SimulationApp::handleOrderHistoryQuery(const FixMessage& msg, const SessionID& sessionID, const std::string& userId) {
1069 1 : LOG() << "[SimulationApp] Processing order history query for user: " << userId;
1070 :
1071 1 : if (!store_) {
1072 : // 未启用持久化时,返回空列表(best effort)
1073 0 : FixMessage response;
1074 0 : response.set(tags::MsgType, "U10");
1075 0 : if (msg.has(tags::RequestID)) {
1076 0 : response.set(tags::RequestID, msg.get_string(tags::RequestID));
1077 : }
1078 0 : response.set(tags::Text, "");
1079 0 : sessionManager_.sendMessage(sessionID, response);
1080 0 : return;
1081 0 : }
1082 :
1083 1 : std::vector<Order> orders = store_->loadOrdersByAccount(userId);
1084 1 : if (msg.has(tags::Symbol) && !msg.get_string(tags::Symbol).empty()) {
1085 0 : const std::string symbol = msg.get_string(tags::Symbol);
1086 0 : orders.erase(
1087 0 : std::remove_if(
1088 : orders.begin(),
1089 : orders.end(),
1090 0 : [&](const Order& o) { return o.symbol != symbol; }),
1091 0 : orders.end());
1092 0 : }
1093 :
1094 1 : auto toClientOrderState = [](OrderStatus status) -> int {
1095 : // client::OrderState: PENDING_NEW=0, NEW=1, PARTIALLY_FILLED=2, FILLED=3, CANCELED=4, REJECTED=5
1096 1 : switch (status) {
1097 0 : case OrderStatus::PENDING_NEW: return 0;
1098 0 : case OrderStatus::NEW: return 1;
1099 1 : case OrderStatus::PARTIALLY_FILLED: return 2;
1100 0 : case OrderStatus::FILLED: return 3;
1101 0 : case OrderStatus::CANCELED: return 4;
1102 0 : case OrderStatus::REJECTED: return 5;
1103 0 : default: return 0;
1104 : }
1105 : };
1106 :
1107 1 : auto toSideString = [](OrderSide side) -> const char* {
1108 1 : return side == OrderSide::BUY ? "BUY" : "SELL";
1109 : };
1110 :
1111 : // 序列化格式与 client 本地文件保存格式对齐:每行一个订单,字段用 '|' 分隔。
1112 : // clOrdID|orderId|symbol|side|price|orderQty|filledQty|avgPx|state|text|updateTime
1113 1 : std::ostringstream text;
1114 1 : text << std::fixed << std::setprecision(6);
1115 2 : for (const auto& o : orders) {
1116 1 : text << o.clOrdID << "|"
1117 1 : << o.orderID << "|"
1118 1 : << o.symbol << "|"
1119 1 : << toSideString(o.side) << "|"
1120 1 : << o.price << "|"
1121 1 : << o.orderQty << "|"
1122 1 : << o.cumQty << "|"
1123 1 : << o.avgPx << "|"
1124 1 : << toClientOrderState(o.status) << "|"
1125 : << "-" << "|"
1126 : << "-"
1127 1 : << "\n";
1128 : }
1129 :
1130 1 : FixMessage response;
1131 1 : response.set(tags::MsgType, "U10");
1132 1 : if (msg.has(tags::RequestID)) {
1133 1 : response.set(tags::RequestID, msg.get_string(tags::RequestID));
1134 : }
1135 1 : response.set(tags::Account, userId);
1136 1 : response.set(tags::Text, text.str());
1137 :
1138 1 : if (!sessionManager_.sendMessage(sessionID, response)) {
1139 0 : LOG() << "[SimulationApp] Failed to send order history response to " << sessionID.to_string();
1140 : }
1141 1 : }
1142 :
1143 : } // namespace fix40
|