Skip to content

coinprices/LocalPriceManager.hpp

Namespaces

Name
sgns

Classes

Name
class sgns::LocalPriceManager

Source code

#pragma once

#include "IPriceSource.hpp"
#include "PriceFetchError.hpp"
#include "PriceQuote.hpp"

#include "boost/asio.hpp"

#include "base/logger.hpp"

#include <chrono>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <thread>
#include <utility>
#include <vector>

namespace sgns
{
    class LocalPriceManager
    {
    public:
        using Clock = std::function<std::chrono::system_clock::time_point()>;

        using Strand = boost::asio::strand<boost::asio::io_context::executor_type>;

        LocalPriceManager( std::shared_ptr<IPriceSource> coinGeckoTier,
                           std::shared_ptr<IPriceSource> gnusServiceTier,
                           std::chrono::milliseconds      coalescingWindow = std::chrono::milliseconds( 50 ),
                           Clock                          now              = [] { return std::chrono::system_clock::now(); } );

        ~LocalPriceManager();

        LocalPriceManager( const LocalPriceManager & )            = delete;
        LocalPriceManager &operator=( const LocalPriceManager & ) = delete;

        PriceResult<std::vector<PriceQuote>> GetQuotes( const std::vector<std::string> &ids,
                                                        const std::string              &currency = "usd" );

    private:
        struct PendingWaiter
        {
            std::vector<std::string>                                 requestedIds; // full original request
            std::vector<PriceQuote>                                  immediate;    // fresh-L1 subset (D-06)
            std::promise<PriceResult<std::vector<PriceQuote>>>       done;
        };

        struct PendingWindow
        {
            std::set<std::string>                          ids;
            std::vector<PendingWaiter>                     waiters;
            std::unique_ptr<boost::asio::steady_timer>     timer;
        };

        void HandleRequestOnStrand( const std::vector<std::string>                     &ids,
                                    const std::string                                  &currency,
                                    std::promise<PriceResult<std::vector<PriceQuote>>> done );

        void DispatchBatchOnStrand( const std::string &currency );

        void StoreInL1( const std::vector<PriceQuote> &quotes );

        // Declaration order is load-bearing (reverse destruction: timers and
        // state maps die before the io_context; the thread is joined in the
        // dtor body BEFORE any member dies): ioc_ FIRST, thread_ LAST
        // (P-1: the HttpStubServer member-layout precedent).
        std::shared_ptr<boost::asio::io_context> ioc_;
        std::unique_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_;
        Strand                                   strand_;
        std::shared_ptr<IPriceSource>            coinGeckoTier_;
        std::shared_ptr<IPriceSource>            gnusServiceTier_;
        std::chrono::milliseconds                coalescingWindow_;
        Clock                                    now_;

        std::map<std::string, std::map<std::string, PriceQuote>> cache_;

        std::map<std::string, PendingWindow> windows_;

        base::Logger m_logger = sgns::base::createLogger( "LocalPriceManager" );
        std::thread  thread_;
    };
} // namespace sgns

Updated on 2026-10-09 at 04:02:50 +0000