Skip to content

quantpylib.datapoller

quantpylib.datapoller is a quant module used for multi-asset, multi-source/vendor/exchange financial data retrieval.

The datapollers are categorized into their data-classes, with current support for:

- crypto
- currencies 
- equities
- exchange
- metadata

The datasources supported are:

- binance
- eodhd
- oanda
- phemex
- hyperliquid (whyperliquid)
- yfinance

In general, a trader may have requirements for different asset-class/categories of financial data, subscriptions to multiple data sources, with each data source supporting a subset of the asset-class universe. Each data source has its own endpoints, parameters, rate-restrictions and authentication flow - the datapoller abstracts these concerns to provide a seamless interface for data retrieval while attempting to maximize throughput.

Each datapoller is derived from the base quantpylib.datapoller.base.BasePoller class and implements a set of methods, and routs the requests to the specified data source and endpoints. The supported data sources are written as wrappers on the quantpylib.wrappers module, and their supported endpoints share the same function name as in the datapollers. The different datapollers are accessible via the master data polling class, given in quantpylib.datapoller.master.Datapoller object instance. For instance, if we would like to get trade bars using the quantpylib.datapoller.currencies.Currencies.get_trade_bars, we can do so by creating a obj = DataPoller(...) instance, and calling obj.currencies.get_trade_bars(...,src="oanda"). The function call is only valid if it is supported by both the datapoller and the datasource, which we can check by their matching function signatures. In general each datasource has its own parameters to the vendor-specific endpoint, as well as different specifications (such as start time of OHLCV historical request in YYYY-MM-DD vs unix-timestamp), so the library provides a unified standard that convert between these specifications. In additional, the datapoller take in flexible arguments to support the different configurations for each external API - it would alot easier to understand through the given examples.

Note that each of the datasource wrappers are also designed to be available for use as standalone Python-SDKs. Supported datapollers and datasources are added and updated frequently.

To understand the valid function signatures of each datapoller, we need to understand the function’s decorator-type, which is indicated @poller or @ts_poller in the documentation, and is defined in quantpylib.datapoller.utils module for polling or time-series polling respectively. The default arguments are supplied through these decorators, which also provide parameter-standardization and parameter-guessing for vendor/source-unique specifications. Source-specific arguments are documented in its corresponding data-wrapper.

We now walkthrough how to construct a valid datapolling request and construct valid parameters to our datapollers for the various datasources. Note that the various endpoints and their matching function name-signatures can be easily looked up in the search-bar (top right): alt text

We need more data sources, more data pollers and more data endpoints to support. If there is a particular functionality you would like documented or implemented, please feel free to reach out to me @ hangukquant@gmail.com or submit a Github issue. Cheers.

Walkthrough

Supposed we are interested in getting some OHLC(V) data for equities. From our quantpylib.wrappers library, we see that the datasources supported with equities data is eodhd and yfinance. Suppose we have no API access to eodhd, so we will make a request through the yfinance library. To see what endpoints are available in the equities datapoller, we can just look at the documentation page - the sidebar has a summary, which we see has a get_trade_bars method. The arguments are not specified, but it is tagged @ts_poller, which means the arguments to supply are given by decorators, documented here. The augmented arguments are ticker, start, end, periods granularity, granularity_multiplier and src. We shall provide ticker, start, periods and src to make our request, and let the rest settle to default arguments.

We already have the keys in our .env file in the current working directory, and we will make the following imports

import os 
from dotenv import load_dotenv
load_dotenv()
from datetime import datetime
from quantpylib.datapoller.master import DataPoller

keys = {
    "yfinance": True,
    "eodhd": os.getenv("EOD_KEY"),
    "binance": True,
    "phemex": True,
    "oanda": ("practice",os.getenv("OANDA_ACC"),os.getenv("OANDA_KEY")),
    "coinbase": False,
}
We just instantiate a master datapoller with our keys, access the equities get_trade_bars method and specify src="yfinance", like this:
datapoller = DataPoller(config_keys=keys)
df = datapoller.equities.get_trade_bars(
    ticker="AAPL",
    start=datetime(2000,1,1),
    periods=365,
    src="yfinance"
)
print(df)
This gives us a year-worth of data for the year 2000~2001:
                               open      high       low     close     volume
