Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simulation Redesign (Epic) #286

Open
8 tasks
chanhosuh opened this issue Dec 4, 2023 · 0 comments
Open
8 tasks

Simulation Redesign (Epic) #286

chanhosuh opened this issue Dec 4, 2023 · 0 comments
Assignees
Labels

Comments

@chanhosuh
Copy link
Member

chanhosuh commented Dec 4, 2023

Basic building blocks

Core

  • TimeSequence iterator: this lets us encompass different ways of tracking "time" that are useful for trading strategies on a blockchain. This could be timestamps, block times, block numbers, or any sequence of increasing items.

    • TimestampPeriod: should be one of the concrete instantiations, has a start, end, optionally a freq that lets us use unix timestamps.
  • SimAsset: at the minimum should have an id, but the base class might not need anything else. Child classes would include:

    • CurrencyPair
    • PoolPair
    • etc
  • SimAssetSeries: Pandas Series-like abstraction for a SimAsset's market data. Common indexing and slicing operations should be supported but it is important to minimize allowed operations to reduce complex interactions in the codebase. Note associated price data likely reflects venue-specific liquidity considerations or may be from maker or taker perspective. This means there should probably be "tags" indicating these classifications.

TODOs:

  • TimeSequence interface design
    • implementation for timestamp periods
  • SimAsset interface design
    • PoolPair implementation
  • SimAssetSeries interface design (low priority, as we can eventually sub this in)

Market Data Sourcing

  • DataSource: has one or more query methods that let us pull data from files, external APIs, or any combination.
    • Produces series from main components: SimAsset and TimeSequence
    • Signature for a query could be: def query(self, sim_asset: SimAsset, time_sequence: TimeSequence) -> SimAssetSeries
    • data sources could be composable or load from multiple sources

TODOs:

  • DataSource interface design
  • FileDataSource
  • ApiDataSource

Market Data Querying

  • ReferenceMarket: this is the fictionalized external venue used as a reference for trading strategies.

    • def prices(self, sim_assets: List[SimAsset], timestep) -> List[float]
    • This particular signature supposes:
      • an infinite-depth external venue, i.e. we can trade at any size
        at the given price without market impact.
      • the "orderbook" is symmetric, i.e. trade direction doesn't matter.
  • one concrete implementation we need for current pipelines will just be a wrapper around SimAssetSeries, but the general idea is to separate data sourcing from trading strategy queries. For example, we can have a reference market that may take in parameters to generate prices via Monte-Carlo or more theoretically-driven means. It is the reference market that gets injected into the Simulation container.

Pools/Venues

  • SimMarket: target venue for the trading strategy; SimPool interface has everything we need for now. This abstraction lets us consider arbitrage routes through multiple pools or even across DEXes and/or CEXes.

  • SimMarketFactory

IoC Container

Using a dependency injection configuration utility like Google's gin, we can imitate an "Inversion of Control" container a la Spring. This lets users simply modify configuration files to "rewire" the container and run a custom simulation.

Proposed IoC-like container for a simulation looks like this:

@gin.configurable
class SimulationContext:
    """
    Basically an 'IoC' container for the simulation application.

    While Gin doesn't require such a container object, this makes configuration
    more explicit.
    """

    # all constructor args will be Gin injected
    def __init__(
        self,
        time_sequence,
        reference_market,
        sim_market_factory,
        sim_market_parameters,
        trader_class,
        log_class,
        metric_classes,
    ):
        self.time_sequence = time_sequence
        self.reference_market = reference_market
        self.sim_market_factory = sim_market_factory
        self.sim_market_parameters = sim_market_parameters
        self.trader_class = trader_class
        self.log_class = log_class
        self.metric_classes = metric_classes

    def executor(
        self,
        sim_market,
    ):
        """
        Executes a trading strategy for the given sim market
        and time sequence.
        """
        # These all use Gin injection, completely separating
        # any need to consider constructor dependencies from
        # this logic.
        trader = self.trader_class()
        log = self.log_class()
        sim_market.prepare_for_run()

        for timestep in self.time_sequence:
            sim_market.prepare_for_trades(timestep)
            sample = self.reference_market.prices(timestep)
            trade_data = trader.process_time_sample(sample, sim_market)
            log.update(price_sample=sample, trade_data=trade_data)

        return log.compute_metrics()

    @property
    def configured_sim_markets(
        self,
    ):
        sim_market_parameters = self.sim_market_parameters
        sim_market_factory = self.sim_market_factory

        initial_params = sim_market_parameters.initial_parameters
        all_run_params = sim_market_parameters.run_parameters

        yield sim_market_factory.create(**initial_params)

        for run_params in all_run_params:
            init_kwargs = initial_params.copy()
            init_kwargs.update(run_params)
            sim_market = sim_market_factory.create(**init_kwargs)
            yield sim_market

    def run_simulation(
        self,
        ncpu=None,
    ):
        configured_sim_markets = self.configured_sim_markets
        initial_sim_market = next(configured_sim_markets)
        output = run_pipeline(configured_sim_markets, self.executor, ncpu)

        metrics = [Metric(pool=initial_sim_market) for Metric in self.metric_classes]
        results = make_results(*output, metrics)
        return results

It can then be run with:

gin.parse_config_file("config.gin")
sim_context = SimulationContext()
sim_context.run_simulation()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants