Backtesting.data

Data API module for retrieving stock prices from various sources.

AlpacaDataAPI

Bases: DataAPI

Alpaca Markets data API implementation with caching.

Source code in Backtesting/data.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class AlpacaDataAPI(DataAPI):
    """Alpaca Markets data API implementation with caching."""

    def __init__(self): 
        """Initialize Alpaca API client from environment variables."""
        load_dotenv(find_dotenv()) 
        self.api_key = os.getenv("ALPACA_API_KEY")
        self.secret_key = os.getenv("ALPACA_API_SECRET_KEY")
        self.connected = False
        self.initial_time = InitialConditions.get_initial_time()
        self.step_size = InitialConditions.get_time_step_size()
        self.data_client = StockHistoricalDataClient(self.api_key, self.secret_key) #alpaca connection
        self.price_cache = {}

    def get_price(self, stock: str, time_step: int) -> float:
        """Get stock price from Alpaca API with caching.

        Args:
            stock: Stock symbol.
            time_step: Time step index.

        Returns:
            Stock price as float. Falls back to previous cached price on error.
        """

        #checking cache
        key = (stock, time_step)
        if key in self.price_cache:
            return self.price_cache[key][0]

        #converting to alpaca format
        timestamp = self.initial_time + timedelta(seconds=self.step_size * time_step)
        timestamp = timestamp.astimezone(timezone.utc)
        start = timestamp
        end = (timestamp + timedelta(seconds = self.step_size))

        request = StockBarsRequest(
                symbol_or_symbols = stock,
                timeframe = TimeFrame.Day,
                start = start,
                end = end,
                limit = 1
        )

        try:

            bars = self.data_client.get_stock_bars(request) #StockHistoricalDataClient().get_stock_bars(stock, timeframe, start, limit)
            bar = bars[stock][0] #bars can be a multidimensional array since many symbols can be passed. we just use one though
            price = bar.close #price at close of interval
            self.price_cache[key] = (price, bar.timestamp)

            return price

        #connection error if client does not connect
        except Exception as e:
            print(f"could not retrieve price: {e}")
            self.price_cache[key] = self.price_cache[(stock, time_step - 1)] #default back to last cached price
            return self.price_cache[key][0]

    def is_connected(self) -> bool:
        """Check Alpaca API connection status."""
        response = requests.get(self.base_url, self.headers)
        if response.status_code == 200:
            self.connected = True
        return self.connected

__init__()

Initialize Alpaca API client from environment variables.

Source code in Backtesting/data.py
80
81
82
83
84
85
86
87
88
89
def __init__(self): 
    """Initialize Alpaca API client from environment variables."""
    load_dotenv(find_dotenv()) 
    self.api_key = os.getenv("ALPACA_API_KEY")
    self.secret_key = os.getenv("ALPACA_API_SECRET_KEY")
    self.connected = False
    self.initial_time = InitialConditions.get_initial_time()
    self.step_size = InitialConditions.get_time_step_size()
    self.data_client = StockHistoricalDataClient(self.api_key, self.secret_key) #alpaca connection
    self.price_cache = {}

get_price(stock, time_step)

Get stock price from Alpaca API with caching.

Parameters:
  • stock (str) –

    Stock symbol.

  • time_step (int) –

    Time step index.

Returns:
  • float

    Stock price as float. Falls back to previous cached price on error.

Source code in Backtesting/data.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def get_price(self, stock: str, time_step: int) -> float:
    """Get stock price from Alpaca API with caching.

    Args:
        stock: Stock symbol.
        time_step: Time step index.

    Returns:
        Stock price as float. Falls back to previous cached price on error.
    """

    #checking cache
    key = (stock, time_step)
    if key in self.price_cache:
        return self.price_cache[key][0]

    #converting to alpaca format
    timestamp = self.initial_time + timedelta(seconds=self.step_size * time_step)
    timestamp = timestamp.astimezone(timezone.utc)
    start = timestamp
    end = (timestamp + timedelta(seconds = self.step_size))

    request = StockBarsRequest(
            symbol_or_symbols = stock,
            timeframe = TimeFrame.Day,
            start = start,
            end = end,
            limit = 1
    )

    try:

        bars = self.data_client.get_stock_bars(request) #StockHistoricalDataClient().get_stock_bars(stock, timeframe, start, limit)
        bar = bars[stock][0] #bars can be a multidimensional array since many symbols can be passed. we just use one though
        price = bar.close #price at close of interval
        self.price_cache[key] = (price, bar.timestamp)

        return price

    #connection error if client does not connect
    except Exception as e:
        print(f"could not retrieve price: {e}")
        self.price_cache[key] = self.price_cache[(stock, time_step - 1)] #default back to last cached price
        return self.price_cache[key][0]