datetime                                                                    
2000-01-03 00:00:00-05:00  0.792742  0.850379  0.768648  0.846127  535796800
2000-01-04 00:00:00-05:00  0.818254  0.836206  0.764869  0.774790  512377600
2000-01-05 00:00:00-05:00  0.784238  0.835733  0.778569  0.786128  778321600
2000-01-06 00:00:00-05:00  0.802191  0.808805  0.718098  0.718098  767972800
2000-01-07 00:00:00-05:00  0.729436  0.763452  0.721878  0.752113  460734400
...                             ...       ...       ...       ...        ...
2000-12-22 00:00:00-05:00  0.213539  0.226768  0.213539  0.226768  318052000
2000-12-26 00:00:00-05:00  0.224878  0.226768  0.215429  0.222044  216815200
2000-12-27 00:00:00-05:00  0.216846  0.223933  0.214484  0.223933  325466400
2000-12-28 00:00:00-05:00  0.217319  0.225823  0.216374  0.223933  305177600
2000-12-29 00:00:00-05:00  0.222044  0.226768  0.219209  0.224878  630336000
Now if we were to specify end=datetime(2000,1,1) instead of start=datetime(2000,1,1), then we will have data from 1999~2000. Or we can just specify the start,end directly, which means we can exclude the periods parameter, like this:
df = datapoller.equities.get_trade_bars(
    ticker="AAPL",
    start=datetime(2000,1,1),
    end=datetime(2020,1,1),
    src="yfinance"
)
print(df)
to get
                                open       high        low      close     volume
datetime                                                                        
2000-01-03 00:00:00-05:00   0.792742   0.850379   0.768648   0.846127  535796800
2000-01-04 00:00:00-05:00   0.818254   0.836206   0.764869   0.774790  512377600
2000-01-05 00:00:00-05:00   0.784238   0.835734   0.778569   0.786128  778321600
2000-01-06 00:00:00-05:00   0.802191   0.808805   0.718097   0.718097  767972800
2000-01-07 00:00:00-05:00   0.729436   0.763452   0.721878   0.752113  460734400
...                              ...        ...        ...        ...        ...
2019-12-24 00:00:00-05:00  69.250147  69.298799  68.819601  69.147980   48478800
2019-12-26 00:00:00-05:00  69.281768  70.536927  69.252580  70.519897   93121200
2019-12-27 00:00:00-05:00  70.814237  71.507495  70.084495  70.493149  146266000
2019-12-30 00:00:00-05:00  70.410455  71.196148  69.379088  70.911545  144114400
2019-12-31 00:00:00-05:00  70.524783  71.436962  70.425051  71.429665  100805600
Under the hood, we are mapping the parameters to the quantpylib.wrappers.yfinance.YFinance.get_trade_bars method. You may use this as a standalone SDK, but the parameters are alot more rigid. With the master datapoller, we can provide any 2 of the 3 of start, end, and periods. But yfinance does not support too many endpoints, and is also not stable, albeit free. Suppose we have an API key for eodhd, we don't need to change much code:
df = datapoller.equities.get_trade_bars(
    ticker="AAPL",
    start=datetime(2000,1,1),
    end=datetime(2020,1,1),
    src="eodhd"
)
print(df)
                open      high       low     close  adjusted_close     volume
