========== Handlers includes (top 40) ========== 1: // src/screens/equity_trading/EquityTradingScreen_Handlers.cpp 2: // 3: // User-action handlers: account switch / symbol selection / mode toggle, 4: // token-expiry recovery, accounts dialog, order submit / cancel / modify, 5: // order-book click, refresh_candles. 6: // 7: // Part of the partial-class split of EquityTradingScreen.cpp. 8: 9: #include "screens/equity_trading/EquityTradingScreen.h" 10: 11: #include "core/logging/Logger.h" 12: #include "core/session/ScreenStateManager.h" 13: #include "core/symbol/SymbolContext.h" 14: #include "core/symbol/SymbolDragSource.h" 15: #include "screens/equity_trading/AccountManagementDialog.h" 16: #include "screens/equity_trading/BroadcastOrderDialog.h" 17: #include "screens/equity_trading/EquityBottomPanel.h" 18: #include "screens/equity_trading/EquityChart.h" 19: #include "screens/equity_trading/EquityOrderBook.h" 20: #include "screens/equity_trading/EquityOrderEntry.h" 21: #include "screens/equity_trading/EquityTickerBar.h" 22: #include "screens/equity_trading/EquityWatchlist.h" 23: #include "services/portfolio/PortfolioService.h" 24: #include "storage/repositories/SettingsRepository.h" 25: #include "trading/AccountManager.h" 26: #include "trading/ActionCenter.h" 27: #include "trading/BrokerRegistry.h" 28: #include "trading/DataStreamManager.h" 29: #include "trading/OrderMatcher.h" 30: #include "trading/PaperTrading.h" 31: #include "ui/theme/StyleSheets.h" 32: #include "ui/theme/Theme.h" 33: 34: #include 35: #include 36: #include 37: #include 38: #include 39: #include 40: #include ========== Handlers: on_account_changed + credentials_saved (260-360) ========== 260: auto* dlg = new AccountManagementDialog(this); 261: 262: connect(dlg, &AccountManagementDialog::account_added, this, [this](const QString& account_id) { 263: Q_UNUSED(account_id); 264: update_account_menu(); 265: // Don't auto-focus a freshly-added account: it has no credentials yet, 266: // so on_account_changed() would spawn ~11 QtConcurrent::run fetches that 267: // all bail at the empty-api_key check. On macOS that thread-storm trips 268: // pthread_create inside QThreadPool and crashes the main thread (see 269: // crash report 2026-05-08-123609.ips). Defer focus to credentials_saved. 270: }); 271: 272: connect(dlg, &AccountManagementDialog::account_removed, this, [this](const QString& account_id) { 273: DataStreamManager::instance().stop_stream(account_id); 274: update_account_menu(); 275: // If removed the focused account, switch to next available 276: if (account_id == focused_account_id_) { 277: auto accounts = AccountManager::instance().active_accounts(); 278: if (!accounts.isEmpty()) 279: on_account_changed(accounts.first().account_id); 280: else 281: focused_account_id_.clear(); 282: } 283: update_connection_status(); 284: }); 285: 286: connect(dlg, &AccountManagementDialog::credentials_saved, this, [this](const QString& account_id) { 287: token_expired_shown_.store(false); 288: // First account ever connected: focus it now (account_added intentionally 289: // does not auto-focus — see comment there). on_account_changed() does its 290: // own start_stream() + symbol/watchlist setup, so we're done after that. 291: if (focused_account_id_.isEmpty()) { 292: on_account_changed(account_id); 293: AccountManager::instance().set_connection_state(account_id, ConnectionState::Connected); 294: update_connection_status(); 295: return; 296: } 297: // Start/restart the stream with new credentials 298: DataStreamManager::instance().start_stream(account_id); 299: auto* stream = DataStreamManager::instance().stream_for(account_id); 300: if (stream && account_id == focused_account_id_) { 301: stream->set_selected_symbol(selected_symbol_, selected_exchange_); 302: stream->subscribe_symbols(watchlist_symbols_); 303: } 304: AccountManager::instance().set_connection_state(account_id, ConnectionState::Connected); 305: update_connection_status(); 306: }); 307: 308: dlg->exec(); 309: dlg->deleteLater(); 310: } 311: 312: // ============================================================================ 313: // Order Handling 314: // ============================================================================ 315: 316: void EquityTradingScreen::on_order_submitted(const UnifiedOrder& order) { 317: if (focused_account_id_.isEmpty()) { 318: order_entry_->show_order_status("No account selected — add one via ACCOUNTS", false); 319: return; 320: } 321: 322: auto account = AccountManager::instance().get_account(focused_account_id_); 323: 324: if (account.trading_mode == "paper") { 325: const QString portfolio_id = account.paper_portfolio_id; 326: if (portfolio_id.isEmpty()) { 327: order_entry_->show_order_status("No paper portfolio for this account", false); 328: return; 329: } 330: 331: LOG_INFO(TAG, QString("Paper order: %1 %2 x%3 account=%4") 332: .arg(order.side == OrderSide::Buy ? "BUY" : "SELL") 333: .arg(order.symbol) 334: .arg(order.quantity) 335: .arg(account.display_name)); 336: 337: const QString side = order.side == OrderSide::Buy ? "buy" : "sell"; 338: const QString type = order_type_str(order.order_type); 339: std::optional price_opt; 340: if (order.order_type == OrderType::Limit || order.order_type == OrderType::StopLossLimit) { 341: price_opt = order.price; 342: } else if (order.order_type == OrderType::Market) { 343: const double ref = current_price_ > 0 ? current_price_ : (order.price > 0 ? order.price : 0.0); 344: if (ref > 0) 345: price_opt = ref; 346: } 347: std::optional stop_opt; 348: if (order.stop_price > 0) 349: stop_opt = order.stop_price; 350: 351: if (order.order_type == OrderType::Market && !price_opt) { 352: order_entry_->show_order_status("Price not available yet — wait for quotes to load", false); 353: return; 354: } 355: 356: try { 357: auto pt_order = pt_place_order(portfolio_id, order.symbol, side, type, order.quantity, price_opt, stop_opt); 358: 359: if (order.order_type == OrderType::Market) { 360: double fill_price = current_price_ > 0 ? current_price_ : (order.price > 0 ? order.price : 0.0); ========== on_account_changed definition location ========== src/screens/equity_trading/EquityTradingScreen_Handlers.cpp:73:void EquityTradingScreen::on_account_changed(const QString& account_id) { src/screens/equity_trading/EquityTradingScreen_Handlers.cpp:266: // so on_account_changed() would spawn ~11 QtConcurrent::run fetches that src/screens/equity_trading/EquityTradingScreen_Handlers.cpp:279: on_account_changed(accounts.first().account_id); src/screens/equity_trading/EquityTradingScreen_Handlers.cpp:289: // does not auto-focus — see comment there). on_account_changed() does its src/screens/equity_trading/EquityTradingScreen_Handlers.cpp:292: on_account_changed(account_id); src/screens/equity_trading/EquityTradingScreen.cpp:109: on_account_changed(acct); src/screens/equity_trading/EquityTradingScreen.cpp:350: on_account_changed(accounts.first().account_id); src/screens/equity_trading/EquityTradingScreen.cpp:494: on_account_changed(id); src/screens/equity_trading/EquityTradingScreen.cpp:568: on_account_changed(acct); src/screens/equity_trading/EquityTradingScreen.h:59: void on_account_changed(const QString& account_id); ========== EquityTradingScreen.h (FULL) ========== 1: #pragma once 2: // Equity Trading Screen — multi-account coordinator. 3: // Supports multiple broker accounts simultaneously via DataStreamManager. 4: // Each account has its own data stream (WS/polling), portfolio, and credentials. 5: 6: #include "core/symbol/IGroupLinked.h" 7: #include "screens/common/IStatefulScreen.h" 8: #include "screens/equity_trading/EquityTypes.h" 9: #include "trading/BrokerAccount.h" 10: #include "trading/TradingTypes.h" 11: 12: #include 13: #include 14: #include 15: #include 16: #include 17: #include 18: #include 19: #include 20: #include 21: 22: #include 23: 24: namespace fincept::screens::equity { 25: class EquityTickerBar; 26: class EquityWatchlist; 27: class EquityChart; 28: class EquityOrderEntry; 29: class EquityOrderBook; 30: class EquityBottomPanel; 31: } // namespace fincept::screens::equity 32: 33: namespace fincept::screens { 34: 35: class EquityTradingScreen : public QWidget, public IGroupLinked, public IStatefulScreen { 36: Q_OBJECT 37: Q_INTERFACES(fincept::IGroupLinked) 38: public: 39: explicit EquityTradingScreen(QWidget* parent = nullptr); 40: ~EquityTradingScreen(); 41: 42: // IGroupLinked — see WatchlistScreen for the propagation contract. 43: void set_group(SymbolGroup g) override { link_group_ = g; } 44: SymbolGroup group() const override { return link_group_; } 45: void on_group_symbol_changed(const SymbolRef& ref) override; 46: SymbolRef current_symbol() const override; 47: 48: // IStatefulScreen 49: QVariantMap save_state() const override; 50: void restore_state(const QVariantMap& state) override; 51: QString state_key() const override { return QStringLiteral("equity_trading"); } 52: 53: protected: 54: void showEvent(QShowEvent* event) override; 55: void hideEvent(QHideEvent* event) override; 56: 57: private slots: 58: // Multi-account slots 59: void on_account_changed(const QString& account_id); 60: void on_symbol_selected(const QString& symbol); 61: void on_mode_toggled(); 62: void on_accounts_clicked(); // opens AccountManagementDialog 63: void handle_token_expired(const QString& account_id); 64: void on_order_submitted(const trading::UnifiedOrder& order); 65: void on_cancel_order(const QString& order_id); 66: void on_ob_price_clicked(double price); 67: void on_import_holdings_requested(const QVector& holdings); 68: 69: void refresh_candles(); 70: void update_clock(); 71: 72: // DataStreamManager signal handlers (on-demand / one-shot — kept as legacy signals) 73: void on_stream_candles_fetched(const QString& account_id, const QVector& candles); 74: void on_stream_orderbook_fetched(const QString& account_id, 75: const QVector>& bids, 76: const QVector>& asks, 77: double spread, double spread_pct, 78: const QVector& bid_orders, 79: const QVector& ask_orders); 80: void on_stream_time_sales_fetched(const QString& account_id, const QVector& trades); 81: void on_stream_latest_trade_fetched(const QString& account_id, const trading::BrokerTrade& trade); 82: void on_stream_calendar_fetched(const QString& account_id, const QVector& days); 83: void on_stream_clock_fetched(const QString& account_id, const trading::MarketClock& clock); 84: 85: private: 86: void setup_ui(); 87: void setup_timers(); 88: void connect_data_stream_signals(); 89: void init_focused_account(); 90: void switch_symbol(const QString& symbol); 91: void update_account_menu(); 92: void update_connection_status(); 93: void async_modify_order(const QString& order_id, double qty, double price); 94: 95: // DataHub subscription helpers (D4 migration) 96: void hub_subscribe_streaming(); 97: void hub_unsubscribe_all(); 98: void hub_subscribe_quotes(); 99: QString broker_id_for_focused() const; 100: 101: // ── Command bar widgets ── 102: QPushButton* account_btn_ = nullptr; // shows focused account name 103: QMenu* account_menu_ = nullptr; // lists all active accounts 104: QLineEdit* symbol_input_ = nullptr; 105: QPushButton* mode_btn_ = nullptr; 106: QPushButton* accounts_btn_ = nullptr; // opens AccountManagementDialog 107: QLabel* exchange_label_ = nullptr; 108: QLabel* clock_label_ = nullptr; 109: QLabel* conn_label_ = nullptr; // aggregate connection status 110: 111: // ── Sub-widgets ── 112: equity::EquityTickerBar* ticker_bar_ = nullptr; 113: equity::EquityWatchlist* watchlist_ = nullptr; 114: equity::EquityChart* chart_ = nullptr; 115: equity::EquityOrderEntry* order_entry_ = nullptr; 116: equity::EquityOrderBook* orderbook_ = nullptr; 117: equity::EquityBottomPanel* bottom_panel_ = nullptr; 118: 119: // ── Timers (only UI-local timers remain; data timers are in AccountDataStream) ── 120: QTimer* clock_timer_ = nullptr; 121: QTimer* market_clock_timer_ = nullptr; 122: 123: // ── Multi-account state ── 124: QString focused_account_id_; // the account targeted by order entry + chart 125: QString selected_symbol_ = "RELIANCE"; 126: QString selected_exchange_ = "NSE"; 127: 128: // Paper trading (derived from focused account) 129: int fill_cb_id_ = -1; // OrderMatcher fill callback 130: 131: // Async guards 132: std::atomic token_expired_shown_{false}; 133: 134: QStringList watchlist_symbols_; 135: double current_price_ = 0.0; // last known LTP for selected symbol 136: 137: bool initialized_ = false; 138: bool hub_active_ = false; 139: QHash watchlist_quote_cache_; 140: 141: // Symbol group link — SymbolGroup::None when unlinked. 142: SymbolGroup link_group_ = SymbolGroup::None; 143: }; 144: 145: } // namespace fincept::screens ========== EquityTradingScreen.cpp constructor + connects (1-120) ========== 1: // Equity Trading Screen — multi-account coordinator. 2: // 3: // Core: ctor/dtor, show/hide events, setup_ui, setup_timers, update_clock, 4: // connect_data_stream_signals, init_focused_account, update_account_menu, 5: // update_connection_status, on_group_symbol_changed, current_symbol. Split 6: // concerns: 7: // - EquityTradingScreen_Streams.cpp — on_stream_* slots from DataStreamManager 8: // - EquityTradingScreen_Handlers.cpp — user-action handlers 9: // - EquityTradingScreen_Holdings.cpp — broker→portfolio holdings import 10: #include "screens/equity_trading/EquityTradingScreen.h" 11: 12: #include "core/logging/Logger.h" 13: #include "core/session/ScreenStateManager.h" 14: #include "core/symbol/SymbolContext.h" 15: #include "core/symbol/SymbolDragSource.h" 16: 17: #include 18: #include 19: #include "screens/equity_trading/AccountManagementDialog.h" 20: #include "screens/equity_trading/BroadcastOrderDialog.h" 21: #include "screens/equity_trading/EquityBottomPanel.h" 22: #include "screens/equity_trading/EquityChart.h" 23: #include "screens/equity_trading/EquityOrderBook.h" 24: #include "screens/equity_trading/EquityOrderEntry.h" 25: #include "screens/equity_trading/EquityTickerBar.h" 26: #include "screens/equity_trading/EquityWatchlist.h" 27: #include "services/portfolio/PortfolioService.h" 28: #include "storage/repositories/SettingsRepository.h" 29: #include "trading/AccountManager.h" 30: #include "trading/BrokerRegistry.h" 31: #include "trading/DataStreamManager.h" 32: #include "trading/OrderMatcher.h" 33: #include "trading/PaperTrading.h" 34: #include "trading/UnifiedTrading.h" 35: #include "ui/theme/StyleSheets.h" 36: #include "ui/theme/Theme.h" 37: 38: #include 39: #include 40: #include 41: #include 42: #include 43: #include 44: #include 45: #include 46: #include 47: #include 48: #include 49: #include 50: #include 51: #include 52: #include 53: #include 54: #include 55: #include 56: #include 57: #include 58: #include 59: #include 60: #include 61: #include 62: 63: #include 64: 65: #include 66: 67: 68: namespace fincept::screens { 69: 70: using namespace fincept::trading; 71: using namespace fincept::screens::equity; 72: 73: static const QString TAG = "EquityTrading"; 74: 75: EquityTradingScreen::EquityTradingScreen(QWidget* parent) : QWidget(parent) { 76: watchlist_symbols_ = DEFAULT_WATCHLIST; 77: setup_ui(); 78: setup_timers(); 79: connect_data_stream_signals(); 80: 81: // Accept symbol drops anywhere on the screen — route through the 82: // existing selection path so sub-panels resync and the linked group is 83: // published to. 84: symbol_dnd::installDropFilter(this, [this](const SymbolRef& ref, SymbolGroup) { 85: if (ref.is_valid()) 86: on_symbol_selected(ref.symbol); 87: }); 88: } 89: 90: EquityTradingScreen::~EquityTradingScreen() { 91: if (fill_cb_id_ >= 0) 92: OrderMatcher::instance().remove_fill_callback(fill_cb_id_); 93: } 94: 95: // ============================================================================ 96: // Visibility — start/stop data streams (P3) 97: // ============================================================================ 98: 99: void EquityTradingScreen::showEvent(QShowEvent* event) { 100: QWidget::showEvent(event); 101: 102: // Restore persisted session state on first show 103: if (!initialized_) { 104: const auto s = ScreenStateManager::instance().load_direct("equity_trading"); 105: if (!s.isEmpty()) { 106: const QString acct = s.value("focused_account_id").toString(); 107: const QString sym = s.value("selected_symbol", selected_symbol_).toString(); 108: if (!acct.isEmpty() && acct != focused_account_id_ && AccountManager::instance().has_account(acct)) 109: on_account_changed(acct); 110: if (sym != selected_symbol_) 111: on_symbol_selected(sym); 112: } 113: } 114: 115: // Start all active account data streams 116: DataStreamManager::instance().start_all_active(); 117: DataStreamManager::instance().resume_all(); 118: 119: // Hub subscriptions for streaming data (D4) 120: hub_subscribe_streaming(); ========== how market data refreshed: grep methods ========== 109: on_account_changed(acct); 323: stream->fetch_candles(selected_symbol_, tf); 341: stream->fetch_clock(); 350: on_account_changed(accounts.first().account_id); 370: connect(&dsm, &DataStreamManager::candles_fetched, 371: this, &EquityTradingScreen::on_stream_candles_fetched); 372: connect(&dsm, &DataStreamManager::orderbook_fetched, 373: this, &EquityTradingScreen::on_stream_orderbook_fetched); 374: connect(&dsm, &DataStreamManager::time_sales_fetched, 375: this, &EquityTradingScreen::on_stream_time_sales_fetched); 376: connect(&dsm, &DataStreamManager::latest_trade_fetched, 377: this, &EquityTradingScreen::on_stream_latest_trade_fetched); 378: connect(&dsm, &DataStreamManager::calendar_fetched, 379: this, &EquityTradingScreen::on_stream_calendar_fetched); 380: connect(&dsm, &DataStreamManager::clock_fetched, 381: this, &EquityTradingScreen::on_stream_clock_fetched); 403: auto* broker = BrokerRegistry::instance().get(account.broker_id); 407: order_entry_->set_broker_id(account.broker_id); 408: watchlist_->set_broker_id(account.broker_id); 440: LOG_WARN(TAG, "Exception refreshing paper portfolio on fill"); 464: stream->fetch_candles(selected_symbol_, chart_->current_timeframe()); 465: stream->fetch_orderbook(selected_symbol_); 466: stream->fetch_time_sales(selected_symbol_); 467: stream->fetch_calendar(); 468: stream->fetch_clock(); 490: auto* broker = BrokerRegistry::instance().get(acct.broker_id); 491: const QString broker_name = broker ? broker->profile().display_name : acct.broker_id; 494: on_account_changed(id); 568: on_account_changed(acct);