pandas 如何正确地对来自www.example.com API的结果进行分页polygon.io?

jtw3ybtb  于 2023-03-11  发布在  Go
关注(0)|答案(1)|浏览(95)

我尝试使用polygon.io下载股票代码两个日期之间的所有分钟条。根据他们的API,API限制为50000个结果。
从他们的Github问题中,我发现了以下comment
聚合端点没有用于分页的next_url。相反,如果响应中的消息超过50,000条,则需要查询较小时间范围的数据。我建议每次查询查询1个月的分钟条。
这是我目前所做的:
返回符号列表:

from polygon import RESTClient
import os.path
from IPython.display import display
import pandas as pd

key = ''
all_tickers = []
df_list = []
final_df = []
from_ = '2021-05-01'
to = '2022-12-01'

def get_tickers():
    
    with RESTClient(key) as client:
        next_url = None
        while True:
            if next_url is None:
                tickers = client.reference_tickers_v3(type="CS")
            else:
                tickers = client._handle_response("ReferenceTickersV3ApiResponse", next_url, {})
            all_tickers.extend(tickers.results)
            if hasattr(tickers, 'next_url'):
                next_url = tickers.next_url
            else:
                break
    

file_name = 'tickers.csv'
if not os.path.exists(file_name):
    get_tickers()

    all_tickers_copy = pd.DataFrame(all_tickers)
    all_tickers_copy.to_csv(file_name, index=False)
else:
    all_tickers = pd.read_csv(file_name)
    all_tickers = all_tickers['ticker']

返回一个列表,其中包含from_to日期之间月份的开始和结束日期:

import pandas as pd

start_date, end_date = from_, to
dtrange = pd.date_range(start=start_date, end=end_date, freq='d')
months = pd.Series(dtrange .month)

starts, ends = months.ne(months.shift(1)), months.ne(months.shift(-1))
df = pd.DataFrame({'month_starting_date': dtrange[starts].strftime('%Y-%m-%d'),
                   'month_ending_date': dtrange[ends].strftime('%Y-%m-%d')})

# as a list of lists:
months = [df.columns.values.tolist()] + df.values.tolist()
months = pd.DataFrame(months)

然后,我有一个函数循环遍历我的符号,并在from_to之间的每个月发出API请求:

def get_daily_agg(from_, to, ticker):
    with RESTClient(key) as client:
        folder_name = 'intraday_bars_gapped_new'
        final_df = pd.DataFrame([])

        try:
            # skip the header and loop through the rows
            for index, row in months[1:].iterrows():
                # save the start and end dates as variables
                from_ = row[0]
                to = row[1]
                print(f'{to} and {from_}')
                r = client.stocks_equities_aggregates(ticker, 1, "minute", from_, to, unadjusted=False, limit='50000')
                print(f'downloading {ticker} from {from_} to {to}')
                df = pd.DataFrame(r.results)
                df = df[['t','v','o','c','h','l', 'vw']]
                df.columns = ['datetime', 'volume','open','close','high', 'low', 'vwap']
                df['datetime'] = pd.to_datetime(df['datetime'],unit='ms')
                df['time'] = df['datetime'].dt.strftime("%H:%M:%S")
                df['date'] = df['datetime'].dt.strftime("%Y-%m-%d")

                final_df.append(df)

        except:
            print(f'nothing found for {ticker} from {from_} to {to}')
            pass

            
    if not os.path.exists(folder_name):
        os.makedirs(folder_name)
        final_df.to_csv('{}/{}.csv'.format(folder_name, ticker), index=False)
    else:
        final_df.to_csv('{}/{}.csv'.format(folder_name, ticker), index=False)

import glob
from pathlib import Path

folder = "daily_bars_filtered/*.csv"
for fname in glob.glob(folder)[:20]:
    ticker = Path(fname).stem
    get_daily_agg(from_, to, ticker)

我的问题是-如何正确分页的结果从polygon.ioAPI?

ttcibm8c

ttcibm8c1#

我认为有两种方法可以处理www.example.com Python RestAPI中缺少“next_url”的问题polygon.io:

按日期范围划分的多个请求

正如你已经注意到的,你可以把你的请求分成不同的日期范围,这样每个请求返回的结果就少于50,000个。这种方法的主要问题是,与使用“next_url”光标相比,它会大大增加你需要发出的请求的数量,特别是考虑到大多数polygon.io帐户都受到他们在给定时间范围内可以发出的请求数量的限制。每月或每两个月提出请求的解决方案是最容易实施的。