datetime                                                                     
2000-01-03  104.8768  112.5040  101.6848  111.9328          0.8461  535796800
2000-01-04  108.2480  110.6224  101.1920  102.5024          0.7748  512377600
2000-01-05  103.7456  110.5664  102.9952  104.0032          0.7862  778321600
2000-01-06  106.1200  107.0048   94.9984   94.9984          0.7181  767972800
2000-01-07   96.4992  101.0016   95.5024   99.5008          0.7521  460734400
...              ...       ...       ...       ...             ...        ...
2024-03-28  171.7500  172.2300  170.5100  171.4800        171.4800   65672700
2024-04-01  171.1900  171.2500  169.4800  170.0300        170.0300   46240500
2024-04-02  169.0800  169.3400  168.2300  168.8400        168.8400   49329500
2024-04-03  168.7900  170.6800  168.5800  169.6500        169.6500   47602100
2024-04-04  170.2900  171.9200  168.8200  168.8200        168.8200   53289969
Actually we don't even need src="eodhd", the equities datapoller has that as its default, and the @ts_poller decorator is capable of guessing some parameters. This of course routs the request to quantpylib.wrappers.eodhd.Eodhd.get_trade_bars. Different from the yfinance wrapper's get_trade_bars endpoint, there is an additional parameter exchange="US", that is specific to the eodhd REST API. They have parameters to identify which asset with the same ticker the client is referring to, but this nomenclature is unique to this particular vendor. This parameter is actually also guessed by the @ts_poller decorator, but if we know precisely what we want, we should pass it in as arguments, as we may guess wrongly - here is an example of AAPL on the US and Mexican exchanges:
df = datapoller.equities.get_trade_bars(
    ticker="AAPL",
    start=datetime(2000,1,1),
    end=datetime(2020,1,1),
    src="eodhd",
    exchange="US"
)
print(df)
df = datapoller.equities.get_trade_bars(
    ticker="AAPL",
    start=datetime(2000,1,1),
    end=datetime(2020,1,1),
    src="eodhd",
    exchange="MX"
)
print(df)
giving
                open      high       low     close  adjusted_close     volume
datetime                                                                     
2000-01-03  104.8768  112.5040  101.6848  111.9328          0.8461  535796800
2000-01-04  108.2480  110.6224  101.1920  102.5024          0.7748  512377600
2000-01-05  103.7456  110.5664  102.9952  104.0032          0.7862  778321600
2000-01-06  106.1200  107.0048   94.9984   94.9984          0.7181  767972800
2000-01-07   96.4992  101.0016   95.5024   99.5008          0.7521  460734400
...              ...       ...       ...       ...             ...        ...
2024-03-28  171.7500  172.2300  170.5100  171.4800        171.4800   65672700
2024-04-01  171.1900  171.2500  169.4800  170.0300        170.0300   46240500
2024-04-02  169.0800  169.3400  168.2300  168.8400        168.8400   49329500
2024-04-03  168.7900  170.6800  168.5800  169.6500        169.6500   47602100
2024-04-04  170.2900  171.9200  168.8200  168.8200        168.8200   53289969

[6102 rows x 6 columns]
                 open       high        low      close  adjusted_close   volume
datetime                                                                       
2004-12-20   704.4184   704.4184   694.5484   697.9980         11.0377   722401
2004-12-21   702.4780   704.4996   702.4780   703.8080         11.1296  1804322
2004-12-27   705.1688   705.1688   705.1688   705.1688         11.1511   140000
2005-01-05   738.7380   738.7380   738.7380   738.7380         11.6819    14560
2005-03-07   471.2496   471.2496   471.2496   471.2496         14.9041   266000
...               ...        ...        ...        ...             ...      ...
2024-03-27  2846.0000  2874.9900  2842.0000  2874.4299       2874.4299     1941
2024-04-01  2873.0000  2873.0000  2819.3500  2827.2100       2827.2100    16860
2024-04-02  2808.0000  2813.6900  2792.5000  2799.7800       2799.7800    23005
2024-04-03  2798.0000  2829.9900  2798.0000  2810.0000       2810.0000     7258
2024-04-04  2810.0000  2839.9900  2800.0000  2807.0300       2807.0300     3685
The best is no-guessing, where you specify the source-specific parameters explicitly. For example, this would raise Exception:
df = datapoller.equities.get_trade_bars(
    ticker="BTC-USD",
    start=datetime(2000,1,1),
    end=datetime(2020,1,1),
    src="eodhd"
)
but if you change the request using crypto poller
df = datapoller.crypto.get_trade_bars(
    ticker="BTC-USD",
    start=datetime(2000,1,1),
    end=datetime(2020,1,1),
    src="eodhd"
)
print(df)
we get
                    open          high           low         close  adjusted_close       volume
