contango 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. contango/__init__.py +20 -0
  2. contango/broker/__init__.py +20 -0
  3. contango/broker/calendar/__init__.py +23 -0
  4. contango/broker/calendar/calendar.py +47 -0
  5. contango/broker/calendar/nyse_calendar.py +72 -0
  6. contango/broker/historical_brokers/__init__.py +23 -0
  7. contango/broker/historical_brokers/config_type.py +55 -0
  8. contango/broker/historical_brokers/historical_broker.py +49 -0
  9. contango/broker/historical_brokers/yfinance/__init__.py +23 -0
  10. contango/broker/historical_brokers/yfinance/yfinance.py +148 -0
  11. contango/broker/historical_brokers/yfinance/yfinance_config.py +35 -0
  12. contango/data/__init__.py +22 -0
  13. contango/data/data_repository.py +79 -0
  14. contango/data/storage/__init__.py +20 -0
  15. contango/data/storage/store_market_data.py +252 -0
  16. contango/py.typed +0 -0
  17. contango/research/__init__.py +22 -0
  18. contango/research/research_runner.py +148 -0
  19. contango/research/research_strategies/__init__.py +20 -0
  20. contango/research/research_strategies/bollinger_band_mean_reversion/__init__.py +20 -0
  21. contango/research/research_strategies/bollinger_band_mean_reversion/runner.py +96 -0
  22. contango/research/research_strategies/bollinger_band_mean_reversion/strategy.py +125 -0
  23. contango/trading/__init__.py +20 -0
  24. contango/trading/analyzer/__init__.py +22 -0
  25. contango/trading/analyzer/data/__init__.py +22 -0
  26. contango/trading/analyzer/data/analyze_and_graph.py +124 -0
  27. contango/trading/analyzer/data/data_prep.py +112 -0
  28. contango/trading/analyzer/graphing/__init__.py +33 -0
  29. contango/trading/analyzer/graphing/equity_curve_overlay.py +91 -0
  30. contango/trading/analyzer/graphing/metric_distribution.py +57 -0
  31. contango/trading/analyzer/graphing/pairwise_heatmap_grid.py +93 -0
  32. contango/trading/analyzer/graphing/parallel_coordinates.py +58 -0
  33. contango/trading/analyzer/graphing/parameter_importance.py +84 -0
  34. contango/trading/analyzer/graphing/risk_return_overview.py +63 -0
  35. contango/trading/analyzer/graphing/trade_quality_scatter.py +53 -0
  36. contango/trading/analyzer/graphing/underwater_drawdown.py +122 -0
  37. contango/trading/execution/__init__.py +20 -0
  38. contango/trading/execution/backtester/__init__.py +25 -0
  39. contango/trading/execution/backtester/config.py +53 -0
  40. contango/trading/execution/backtester/market/__init__.py +22 -0
  41. contango/trading/execution/backtester/market/feed.py +49 -0
  42. contango/trading/execution/backtester/orders/__init__.py +23 -0
  43. contango/trading/execution/backtester/orders/order_filler.py +185 -0
  44. contango/trading/execution/backtester/orders/stoploss_order_manager.py +88 -0
  45. contango/trading/execution/backtester/portfolio/__init__.py +22 -0
  46. contango/trading/execution/backtester/portfolio/portfolio.py +117 -0
  47. contango/trading/execution/backtester/strategy_backtester.py +164 -0
  48. contango/trading/execution/engine/__init__.py +35 -0
  49. contango/trading/execution/engine/events/__init__.py +34 -0
  50. contango/trading/execution/engine/events/event_bus.py +78 -0
  51. contango/trading/execution/engine/events/events.py +126 -0
  52. contango/trading/execution/engine/orders/__init__.py +22 -0
  53. contango/trading/execution/engine/orders/order_api.py +87 -0
  54. contango/trading/execution/engine/results/__init__.py +23 -0
  55. contango/trading/execution/engine/results/execution_data.py +47 -0
  56. contango/trading/execution/engine/results/results_collector.py +70 -0
  57. contango/trading/execution/engine/strategy/__init__.py +23 -0
  58. contango/trading/execution/engine/strategy/strategy.py +85 -0
  59. contango/trading/execution/engine/strategy/strategy_injector.py +41 -0
  60. contango/trading/indicators/__init__.py +22 -0
  61. contango/trading/indicators/calculations/__init__.py +32 -0
  62. contango/trading/indicators/calculations/average_true_range.py +56 -0
  63. contango/trading/indicators/calculations/bollinger_bands.py +109 -0
  64. contango/trading/indicators/calculations/ema.py +58 -0
  65. contango/trading/indicators/calculations/rsi.py +76 -0
  66. contango/trading/indicators/calculations/sma.py +65 -0
  67. contango/trading/indicators/calculations/true_range.py +57 -0
  68. contango/trading/indicators/calculations/vwap.py +107 -0
  69. contango/trading/indicators/calculations/wilder_average.py +68 -0
  70. contango/trading/indicators/indicator.py +34 -0
  71. contango/trading/indicators/state/__init__.py +27 -0
  72. contango/trading/indicators/state/bollinger_bands_state.py +58 -0
  73. contango/trading/indicators/state/ema_state.py +49 -0
  74. contango/trading/indicators/state/rsi_state.py +59 -0
  75. contango/trading/indicators/state/sma_state.py +49 -0
  76. contango/trading/indicators/state/vwap_state.py +54 -0
  77. contango/trading/indicators/state/wilder_average_state.py +49 -0
  78. contango/trading/optimizer/__init__.py +20 -0
  79. contango/trading/optimizer/analysis/__init__.py +28 -0
  80. contango/trading/optimizer/analysis/builder.py +227 -0
  81. contango/trading/optimizer/analysis/calculate_metrics.py +49 -0
  82. contango/trading/optimizer/analysis/calculators/__init__.py +20 -0
  83. contango/trading/optimizer/analysis/calculators/drawdown.py +85 -0
  84. contango/trading/optimizer/analysis/calculators/returns.py +104 -0
  85. contango/trading/optimizer/analysis/calculators/risk.py +152 -0
  86. contango/trading/optimizer/analysis/calculators/trades.py +226 -0
  87. contango/trading/optimizer/analysis/context.py +97 -0
  88. contango/trading/optimizer/analysis/metrics.py +131 -0
  89. contango/trading/optimizer/experiments/__init__.py +26 -0
  90. contango/trading/optimizer/experiments/backtest_experiment.py +39 -0
  91. contango/trading/optimizer/experiments/backtest_experiment_grid.py +62 -0
  92. contango/trading/optimizer/experiments/backtest_experiment_result.py +38 -0
  93. contango/trading/optimizer/experiments/backtest_experiment_runner.py +69 -0
  94. contango-0.1.0.dist-info/METADATA +865 -0
  95. contango-0.1.0.dist-info/RECORD +98 -0
  96. contango-0.1.0.dist-info/WHEEL +5 -0
  97. contango-0.1.0.dist-info/licenses/LICENSE +661 -0
  98. contango-0.1.0.dist-info/top_level.txt +1 -0
