#include "actors/AITraderActor.hpp" #include #include #include #include namespace eunex { AITraderActor::AITraderActor(const tredzone::ActorId& oeGatewayId, const std::vector& symbols) : oePipe_(*this, oeGatewayId) , symbols_(symbols) , rng_(std::random_device{}()) { registerEventHandler(*this); registerEventHandler(*this); registerEventHandler(*this); initMembers(); registerCallback(*this); } void AITraderActor::initMembers() { Strategy strategies[] = {Strategy::Momentum, Strategy::MeanReversion, Strategy::Random}; for (int i = 0; i < NUM_MEMBERS; ++i) { AITraderMember m{}; m.memberId = static_cast(i + 1); m.sessionId = static_cast(200 + i); std::snprintf(m.name, sizeof(m.name), "MBR%02d", i + 1); m.strategy = strategies[i % 3]; m.orderCount = 0; members_.push_back(m); } } void AITraderActor::onEvent(const BookUpdateEvent& event) { BBO bbo{}; if (event.bidDepth > 0) bbo.bestBid = event.bids[0].price; if (event.askDepth > 0) bbo.bestAsk = event.asks[0].price; bbos_[event.symbolIdx] = bbo; } void AITraderActor::onEvent(const TradeEvent& event) { auto& history = priceHistory_[event.trade.symbolIdx]; history.push_back(event.trade.price); if (history.size() > MAX_PRICE_HISTORY) { history.erase(history.begin()); } } void AITraderActor::onEvent(const ExecReportEvent&) { // Could track fills per member; not needed for basic trading } void AITraderActor::onCallback() { tradeRound(); registerCallback(*this); } void AITraderActor::tradeRound() { if (symbols_.empty()) return; for (auto& member : members_) { std::uniform_int_distribution symDist(0, symbols_.size() - 1); SymbolIndex_t symIdx = symbols_[symDist(rng_)]; auto bboIt = bbos_.find(symIdx); if (bboIt == bbos_.end() || (bboIt->second.bestBid == 0 && bboIt->second.bestAsk == 0)) { submitOrder(member, symIdx); continue; } const BBO& bbo = bboIt->second; auto histIt = priceHistory_.find(symIdx); std::vector emptyHist; const auto& history = (histIt != priceHistory_.end()) ? histIt->second : emptyHist; switch (member.strategy) { case Strategy::Momentum: strategyMomentum(member, symIdx, bbo, history); break; case Strategy::MeanReversion: strategyMeanReversion(member, symIdx, bbo, history); break; case Strategy::Random: strategyRandom(member, symIdx, bbo); break; } } } Price_t AITraderActor::referencePrice(SymbolIndex_t sym) { switch (sym) { case 1: return toFixedPrice(154.0); // AAPL case 2: return toFixedPrice(324.0); // MSFT case 3: return toFixedPrice(141.0); // GOOGL case 4: return toFixedPrice(375.0); // TSLA case 5: return toFixedPrice(201.0); // NVDA case 6: return toFixedPrice(320.0); // AMD case 7: return toFixedPrice(146.0); // ENX default: return toFixedPrice(100.0); } } void AITraderActor::submitOrder(const AITraderMember& member, SymbolIndex_t symIdx) { std::uniform_int_distribution sideDist(0, 1); std::uniform_int_distribution qtyDist(10, 100); Side side = sideDist(rng_) ? Side::Buy : Side::Sell; Price_t refPrice = referencePrice(symIdx); std::uniform_int_distribution spreadDist(-3, 3); Price_t tickOffset = spreadDist(rng_) * (PRICE_SCALE / 100); Price_t price = refPrice + tickOffset; if (price <= 0) price = PRICE_SCALE; Quantity_t qty = qtyDist(rng_); ClOrdId_t clOrdId = nextClOrdId_++; oePipe_.push(clOrdId, symIdx, side, OrderType::Limit, TimeInForce::Day, price, qty, member.sessionId); } void AITraderActor::strategyMomentum(const AITraderMember& member, SymbolIndex_t symIdx, const BBO& bbo, const std::vector& history) { if (history.size() < 3) { submitOrder(member, symIdx); return; } Price_t recent = history.back(); Price_t older = history[history.size() - 3]; Side side = (recent > older) ? Side::Buy : Side::Sell; Price_t midPrice = (bbo.bestBid + bbo.bestAsk) / 2; if (midPrice == 0) midPrice = recent; std::uniform_int_distribution spreadDist(-2, 2); Price_t tickOffset = spreadDist(rng_) * (PRICE_SCALE / 100); Price_t price = midPrice + tickOffset; if (price <= 0) price = PRICE_SCALE; std::uniform_int_distribution qtyDist(10, 50); Quantity_t qty = qtyDist(rng_); ClOrdId_t clOrdId = nextClOrdId_++; oePipe_.push(clOrdId, symIdx, side, OrderType::Limit, TimeInForce::Day, price, qty, member.sessionId); } void AITraderActor::strategyMeanReversion(const AITraderMember& member, SymbolIndex_t symIdx, const BBO& bbo, const std::vector& history) { if (history.size() < 5) { submitOrder(member, symIdx); return; } int64_t sum = 0; for (auto p : history) sum += p; Price_t mean = static_cast(sum / static_cast(history.size())); Price_t current = history.back(); Side side = (current > mean) ? Side::Sell : Side::Buy; Price_t price = mean; std::uniform_int_distribution qtyDist(10, 50); Quantity_t qty = qtyDist(rng_); ClOrdId_t clOrdId = nextClOrdId_++; oePipe_.push(clOrdId, symIdx, side, OrderType::Limit, TimeInForce::Day, price, qty, member.sessionId); } void AITraderActor::strategyRandom(const AITraderMember& member, SymbolIndex_t symIdx, const BBO& bbo) { std::uniform_int_distribution sideDist(0, 1); Side side = sideDist(rng_) ? Side::Buy : Side::Sell; Price_t midPrice = (bbo.bestBid + bbo.bestAsk) / 2; if (midPrice == 0) midPrice = referencePrice(symIdx); std::uniform_int_distribution spreadDist(-5, 5); Price_t tickOffset = spreadDist(rng_) * (PRICE_SCALE / 100); Price_t price = midPrice + tickOffset; if (price <= 0) price = PRICE_SCALE; std::uniform_int_distribution qtyDist(5, 100); Quantity_t qty = qtyDist(rng_); ClOrdId_t clOrdId = nextClOrdId_++; oePipe_.push(clOrdId, symIdx, side, OrderType::Limit, TimeInForce::Day, price, qty, member.sessionId); } int AITraderActor::totalOrderCount() const { int total = 0; for (auto& m : members_) total += m.orderCount; return total; } } // namespace eunex