datetime                                                                                       
2010-07-17      0.049510      0.049510      0.049510      0.049510        0.049510            0
2010-07-18      0.049510      0.049510      0.049510      0.049510        0.049510            0
2010-07-19      0.085840      0.085840      0.085840      0.085840        0.085840            0
2010-07-20      0.080800      0.080800      0.080800      0.080800        0.080800            0
2010-07-21      0.074740      0.074740      0.074740      0.074740        0.074740            0
...                  ...           ...           ...           ...             ...          ...
2024-03-31  69647.779030  71377.779498  69624.868677  71333.647926    71333.647926  20050941373
2024-04-01  71333.484717  71342.091454  68110.696020  69702.146113    69702.146113  34873527352
2024-04-02  69705.024322  69708.381258  64586.594304  65446.974233    65446.974233  50705240709
2024-04-03  65446.671764  66914.322564  64559.899948  65980.808650    65980.808650  34488018367
2024-04-04  65975.696667  69291.254806  65113.796534  68508.841844    68508.841844  34439527442
The purpose of the @ts_poller adds a layer of complexity, but it's purpose is to both be extremely flexible in the arguments the methods takes in, to generalize to arbitrary data-sources, while still providing the simplest possible unified-interface at the user-level by routing to the correct wrapper's endpoint. In fact, if we knew how they were routed, some 'wrong' calls actually work:
df = datapoller.equities.get_trade_bars(
    ticker="BTC-USD",
    start=datetime(2000,1,1),
    end=datetime(2020,1,1),
    src="eodhd",
    exchange="CC"
)
this works because even though we are using the equities datapoller, we specified the CC exchange which maps to the crypto asset class in the eodhd vendor. Of course, we do not recommend such workarounds. Note that additional arguments to otherwise valid requests are simply ignored - this request is valid:
df = datapoller.crypto.get_trade_bars(
    ticker="BTC-USD",
    start=datetime(2000,1,1),
    end=datetime(2020,1,1),
    src="eodhd",
    gibberish="gibberish"
)
Let's make a request to a @poller method, such as the quantpylib.datapoller.equities.get_ticker_fundamentals. The @poller only asks for the src parameter, while get_ticker_fundamentals asks for a ticker parameter, so we can do
df=datapoller.equities.get_ticker_fundamentals(ticker="AAPL",src="eodhd")
that routs to the eodhd wrapper. Everything else is the same.

Safe-Throttling

As far as possible, when the specified endpoint specifies rate-limits, rapid submission of requests are sent-through to the data-vendor as quickly as possible, while respecting the rate-limits. This is done by using our custom semaphore-like logic, defined in the quantpylib.throttler.rate_semaphore. This is specific to each data-vendor, and each instance of the wrapper around our data-source has its own resource-pool. This design is to maximise throughput of user-requests, and is supported in both asynchronous and synchronous methods. All requests submitted to a specific datasource with throttle-support will be paced, with support for multi-threading or coroutines. For instance, suppose we do

import threading
import requests
try:
    def raw():
        response=requests.get(f"https://eodhd.com/api/fundamentals/AAPL.US?api_token={os.getenv('EOD_KEY')}&fmt=json")
        if response.status_code == 429: raise Exception("TooManyTooFast")
        else: print(response.status_code)
    req=lambda:datapoller.equities.get_ticker_fundamentals(ticker="AAPL")
    threads=[threading.Thread(target=raw,args=()) for i in range(500)]
    # req=lambda:datapoller.equities.get_ticker_fundamentals(ticker="AAPL")
    # threads=[threading.Thread(target=req,args=()) for i in range(500)]
    for thread in threads: thread.start()
    for thread in threads: thread.join()
except Exception as err:
    print(err)
The raw, multi-threaded request will eventually get rejected by the server with a 429 TooManyRequests code, while if we try:
# req=lambda:datapoller.equities.get_ticker_fundamentals(ticker="AAPL")
# threads=[threading.Thread(target=raw,args=()) for i in range(500)]
req=lambda:datapoller.equities.get_ticker_fundamentals(ticker="AAPL")
threads=[threading.Thread(target=req,args=()) for i in range(500)]
the application will gracefully pace the execution of multiple threads and not crash.

