@DalCache
Thank you for your post about “old” or “stale” data when fetching one-minute bars. This is a common question. Alpaca data is always up to date, and the reason you are fetching the data you are is your choice of parameters.
I found what I believe are your bar requests from 2026-06-12 in the Alpaca logs. See an example below:
{
"timestamp":"2026-06-12T17:26:23.570Z",
"status_code":200,
"ip":"47.185.186.123",
"method":"GET",
"path":"/v2/stocks/bars",
"query":"limit=200&timeframe=1Min&feed=sip&symbols=CUPR",
"user_agent":"APCA-PY/0.43.2",
}
The issue is that you did not include a start, end, or sort parameter, but you did always include a limit parameter in your StockBarsRequest.
By not including a start or end parameter, the API uses default settings. The start parameter defaults to 00:00 ET of the current date. The end parameter defaults to the most recent time for which data can be accessed with the user’s market data subscription. This is the current time if one has an Algo Trader Plus market data subscription, and 15 minutes before the current time if not.
By omitting a sort parameter, the API defaults to ascending sort and therefore returns the first bars.
By including a limit parameter, the API will return no more than that many bars. Any bars after that are not returned. This can be problematic for two reasons. First, this is the maximum number of bars that will be returned, and there is no guarantee that this many will actually be returned. The second issue is that this will be the total bars returned, not the bars per symbol. If multiple symbols are requested, this will not return a fixed number of bars for each symbol as probably intended. The primary use of the limit parameter is to page results into manageable-sized chunks. It’s recommended not to use this parameter for other purposes.
The request in the example above does not include a start, end, or sort parameter, but does include a limit parameter of 200. Therefore, it returned the first 200 minute bars of the day, which were from 2026-06-12 04:07 to 2026-06-12 12:14. The data is not ‘stale’. The request simply isn’t fetching enough bars.
You should also note that bars are only created if there are valid trades during the bar. See the documentation here for how bars are aggregated. There can be Instances where bars are “missing”. For example, you could fetch all bars up to 13:26 ET, and the last bar could be 13:20 ET. This can happen if there were no valid trades, and therefore no bars created between 13:21 and 13:26. This can also happen in the middle of the data. There can potentially always be “missing” bars.
If you are calculating signals such as VWAP, EMA9, EMA20, or MACD and they require contiguous timestamps without “missing” bars, you will need to do a bit of cleanup after fetching the bar data. If you want the most current 200 minutes’ worth of bars, follow these steps.
- Add a bar for the most current minute if that minute does not exist
- Resample the dataframe to add any missing bars
- Select the most current 200 bars for each symbol
You will also need to decide how to fill in any missing bars. The most common approach is to forward-fill any missing bars, but you could also leave them as NaN if your signals can handle NaN values.
Below is some example code that ensures the most recent bar and forward fills any “missing” bars. By default, the resample method effectively deletes any duplicate indexes, so if the current minute exists, the new one will be ignored.
import pandas as pd
!pip install -q alpaca-py
from alpaca.data import StockHistoricalDataClient
from alpaca.data.requests import StockBarsRequest
from alpaca.data.timeframe import TimeFrame
from alpaca.data.requests import DataFeed
# instantiate a data client
ALPACA_API_KEY = 'xxxxx'
ALPACA_API_SECRET_KEY = 'xxxxx'
data_client = StockHistoricalDataClient(ALPACA_API_KEY, ALPACA_API_SECRET_KEY)
# fetch all the minute-bars for the current day
# omit the end parameter to get the most recent bars
# finally convert to a dataframe with the market time zone
symbols = ['CUPR', 'IBM']
start_of_current_day = pd.Timestamp.now(tz='America/New_York').floor('D')
bars = data_client.get_stock_bars(StockBarsRequest(
symbol_or_symbols=symbols,
start=start_of_current_day,
timeframe=TimeFrame.Minute,
feed=DataFeed.SIP,
)).df.tz_convert('America/New_York', level='timestamp')
# add a bar for the most current minute
# create a MultiIndex and empty dataframe as the most current minute bars
current_minute = pd.Timestamp.now(tz='America/New_York').floor('min')
symbols_for_current_bar = bars.index.get_level_values('symbol').unique()
current_index = pd.MultiIndex.from_product([symbols_for_current_bar, [current_minute]], names=['symbol', 'timestamp'])
current_row_df = pd.DataFrame(index=new_index, columns=bars.columns)
bars_with_current_minute = pd.concat([bars, current_row_df]).sort_index()
# Resample, forward fill, and select the last n bars per symbol
BARS_PER_SYMBOL = 200
updated_bars = (bars_with_current_minute
.groupby('symbol').resample('1min', level='timestamp').last()
.groupby('symbol').ffill()
.groupby('symbol').tail(BARS_PER_SYMBOL)
)
Hope that explains what you are seeing. The Alpaca data is always available within about 20ms from the end of each minute and is never “stale”.