mirror of
https://github.com/Arianhgh/fx-quant-research.git
synced 2026-08-08 08:27:45 +00:00
114 lines
3.8 KiB
Python
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()
|