Examples

A number of examples are given. This would be the setup:

import os 
import asyncio
from dotenv import load_dotenv
load_dotenv()
from datetime import datetime
from quantpylib.datapoller.master import DataPoller

keys = {
    "yfinance": True,
    "eodhd": os.getenv("EOD_KEY"),
    "binance": True,
    "phemex": True,
    "oanda": ("practice",os.getenv("OANDA_ACC"),os.getenv("OANDA_KEY")),
    "coinbase": False,
}

datapoller = DataPoller(config_keys=keys)

async def main():
    pass #code here

if __name__ == "__main__":
    asyncio.run(main())

Get OHLCV

Let's get OHLCV for crypto, currencies and equities. We get AAPL, EUR_USD, BTCUSDT OHLCV data from the equities, currencies and crypto datapoller using source eodhd, oanda and binance respectively.

start,end=datetime(2020,1,1),datetime.now()
df1=datapoller.equities.get_trade_bars(ticker="AAPL",start=start,end=end,granularity="d",src="eodhd")
df2=datapoller.currencies.get_trade_bars(ticker="EUR_USD",start=start,end=end,granularity="d",src="oanda")
df3=datapoller.crypto.get_trade_bars(ticker="BTCUSDT",start=start,end=end,granularity="d",src="binance")
print(df1,df2,df3)
We can go for other data-granularities:
df=datapoller.currencies.get_trade_bars(ticker="EUR_USD",granularity="h",granularity_multiplier=4,start=start,end=end,src="oanda")
print(df)
and we get 4-hour candles:
                              open     high      low    close  volume