contango/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ # trading/execution/backtester/market/__init__.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+
20
+ __all__ = []
@@ -0,0 +1,20 @@
1
+ # broker/__init__.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+
20
+ __all__ = []
@@ -0,0 +1,23 @@
1
+ # broker/calendar/__init__.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ from contango.broker.calendar.calendar import Calendar
20
+ from contango.broker.calendar.nyse_calendar import NYSECalendar
21
+
22
+
23
+ __all__ = ['Calendar', 'NYSECalendar']
@@ -0,0 +1,47 @@
1
+ # broker/calendar/calendar.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ from abc import ABC, abstractmethod
20
+ from datetime import datetime
21
+
22
+ from contango.broker.historical_brokers.config_type import Interval
23
+
24
+
25
+ class Calendar(ABC):
26
+ """
27
+ A delegation of the available trading times for calendar type (i.e. NYSE or Crypto).
28
+ """
29
+ @abstractmethod
30
+ def get_expected_timestamps(
31
+ self,
32
+ start_timestamp: int,
33
+ end_timestamp: int,
34
+ interval: Interval,
35
+ ) -> list[datetime]:
36
+ """
37
+ Returns the expected available timestamps in a calendar for a specified period.
38
+
39
+ Args:
40
+ start_timestamp: The start time in unix ms.
41
+ end_timestamp: The end time in unix ms.
42
+ interval: The bar interval type.
43
+
44
+ Returns:
45
+ list[datetime]: The dates & times of the available trading bars for the designated period.
46
+ """
47
+ ...
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import cast
5
+ from zoneinfo import ZoneInfo
6
+
7
+ import exchange_calendars as xcals # pyright: ignore[reportMissingTypeStubs]
8
+ import pandas as pd
9
+
10
+ from contango.broker.historical_brokers.config_type import Interval
11
+ from contango.broker.calendar.calendar import Calendar
12
+
13
+
14
+ class NYSECalendar(Calendar):
15
+ """
16
+ Trading calendar for the New York Stock Exchange (9:00AM-4:00PM on weekdays, holidays taken into account).
17
+ """
18
+ def __init__(self) -> None:
19
+ """
20
+ Initializes `NYSECalendar.
21
+ """
22
+ self._calendar = xcals.get_calendar("XNYS", start="1990-01-01")
23
+
24
+ def get_expected_timestamps(
25
+ self,
26
+ start_timestamp: int,
27
+ end_timestamp: int,
28
+ interval: Interval,
29
+ ) -> list[datetime]:
30
+ """
31
+ Returns the expected available NYSE timestamps for the specified period.
32
+
33
+ Args:
34
+ start_timestamp: The start time in unix ms.
35
+ end_timestamp: The end time in unix ms.
36
+ interval: The bar interval type.
37
+
38
+ Returns:
39
+ list[datetime]: The close times of the available NYSE trading bars for the designated period.
40
+ """
41
+ start_ny = pd.Timestamp(start_timestamp, unit="ms", tz=ZoneInfo("UTC")).tz_convert(ZoneInfo("America/New_York"))
42
+ end_ny = pd.Timestamp(end_timestamp, unit="ms", tz=ZoneInfo("UTC")).tz_convert(ZoneInfo("America/New_York"))
43
+
44
+ start_date = start_ny.tz_localize(None).normalize()
45
+ end_date = end_ny.tz_localize(None).normalize()
46
+
47
+ frequency = pd.Timedelta(interval.value)
48
+
49
+ raw_index = self._calendar.trading_index(
50
+ start=start_date,
51
+ end=end_date,
52
+ period=frequency,
53
+ intervals=True,
54
+ force=True,
55
+ )
56
+
57
+ close_timestamps: pd.DatetimeIndex
58
+ if isinstance(raw_index, pd.IntervalIndex):
59
+ close_timestamps = cast(pd.DatetimeIndex, raw_index.right)
60
+ else:
61
+ close_timestamps = raw_index.tz_localize(timezone.utc)
62
+
63
+ start_utc = start_ny.tz_convert(ZoneInfo("UTC"))
64
+ end_utc = end_ny.tz_convert(ZoneInfo("UTC"))
65
+
66
+ close_times: list[datetime] = [
67
+ ts.to_pydatetime().astimezone(timezone.utc)
68
+ for ts in close_timestamps
69
+ if start_utc <= ts <= end_utc
70
+ ]
71
+
72
+ return close_times
@@ -0,0 +1,23 @@
1
+ # broker/historical_brokers/__init__.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ from contango.broker.historical_brokers.historical_broker import HistoricalBroker
20
+ from contango.broker.historical_brokers.config_type import Config, Interval
21
+
22
+
23
+ __all__ = ['HistoricalBroker', 'Config', 'Interval']
@@ -0,0 +1,55 @@
1
+ # broker/historical_brokers/config_type.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ from enum import Enum
20
+ from datetime import timedelta
21
+ from dataclasses import dataclass
22
+
23
+
24
+ class Interval(Enum):
25
+ """
26
+ The interval type to determine how frequent of bars to derive from a broker.
27
+ """
28
+ MINUTE_1 = timedelta(minutes=1)
29
+ MINUTE_2 = timedelta(minutes=2)
30
+ MINUTE_5 = timedelta(minutes=5)
31
+ MINUTE_15 = timedelta(minutes=15)
32
+ MINUTE_30 = timedelta(minutes=30)
33
+ MINUTE_60 = timedelta(minutes=60)
34
+ MINUTE_90 = timedelta(minutes=90)
35
+ HOUR_1 = timedelta(hours=1)
36
+ DAY_1 = timedelta(days=1)
37
+ DAY_5 = timedelta(days=5)
38
+ WEEK_1 = timedelta(weeks=1)
39
+
40
+
41
+ @dataclass
42
+ class Config:
43
+ """
44
+ Marks a dataclass as a configurator for a historical broker when deriving data from it.
45
+
46
+ Attributes:
47
+ ticker: The ticker symbol for a broker call.
48
+ interval: The interval for a broker call.
49
+ start_timestamp: The start time in unix ms to derive data from.
50
+ end_timestamp: The end time in unix ms to derive data from.
51
+ """
52
+ ticker: str
53
+ interval: Interval
54
+ start_timestamp: int
55
+ end_timestamp: int
@@ -0,0 +1,49 @@
1
+ # broker/historical_brokers/historical_broker.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Generic, TypeVar, Iterable
20
+ from abc import ABC, abstractmethod
21
+ from datetime import datetime
22
+
23
+ from contango.trading.execution.engine.events import MarketDataEvent
24
+ from contango.broker.historical_brokers.config_type import Config
25
+
26
+
27
+ TConfig = TypeVar("TConfig", bound=Config)
28
+
29
+
30
+ class HistoricalBroker(ABC, Generic[TConfig]):
31
+ """
32
+ A single broker (or data provider) for historical market data.
33
+ """
34
+ @abstractmethod
35
+ def get_bars(self, config: TConfig) -> list[MarketDataEvent]:
36
+ """
37
+ Returns a list of market data events for any configuration parameters.
38
+ """
39
+ ...
40
+
41
+ @abstractmethod
42
+ def get_expected_timestamps(
43
+ self,
44
+ config: TConfig,
45
+ ) -> Iterable[datetime]:
46
+ """
47
+ Returns an Iterable of the expected timestamps for the start & finishing timestamps of a configuration.
48
+ """
49
+ ...
@@ -0,0 +1,23 @@
1
+ # broker/historical_brokers/yfinance/__init__.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ from contango.broker.historical_brokers.yfinance.yfinance import Yfinance
20
+ from contango.broker.historical_brokers.yfinance.yfinance_config import YfinanceConfig
21
+
22
+
23
+ __all__ = ['Yfinance', 'YfinanceConfig']
@@ -0,0 +1,148 @@
1
+ # broker/historical_brokers/yfinance/yfinance.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ import pandas as pd
20
+ import yfinance as yf # type: ignore[missingTypeStubs]
21
+
22
+ from typing import Iterable
23
+ from datetime import datetime, timezone
24
+
25
+ from contango.broker.historical_brokers.historical_broker import HistoricalBroker
26
+ from contango.broker.historical_brokers.yfinance.yfinance_config import YfinanceConfig
27
+ from contango.broker.historical_brokers.config_type import Interval
28
+ from contango.broker.calendar.calendar import Calendar
29
+
30
+ from contango.trading.execution.engine.events import MarketDataEvent
31
+
32
+
33
+ USD = float
34
+ time_unix_ms = int
35
+ units = int
36
+
37
+ # Map interval values from the Enum to the expected str values via yfinance.download.
38
+ INTERVAL_MAP: dict[Interval, str] = {
39
+ Interval.MINUTE_1: "1m",
40
+ Interval.MINUTE_2: "2m",
41
+ Interval.MINUTE_5: "5m",
42
+ Interval.MINUTE_15: "15m",
43
+ Interval.MINUTE_30: "30m",
44
+ Interval.MINUTE_60: "60m",
45
+ Interval.MINUTE_90: "90m",
46
+ Interval.HOUR_1: "1h",
47
+ Interval.DAY_1: "1d",
48
+ Interval.DAY_5: "5d",
49
+ Interval.WEEK_1: "1wk"
50
+ }
51
+
52
+
53
+ class Yfinance(HistoricalBroker[YfinanceConfig]):
54
+ """
55
+ The yfinance data provider (not an official broker).
56
+ Data is limited in large quantities, some data may be innacurate, and rate limiting may be enforced with usage.
57
+ """
58
+ def __init__(self, calendar: Calendar) -> None:
59
+ """
60
+ Initializes `Yfinance`.
61
+
62
+ Args:
63
+ calendar: The calender type to follow (i.e. NYSE).
64
+ """
65
+ self._calendar = calendar
66
+
67
+ def _load_data(self, config: YfinanceConfig) -> pd.DataFrame:
68
+ """
69
+ Loads yfinance data from a `YfinanceConfig`.
70
+
71
+ Args:
72
+ config: The determiner for the ticker, start, end, and interval when downloading yfinance data.
73
+
74
+ Returns:
75
+ A normalized pandas dataframe representation of the yfinance data.
76
+
77
+ Raises:
78
+ RuntimeError: If no data was returned for the given config.
79
+ """
80
+ start = datetime.fromtimestamp(config.start_timestamp / 1000, tz=timezone.utc).strftime('%Y-%m-%d')
81
+ end = datetime.fromtimestamp(config.end_timestamp / 1000, tz=timezone.utc).strftime('%Y-%m-%d')
82
+ interval = INTERVAL_MAP.get(config.interval, None)
83
+
84
+ if interval is None:
85
+ raise RuntimeError("Interval type provided was not in the interval map for yfinance!")
86
+
87
+ data = yf.download( # type: ignore[unknownMemberType]
88
+ tickers=config.ticker,
89
+ start=start,
90
+ end=end,
91
+ interval=interval,
92
+ )
93
+
94
+ if data is None or data.empty:
95
+ raise RuntimeError(f"No data returned for {config.ticker}")
96
+
97
+ return data
98
+
99
+ def get_bars(self, config: YfinanceConfig) -> list[MarketDataEvent]:
100
+ """
101
+ Returns a list of the bars for the given period.
102
+
103
+ Args:
104
+ config: The yfinance config to derive bars from.
105
+
106
+ Raises:
107
+ RuntimeError: Upon yfinance not returning any data.
108
+ """
109
+ data = self._load_data(config)
110
+
111
+ if isinstance(data.columns, pd.MultiIndex):
112
+ data.columns = data.columns.get_level_values(0)
113
+
114
+ bars: list[MarketDataEvent] = []
115
+ for timestamp, row in data.iterrows():
116
+ bars.append(
117
+ MarketDataEvent(
118
+ timestamp=int(pd.Timestamp(timestamp).timestamp() * 1000), # type: ignore[argumentType]
119
+ symbol=config.ticker,
120
+ open=float(row["Open"]),
121
+ high=float(row["High"]),
122
+ low=float(row["Low"]),
123
+ close=float(row["Close"]),
124
+ volume=int(row["Volume"]),
125
+ )
126
+ )
127
+
128
+ return bars
129
+
130
+ def get_expected_timestamps(
131
+ self,
132
+ config: YfinanceConfig,
133
+ ) -> Iterable[datetime]:
134
+ """
135
+ Returns the expected yfinance timestamps for the given calendar at initialization.
136
+
137
+ Args:
138
+ config: The configuration, in which the start timestamp, end timestamp, & interval will be used to
139
+ derive the valid timestamps from.
140
+
141
+ Returns:
142
+ Iterable[datetime]: An iterable of valid datetime objects.
143
+ """
144
+ return self._calendar.get_expected_timestamps(
145
+ start_timestamp=config.start_timestamp,
146
+ end_timestamp=config.end_timestamp,
147
+ interval=config.interval,
148
+ )
@@ -0,0 +1,35 @@
1
+ # broker/historical_brokers/yfinance/yfinance_config.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+
21
+ from contango.broker.historical_brokers.config_type import Config
22
+
23
+
24
+ @dataclass
25
+ class YfinanceConfig(Config):
26
+ """
27
+ Holds the configuration for deriving data from the yfinance broker.
28
+
29
+ Attributes:
30
+ ticker: The ticker symbol (e.g. AAPL) to derive yfinance data from.
31
+ start_date: The start date (YYYY-MM-DD) to derive yfinance data from.
32
+ end_date: The end date (YYYY-MM-DD) to derive yfinance data from.
33
+ interval: The trading interval to derive yfinance data from.
34
+ """
35
+ pass
@@ -0,0 +1,22 @@
1
+ # data/__init__.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ from contango.data.data_repository import DataRepository
20
+
21
+
22
+ __all__ = ['DataRepository']
@@ -0,0 +1,79 @@
1
+ # data/data_repository.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import replace
20
+ from pathlib import Path
21
+ from typing import Generic, TypeVar, Iterable
22
+ from datetime import datetime
23
+
24
+ from contango.broker.historical_brokers.config_type import Config
25
+ from contango.broker.historical_brokers.historical_broker import HistoricalBroker
26
+
27
+ from contango.trading.execution.engine.events.events import MarketDataEvent
28
+
29
+ from contango.data.storage.store_market_data import DataStorage
30
+
31
+
32
+ TConfig = TypeVar("TConfig", bound=Config)
33
+
34
+
35
+ class DataRepository(Generic[TConfig]):
36
+ """
37
+ Retrieves data from either internal storage, a broker, or both.
38
+ Data that is not already in storage is automatically added.
39
+ """
40
+ @staticmethod
41
+ def get_data_and_store(
42
+ broker: HistoricalBroker[TConfig],
43
+ config: TConfig,
44
+ expected_timestamps: Iterable[datetime],
45
+ database_path: str | Path | None = None,
46
+ ) -> list[MarketDataEvent]:
47
+ """
48
+ Retrieves data from either the database, a broker, or both.
49
+ Any data not in the database that is retrieved from the broker is then put into storage for further use.
50
+
51
+ Args:
52
+ broker: The historical broker to derive data from if necessary.
53
+ config: The corresponding config to the historical broker.
54
+ expected_timestamps: An iterable of the expected timestamps.
55
+ database_path: Where the database should live. If not provided,
56
+ DataStorage resolves it via the OS-standard
57
+ user data directory.
58
+ """
59
+ with DataStorage(database_path) as storage:
60
+ ticker = config.ticker
61
+ interval = config.interval.__str__()
62
+ start_timestamp = config.start_timestamp
63
+ end_timestamp = config.end_timestamp
64
+ missing_timestamps = storage.get_missing_timestamps(ticker, interval, expected_timestamps)
65
+ if len(missing_timestamps) != 0:
66
+ min_timestamp = min(missing_timestamps)
67
+ max_timestamp = max(missing_timestamps)
68
+ interval_ms = int(config.interval.value.total_seconds() * 1000)
69
+ new_config = replace(
70
+ config,
71
+ start_timestamp=min_timestamp,
72
+ end_timestamp=max(max_timestamp + interval_ms, min_timestamp + interval_ms),
73
+ )
74
+ new_data = broker.get_bars(new_config)
75
+ storage.add_data_to_storage(interval, new_data)
76
+
77
+ data = storage.get_data(ticker, interval, start_timestamp, end_timestamp)
78
+
79
+ return data
@@ -0,0 +1,20 @@
1
+ # data/storage/__init__.py — part of Contango, a parameterized backtesting & execution framework
2
+ # Copyright (C) 2026 Jacob Taylor
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ from __future__ import annotations
18
+
19
+
20
+ __all__ = []