Postprocessing
Postprocessing directives provide a way to do additional postprocessing on the generated prices before they are returned to the user. These postprocessing directives may be stacked, meaning that the output of one can serve as the input into another. Below describes the supported postprocessing directives.
Arbitrage Removal
Detects and removes triangular arbitrage opportunities in a system of currency pairs.
Arbitrage is a financial strategy that involves buying a currency at a lower price and immediately selling it at a higher price in a different market, taking advantage of price discrepancies to make a profit.
This postprocessing directive operates by finding all currencies that can be directly exchanged from each currency, iterating over all combinations of these directly reachable currencies, and checking if there's an arbitrage opportunity. If such an opportunity is detected, the prices are modified to remove the opportunity.
remove_arbitrage_opportunity_for_single_timestep(prices)
This function takes a dictionary of price pairs as input. The keys of this dictionary are tuples (representing a pair of currencies).
The function returns an updated dictionary of prices with the first arbitrage opportunity that leads to the smallest overall percentage price change removed. If an arbitrage opportunity was detected and corrected, the function returns True; otherwise, it returns False.
Input
prices
: A dictionary where keys can be tuples or strings, and values are floats. Each key represents a currency pair or a single currency, and each value represents the price for that pair or currency.
Output
prices
: The updated dictionary of prices with the first arbitrage opportunity that leads to the smallest overall percentage price change removed.boolean
: True if an arbitrage opportunity was detected and corrected, False otherwise.
Algorithm
- Identify the pairs of currencies and individual currencies.
- For each currency, identify which other currencies it can be directly exchanged with.
- Iterate over all combinations of directly reachable currencies, and check if there's an arbitrage opportunity.
- If an arbitrage opportunity is found, calculate the ideal prices that would remove the opportunity.
- Keep track of the arbitrage opportunity that leads to the smallest overall percentage price change.
- Correct the prices for the identified arbitrage opportunity.
Complexity
The time complexity of this function is O(n^3)
, where n
is the number of currencies. This is because we are iterating over all combinations of directly reachable currencies for each currency.
The space complexity is O(n)
, where n
is also the number of currencies. This is because we store a dictionary of reachable currencies for each currency.
remove_arbitrage_opportunity(prices)
This function uses remove_arbitrage_opportunity_for_single_timestep()
to remove arbitrage opportunities for multiple timesteps. It returns a dictionary of the corrected prices and a list of boolean flags indicating whether an arbitrage opportunity was found at each timestep.
Input
prices
: A dictionary where keys are tuples representing currency pairs and values are lists of floats representing prices at each timestep.
Output
corrected_prices
: A dictionary where keys are the same as the input dictionary and values are lists of corrected prices at each timestep.arbitrage_flags
: A list of boolean flags indicating whether an arbitrage opportunity was found at each timestep.
remove_all_arbitrage_opportunities(prices)
This function iteratively calls remove_arbitrage_opportunity()
until no arbitrage opportunities are found in any timestep.
Input
prices
: A dictionary where keys are tuples representing currency pairs and values are lists of floats representing prices at each timestep.
Output
prices
: The same dictionary as the input, but with prices adjusted to remove all arbitrage opportunities.
Miscellaneous Design Decisions
All currency combinations are checked to find the arbitrage opportunity with the smallest percentage price change.
Caching is used to store the directly reachable currencies from each currency, reducing the time complexity.
Price Aggregation
The AggregationDirective
class transforms input price data based on a set of predefined steps. The class can produce either standard price lists or Open-High-Low-Close (OHLC) data.
Class Parameters
steps
(Dict[Union[AnyStr, SupportsInt], SupportsInt]): A dictionary defining the steps for the price aggregation.is_ohlc
(bool, optional): Flag to indicate if the data is OHLC data. Default to False.reverse
(bool, optional): Flag to indicate if the price data should be processed in reverse order. Default to False.
Class Methods
__init__(self, steps: Dict[Union[AnyStr, SupportsInt], SupportsInt], is_ohlc: bool = False, reverse: bool = False)
Initializes an instance of the AggregationDirective class.
execute(self, prices_array)
Processes an array of price lists and creates a corresponding aggregated list of prices for each list in the array based on the given steps.
is_reversed(self)
Returns True
if the price data is processed in reverse order. Otherwise, it returns False
.
get_ohlc(price_range, reverse=False)
Given a range of prices, it returns the open, high, low, and close prices as a list. If the reverse
parameter is True
, the function swaps the open and close prices.
Example Usage
import numpy as np
# Test the function when stepping forward (simulated prices)
_prices = np.array([i * 1.0 for i in range(250)])
_prices = _prices.reshape((1, 1, _prices.shape[0]))
_steps = {0: 1, 100: 10, 110: 1}
_is_ohlc = True
_directive = AggregationDirective(steps=_steps, is_ohlc=_is_ohlc, reverse=False)
_output = _directive.execute(_prices)
print(_output)
# Test the function when stepping backward (historical prices)
_directive = AggregationDirective(steps=_steps, is_ohlc=_is_ohlc, reverse=True)
_output = _directive.execute(_prices)
print(_output)
In the example above, we first create a list of ascending prices, reshape it to fit the expected input shape, define a step dictionary, and set the is_ohlc
flag to True
. Then, we instantiate the AggregationDirective
class and execute it on the list of prices. Finally, we print the result. We do this twice, once for ascending (simulated) prices and once for descending (historical) prices.
Rescaler
The RescalerDirective
class rescales the final prices while keeping the overall
simulated shape. The rescaling is done such that the starting price never changes but the
ending price is a targeted range based on the expected value. The expected value here is
defined as the 90% confidence interval of the price after t
time steps given the historical
standard deviation of price returns and the provided market drift. This is done for each
asset based on its configured market drift.
Class Parameters
market_drift_scale_factor
float: A value that scales the derivedmu
metric for the expected value. This value is provided by the user but defaults to1.0
. The formula ismu = market_drift * standard_deviation_of_returns * market_drift_scale_factor
.confidence_interval
float: A value that indicates the confidence interval to use for getting lower and upper bounds. This is set between0.0
and0.99
. For example, the default value of0.9
indicates to use the 90% confidence interval.rescale_per_step
bool: Defaults toFalse
. A value indicating whether to rescale the price for every step rather than the first step. Specifically, this derives the 90% confidence interval for each step rather than the last step and assigns the price from a random selection of this interval for every step. Note that this is effectively regenerating the full price series rather than rescaling it per se.index_to_token_mapping
Dict[int, AnyStr]: Provides a mapping of the index back to the asset. This is an implementation detail.