from polygon import RESTClient
from urllib3 import HTTPResponse
from datetime import date
from dateutil.rrule import rrule, MONTHLY

client = RESTClient(api_key='<your_api_key>')

ticker='<your_ticker>'

start_date = date(2021, 3, 1)
end_date = date(2023, 2, 28)

for d in rrule(freq=MONTHLY, dtstart=start_date, until=end_date):
    from_date = d.strftime("%Y-%m")+"-01"
    to_date = d.strftime("%Y-%m")+f"-{calendar.monthrange(d.year, d.month)[1]}"
    
    bars = cast(
        HTTPResponse,
        client.get_aggs(
            ticker=ticker, 
            multiplier=1, 
            timespan="minute", 
            from_=from_date, 
            to=to_date,
            limit=50000,
            raw = True),
            )

    <operate on the return data>

一次处理两个月所需的请求数量可能会减少一半。快速查找过去两年的数据,某个月内某个股票的分钟数聚合的最大数据集是股票AAPL,它在2022-03-01到2022-03-31之间有18,956条记录。因此我认为您可以安全地一次收集两个月的数据,并且仍然低于50,000条记录的限制,因此如果您像我上面所做的那样使用rrule,请考虑将interval参数设置为2(2个月)或您正在使用的库的任何等效参数。
但即使一次做两个月,这种方法也是非常低效的。有一些股票代码,你可以在不到3个请求的情况下获得整个2年的数据,但即使一次做两个月,也需要6个请求。

对“next_url”使用普通HTTP请求,而不是面Python库

从您的多边形RESTClient客户端返回的数据。get_aggs请求实际上包括'next_url',只是多边形RESTClient库没有任何功能来检索其值或基于该值进行后续请求。然而,我们可以解析该数据以收集'next_url',然后对该URL进行正常HTTP请求以检索额外数据。
与之前一样,您可以使用多边形客户端的client.get_aggs以常规方式获取第一个记录集合:

bars = cast(
    HTTPResponse,
    client.get_aggs(
        ticker=ticker, 
        multiplier=1, 
        timespan="minute", 
        from_=start_date, 
        to=end_date,
        limit=50000,
        raw = True),
    )
    
    <Operate on the returned data>

然后,解析返回的数据以检索“next_url”,如果存在,则该URL将是数据中的最后一个字符串。有几种方法可以执行此操作,但性能各不相同。
最有吸引力的方法是使用BeautifulSoup这样的JSON解析器并检索'next_url',但这可能效率不高,因为它会解析整个数据,而我们只需要最后一个String。
更快但更难看的是,我们可以从末尾向后迭代数据的字符以检索最后一个字符串,反向以使其恢复正常,并检查它是否包含游标。
如果存在游标,则数据中的最后一个字符串(next_url)将采用以下格式:“https://api.polygon.io/v2/aggs/ticker/the_ticker/range/1/minute/unformatted日期 / 格式化日期 ?游标= 游标ID
如果没有游标,则数据中的最后一个字符串将为“count”
所以我们可以这样做:

def get_cursor(bars):
    bars_str = str(bars.data)
    info = ""

    for index in range(4, 200):
        character = bars_str[-index]
        if character == '"':
            break
        else:
            info += str(character)

    info = info[::-1]

    if "cursor" in info:
        return info

我知道那样会更优雅,但你明白我的意思。
然后,可以使用普通HTTP请求从该游标获取数据,并对每个包含游标的结果重复此操作。
请记住将API_key附加到请求中。您只需将“&apiKey="+附加<your_api_key>到URL中即可。
比如:

from urllib3 import HTTPResponse, PoolManager

process_next_url(bars, api_key):
    cursor = get_cursor(bars)

    http = PoolManager()

    while cursor is not None:
        bars = http_pool_manager.request('GET', cursor+"&apiKey="+api_key)

        <Operate on bars.data>

        cursor = get_cursor(bars)

当使用普通HTTP请求而不是多边形RESTClient库时,需要记住的一点是,当请求失败时,它不会引发与多边形库相同类型的Python异常。

考虑对www.example.com请求执行try/catchpolygon.io,并在超过每分钟请求数限制时进行处理

您的请求可能会出现许多问题,其中许多问题可以通过重试来解决。例如,您可以考虑将http/API请求放入范围为n的for循环中,然后重试由于某种原因失败的请求。例如,polygon.io帐户有请求/分钟限制,请求将返回一些错误,如“...超过了每分钟的最大请求数...”“您可以通过等待time.sleep()然后重试来自动解决。404找不到有时可能是类似的。

相关问题