is_connected()

Check Alpaca API connection status.

Source code in Backtesting/data.py
136
137
138
139
140
141
def is_connected(self) -> bool:
    """Check Alpaca API connection status."""
    response = requests.get(self.base_url, self.headers)
    if response.status_code == 200:
        self.connected = True
    return self.connected

DataAPI

Bases: ABC

Abstract base class for data API implementations.

Source code in Backtesting/data.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class DataAPI(ABC):
    """Abstract base class for data API implementations."""

    @abstractmethod 
    def get_price(self, stock: str, time_step: int) -> float:
        """Get stock price at given time step.

        Args:
            stock: Stock symbol.
            time_step: Time step index.

        Returns:
            Stock price as float.
        """
        pass

    @abstractmethod
    def is_connected(self) -> bool:
        """Check if API connection is active.

        Returns:
            True if connected, False otherwise.
        """
        pass

get_price(stock, time_step) abstractmethod

Get stock price at given time step.

Parameters:
  • stock (str) –

    Stock symbol.

  • time_step (int) –

    Time step index.

Returns:
  • float

    Stock price as float.

Source code in Backtesting/data.py
17
18
19
20
21
22
23
24
25
26
27
28
@abstractmethod 
def get_price(self, stock: str, time_step: int) -> float:
    """Get stock price at given time step.

    Args:
        stock: Stock symbol.
        time_step: Time step index.

    Returns:
        Stock price as float.
    """
    pass

is_connected() abstractmethod

Check if API connection is active.

Returns:
  • bool

    True if connected, False otherwise.

Source code in Backtesting/data.py
30
31
32
33
34
35
36
37
@abstractmethod
def is_connected(self) -> bool:
    """Check if API connection is active.

    Returns:
        True if connected, False otherwise.
    """
    pass

PolygonDataAPI

Bases: DataAPI

Polygon.io data API implementation.

Source code in Backtesting/data.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class PolygonDataAPI(DataAPI):
    """Polygon.io data API implementation."""

    def __init__(self, api_key: str):
        """Initialize Polygon API client.

        Args:
            api_key: Polygon.io API key.
        """
        self.api_key = api_key
        self.connected = False
        self.initial_time = InitialConditions.get_initial_time()
        self.step_size = InitialConditions.get_time_step_size()

    def get_price(self, stock: str, time_step: int) -> float:
        """Get stock price from Polygon API.

        Args:
            stock: Stock symbol.
            time_step: Time step index.

        Returns:
            Stock price as float. Returns 0.0 on error.
        """
        #convert to polygon format
        try:
            timestamp = self.initial_time + (self.step_size * time_step)
            #TODO
            return 0.0
        except Exception as e:
            print(f"could not retrieve price: {e}")
            return 0.0

    def is_connected(self) -> bool:
        """Check Polygon API connection status."""
        #TODO
        return self.connected

__init__(api_key)

Initialize Polygon API client.

Parameters:
  • api_key (str) –

    Polygon.io API key.

Source code in Backtesting/data.py
42
43
44
45
46
47
48
49
50
51
def __init__(self, api_key: str):
    """Initialize Polygon API client.

    Args:
        api_key: Polygon.io API key.
    """
    self.api_key = api_key
    self.connected = False
    self.initial_time = InitialConditions.get_initial_time()
    self.step_size = InitialConditions.get_time_step_size()

get_price(stock, time_step)

Get stock price from Polygon API.

Parameters:
  • stock (str) –

    Stock symbol.

  • time_step (int) –

    Time step index.

Returns:
  • float

    Stock price as float. Returns 0.0 on error.

Source code in Backtesting/data.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def get_price(self, stock: str, time_step: int) -> float:
    """Get stock price from Polygon API.

    Args:
        stock: Stock symbol.
        time_step: Time step index.

    Returns:
        Stock price as float. Returns 0.0 on error.
    """
    #convert to polygon format
    try:
        timestamp = self.initial_time + (self.step_size * time_step)
        #TODO
        return 0.0
    except Exception as e:
        print(f"could not retrieve price: {e}")
        return 0.0

is_connected()

Check Polygon API connection status.

Source code in Backtesting/data.py
72
73
74
75
def is_connected(self) -> bool:
    """Check Polygon API connection status."""
    #TODO
    return self.connected