Files
2026-06-17 17:12:19 -04:00

114 lines
3.8 KiB
Python

"""OANDA v20 historical candle downloader.
The access token is read from configuration (the ``OANDA_ACCESS_TOKEN``
environment variable) rather than being hard-coded.
"""
import time
from datetime import datetime, timedelta
import pandas as pd
import oandapyV20
import oandapyV20.endpoints.instruments as instruments
from oandapyV20.exceptions import V20Error
from tradingbot.config import OANDA_ACCESS_TOKEN
class OandaDataConnector:
def __init__(self, access_token):
self.client = oandapyV20.API(access_token=access_token)
self.last_request_time = 0
self.request_limit_delay = 0.01 # 100 requests per second max
def _respect_rate_limit(self):
current_time = time.time()
time_passed = current_time - self.last_request_time
if time_passed < self.request_limit_delay:
time.sleep(self.request_limit_delay - time_passed)
self.last_request_time = time.time()
def get_historical_data_chunked(self, instrument, timeframe, start_date, end_date=None, chunk_days=5):
if end_date is None:
end_date = datetime.now()
all_candles = []
current_date = start_date
while current_date < end_date:
self._respect_rate_limit()
chunk_end = min(current_date + timedelta(days=chunk_days), end_date)
params = {
"from": current_date.strftime('%Y-%m-%dT%H:%M:%SZ'),
"to": chunk_end.strftime('%Y-%m-%dT%H:%M:%SZ'),
"granularity": timeframe,
"price": "MBA" # Mid, Bid, Ask prices
}
try:
r = instruments.InstrumentsCandles(instrument=instrument, params=params)
self.client.request(r)
for candle in r.response['candles']:
if candle['complete']:
all_candles.append({
'timestamp': candle['time'],
'open': float(candle['mid']['o']),
'high': float(candle['mid']['h']),
'low': float(candle['mid']['l']),
'close': float(candle['mid']['c']),
'volume': float(candle['volume'])
})
print(f"Downloaded data from {current_date.date()} to {chunk_end.date()}")
current_date = chunk_end
except V20Error as e:
print(f"Error fetching data: {e}")
return None
df = pd.DataFrame(all_candles)
if not df.empty:
df['timestamp'] = pd.to_datetime(df['timestamp'])
df.set_index('timestamp', inplace=True)
df = df.sort_index()
return df
def download(instrument="EUR_USD", timeframe="M5", lookback_days=1825,
access_token=None, out_dir="."):
"""Download ``lookback_days`` of candles and save them to a CSV.
Returns the downloaded DataFrame (or ``None`` on failure).
"""
token = access_token or OANDA_ACCESS_TOKEN
if not token:
raise ValueError(
"No OANDA access token. Set the OANDA_ACCESS_TOKEN environment "
"variable or pass access_token=..."
)
connector = OandaDataConnector(token)
start_date = datetime.now() - timedelta(days=lookback_days)
data = connector.get_historical_data_chunked(
instrument=instrument,
timeframe=timeframe,
start_date=start_date,
)
if data is not None:
name = instrument.replace("_", "")
filename = (
f"{out_dir}/{name}_{timeframe}_"
f"{start_date.strftime('%Y%m%d')}_to_{datetime.now().strftime('%Y%m%d')}.csv"
)
data.to_csv(filename)
print(f"Data saved to {filename}")
print(f"Total candles downloaded: {len(data)}")
return data
if __name__ == "__main__":
download()