datetime                                                             
2020-01-01 22:00:00+00:00  1.12124  1.12246  1.12124  1.12209     673
2020-01-02 02:00:00+00:00  1.12206  1.12247  1.12010  1.12044     700
2020-01-02 06:00:00+00:00  1.12044  1.12140  1.12013  1.12032    2392
2020-01-02 10:00:00+00:00  1.12034  1.12039  1.11830  1.11966    2610
2020-01-02 14:00:00+00:00  1.11962  1.12030  1.11636  1.11700    4887
...                            ...      ...      ...      ...     ...
2024-04-04 17:00:00+00:00  1.08587  1.08620  1.08319  1.08374   13217
2024-04-04 21:00:00+00:00  1.08401  1.08441  1.08350  1.08416    9891
2024-04-05 01:00:00+00:00  1.08416  1.08431  1.08234  1.08264    7608
2024-04-05 05:00:00+00:00  1.08263  1.08464  1.08226  1.08376    9345
2024-04-05 09:00:00+00:00  1.08374  `1.08422  1.08350  1.08393    1436
The datapoller routs to the oanda wrapper, and because Oanda places a limit of candles per request, given your polling-period and granularity desired, there may be multiple requests to the Oanda REST API server to stitch together the non-overlapping periods. This is the advantage of our datapoller and wrapper interface - the user does not have to concern themselves with the source-specific limitations.

Get Tick Historical

Some endpoints support historical data. eodhd has support for tick data on the US-listed common-stocks.

ticks=datapoller.equities.get_trade_ticks(ticker="META",start=datetime(2023,4,3,0,0,0), end=datetime(2023,4,3,12,0,0))
print(ticks)
We get:
     ex mkt   price      seq  shares    sl sub_mkt             ts
0     Q   K  211.00    77001       5  @ TI          1680508800013
1     Q   K  211.42    77002       1  @ TI          1680508800013
2     Q   K  210.99    77124      10  @ TI          1680508800017
3     Q   K  211.42    77125       7  @ TI          1680508800017
4     Q   K  211.42    77127      30  @ TI          1680508800017
...  ..  ..     ...      ...     ...   ...     ...            ...
2071  Q   Q  210.00  3635409      59  @FTI          1680523180411
2072  Q   Q  210.00  3635410      41  @FTI          1680523180411
2073  Q   K  210.00  3636017     800  @FT           1680523181713
2074  Q   Q  210.00  3636650      20  @FTI          1680523184200
2075  Q   K  210.00  3637810      27  @FTI          1680523185877

[2076 rows x 8 columns]

Get Fundamentals

Some assets and datasources support fundamental data. We could get fundamental data for Goldman Sachs and Ethereum like this:

f1=datapoller.equities.get_ticker_fundamentals(ticker="GS",src="eodhd")
f2=datapoller.crypto.get_ticker_fundamentals(ticker="ETH-USD",src="eodhd")

Get Exchange Universe

We might also be interested in getting the listed set of tickers for a data vendor or exchange. In the case of eodhd, a data vendor, they support multiple exchanges. We can type in the search-bar (top right) for the endpoint get_tickers_in_exchange, and we see binance, eodhd and oanda support this endpoint through the exchange datapoller. We can see that the eodhd endpoint takes in an exchange parameter, so we can ask for their US-listed stocks as follows:

tickers = datapoller.exchange.get_tickers_in_exchange(exchange="US",src="eodhd")
On the other hand Binance is already an exchange, so looking at their endpoint, we see that no additional argument is required, so we can do the route by (same for oanda)
tickers = datapoller.exchange.get_tickers_in_exchange(src="binance")
tickers = datapoller.exchange.get_tickers_in_exchange(src="oanda")
Note that the return type from these endpoints are mostly not-standardized, and no have fixed schema, except for the get_trade_bars and common methods. This is a point for improvement that we are hoping contributors can come in, as we need the manpower!

Streaming Data

We are capable of streaming market data in an extremely simple interface. This is interfaced by an asynchronous method in the datapoller in question, and all streamed data is stored in the objects' stream_buffer attribute, which is a collections.defaultdict object with deque as the buffer for each streamed datapoint.

We can see that both eodhd and currencies support it, so we can make request:

await datapoller.currencies.stream_market_data(ticker="EURUSD",src="eodhd")
while True:
    print(datapoller.currencies.stream_buffer["EURUSD"])
    await asyncio.sleep(3)
and we can print the instance of the stream_buffer anytime we want, which maintains the current stream state. The length of stream_buffer is controlled by the object instantiation for any datapoller instance, see quantpylib.datapoller.base.BasePoller. The first two iterations give:
deque([], maxlen=1000000000)
deque([{'s': 'EURUSD', 'a': 1.0835, 'b': 1.0834, 'dc': '-0.0379', 'dd': '-0.0004', 'ppms': False, 't': 1712331637066}, {'s': 'EURUSD', 'a': 1.0835, 'b': 1.0834, 'dc': '-0.0378', 'dd': '-0.0004', 'ppms': False, 't': 1712331637140}, {'s': 'EURUSD', 'a': 1.0835, 'b': 1.0834, 'dc': '-0.0377', 'dd': '-0.0004', 'ppms': False, 't': 1712331637278}, 
    {'s': 'EURUSD', 'a': 1.0835, 'b': 1.0834, 'dc': '-0.0393', 'dd': '-0.0004', 'ppms': False, 't': 1712331637844}, {'s': 'EURUSD', 'a': 1.0835, 'b': 1.0834, 'dc': '-0.0383', 'dd': '-0.0004', 'ppms': False, 't': 1712331637948}, {'s': 'EURUSD', 'a': 1.0835, 'b': 1.0834, 'dc': '-0.0374', 'dd': '-0.0004', 'ppms': False, 't': 1712331638154}, 
    {'s': 'EURUSD', 'a': 1.0836, 'b': 1.0835, 'dc': '-0.0306', 'dd': '-0.0003', 'ppms': False, 't': 1712331638426}, {'s': 'EURUSD', 'a': 1.0835, 'b': 1.0835, 'dc': '-0.028', 'dd': '-0.0003', 'ppms': False, 't': 1712331638445}, {'s': 'EURUSD', 'a': 1.0835, 'b': 1.0835, 'dc': '-0.028', 'dd': '-0.0003', 'ppms': False, 't': 1712331638725}, {'s': 'EURUSD', 'a': 1.0835, 'b': 1.0835, 'dc': '-0.032', 'dd': '-0.0003', 'ppms': False, 't': 1712331638748}, {'s': 'EURUSD', 'a': 1.0835, 'b': 1.0835, 'dc': '-0.0283', 'dd': '-0.0003', 'ppms': False, 't': 1712331639048}], maxlen=1000000000)
It gets too large afterwards to display here, but we will see that each iteration contains more data. Of course, you can stream the same assets from different data sources, different assets from the same data source, and so on...all of these are valid:
await datapoller.currencies.stream_market_data(ticker="EURUSD",src="eodhd")
await datapoller.currencies.stream_market_data(ticker="EUR_USD",src="oanda")
await datapoller.equities.stream_market_data(ticker="AAPL",src="eodhd")
await datapoller.crypto.stream_market_data(ticker="BTC-USD",src="eodhd")
while True:
    print(datapoller.currencies.stream_buffer["EURUSD"])
    print(datapoller.currencies.stream_buffer["EUR_USD"])
    print(datapoller.equities.stream_buffer["AAPL"])
    print(datapoller.crypto.stream_buffer["BTC-USD"])
    await asyncio.sleep(3)
Once we are done, we can close all of the streams. Note that the await lines above are executed one-after-another, to call concurrently, we can use gather, which we demonstrate in using for closing the streams:
await asyncio.gather(
    *[
        datapoller.currencies.close_market_data_stream(ticker="EURUSD",src="eodhd"),
        datapoller.currencies.close_market_data_stream(ticker="EUR_USD",src="oanda"),
        datapoller.equities.close_market_data_stream(ticker="AAPL",src="eodhd"),
        datapoller.crypto.close_market_data_stream(ticker="BTC-USD",src="eodhd")
    ]
)

Queries

Some data vendors provide a functionality to input some string or alternatively formatted specification into their search engine. This is provided in the quantpylib.datapoller.metadata.query_engine method:

query = datapoller.metadata.query_engine(query="AAPL")
print(query)
gives us
      Code Exchange                                               Name          Type  ... Currency          ISIN previousClose  previousCloseDate
0     AAPL       US                                          Apple Inc  Common Stock  ...      USD  US0378331005      168.8200         2024-04-04
1     AAPL       BA                                      Apple Inc DRC  Common Stock  ...      ARS  US0378331005     8925.5000         2024-04-04
2     AAPL       MX                                          Apple Inc  Common Stock  ...      MXN  US0378331005     2807.0300         2024-04-04
3     AAPL      NEO                                      Apple Inc CDR  Common Stock  ...      CAD  CA03785Y1007       25.0400         2024-04-04
4     AAPL       SN                                          Apple Inc  Common Stock  ...      USD  US0378331005      170.5200         2024-04-04
5   AAPL34       SA                                          Apple Inc  Common Stock  ...      BRL  BRAAPLBDR004       42.8200         2024-04-04
6     AAPU       US  Direxion Shares ETF Trust - Direxion Daily AAP...           ETF  ...      USD  US25461A8743       21.9100         2024-04-04
7     AAPD       US  Direxion Shares ETF Trust - Direxion Daily AAP...           ETF  ...      USD  US25461A3041       23.0400         2024-04-04
8     APLY       US           YieldMax AAPL Option Income Strategy ETF           ETF  ...      USD  US88634T8577       16.5600         2024-04-04
9     3SAP       PA                                   Granite -3x AAPL           ETF  ...      EUR  XS2193970030       26.9500         2024-04-04
10    APLY      NEO              APPLE (AAPL) Yield Shares Purpose ETF           ETF  ...      CAD          None       22.7000         2024-04-04
11    3LAP       PA                                   Granite +3x AAPL           ETF  ...      EUR  XS2193969883       18.4260         2024-04-04
12    SALE      LSE  Leverage Shares 3x Short Apple (AAPL) ETP Secu...           ETF  ...      EUR  XS2472334742        2.9949         2024-04-04
13    3SAA    XETRA               Leverage Shares -3x Short Apple AAPL           ETF  ...      EUR  XS2472334742        2.9892         2024-04-04
14    AAPY       US       Kurv Yield Premium Strategy Apple (AAPL) ETF           ETF  ...      USD          None       23.3839         2024-04-04