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.
- contango/__init__.py +20 -0
- contango/broker/__init__.py +20 -0
- contango/broker/calendar/__init__.py +23 -0
- contango/broker/calendar/calendar.py +47 -0
- contango/broker/calendar/nyse_calendar.py +72 -0
- contango/broker/historical_brokers/__init__.py +23 -0
- contango/broker/historical_brokers/config_type.py +55 -0
- contango/broker/historical_brokers/historical_broker.py +49 -0
- contango/broker/historical_brokers/yfinance/__init__.py +23 -0
- contango/broker/historical_brokers/yfinance/yfinance.py +148 -0
- contango/broker/historical_brokers/yfinance/yfinance_config.py +35 -0
- contango/data/__init__.py +22 -0
- contango/data/data_repository.py +79 -0
- contango/data/storage/__init__.py +20 -0
- contango/data/storage/store_market_data.py +252 -0
- contango/py.typed +0 -0
- contango/research/__init__.py +22 -0
- contango/research/research_runner.py +148 -0
- contango/research/research_strategies/__init__.py +20 -0
- contango/research/research_strategies/bollinger_band_mean_reversion/__init__.py +20 -0
- contango/research/research_strategies/bollinger_band_mean_reversion/runner.py +96 -0
- contango/research/research_strategies/bollinger_band_mean_reversion/strategy.py +125 -0
- contango/trading/__init__.py +20 -0
- contango/trading/analyzer/__init__.py +22 -0
- contango/trading/analyzer/data/__init__.py +22 -0
- contango/trading/analyzer/data/analyze_and_graph.py +124 -0
- contango/trading/analyzer/data/data_prep.py +112 -0
- contango/trading/analyzer/graphing/__init__.py +33 -0
- contango/trading/analyzer/graphing/equity_curve_overlay.py +91 -0
- contango/trading/analyzer/graphing/metric_distribution.py +57 -0
- contango/trading/analyzer/graphing/pairwise_heatmap_grid.py +93 -0
- contango/trading/analyzer/graphing/parallel_coordinates.py +58 -0
- contango/trading/analyzer/graphing/parameter_importance.py +84 -0
- contango/trading/analyzer/graphing/risk_return_overview.py +63 -0
- contango/trading/analyzer/graphing/trade_quality_scatter.py +53 -0
- contango/trading/analyzer/graphing/underwater_drawdown.py +122 -0
- contango/trading/execution/__init__.py +20 -0
- contango/trading/execution/backtester/__init__.py +25 -0
- contango/trading/execution/backtester/config.py +53 -0
- contango/trading/execution/backtester/market/__init__.py +22 -0
- contango/trading/execution/backtester/market/feed.py +49 -0
- contango/trading/execution/backtester/orders/__init__.py +23 -0
- contango/trading/execution/backtester/orders/order_filler.py +185 -0
- contango/trading/execution/backtester/orders/stoploss_order_manager.py +88 -0
- contango/trading/execution/backtester/portfolio/__init__.py +22 -0
- contango/trading/execution/backtester/portfolio/portfolio.py +117 -0
- contango/trading/execution/backtester/strategy_backtester.py +164 -0
- contango/trading/execution/engine/__init__.py +35 -0
- contango/trading/execution/engine/events/__init__.py +34 -0
- contango/trading/execution/engine/events/event_bus.py +78 -0
- contango/trading/execution/engine/events/events.py +126 -0
- contango/trading/execution/engine/orders/__init__.py +22 -0
- contango/trading/execution/engine/orders/order_api.py +87 -0
- contango/trading/execution/engine/results/__init__.py +23 -0
- contango/trading/execution/engine/results/execution_data.py +47 -0
- contango/trading/execution/engine/results/results_collector.py +70 -0
- contango/trading/execution/engine/strategy/__init__.py +23 -0
- contango/trading/execution/engine/strategy/strategy.py +85 -0
- contango/trading/execution/engine/strategy/strategy_injector.py +41 -0
- contango/trading/indicators/__init__.py +22 -0
- contango/trading/indicators/calculations/__init__.py +32 -0
- contango/trading/indicators/calculations/average_true_range.py +56 -0
- contango/trading/indicators/calculations/bollinger_bands.py +109 -0
- contango/trading/indicators/calculations/ema.py +58 -0
- contango/trading/indicators/calculations/rsi.py +76 -0
- contango/trading/indicators/calculations/sma.py +65 -0
- contango/trading/indicators/calculations/true_range.py +57 -0
- contango/trading/indicators/calculations/vwap.py +107 -0
- contango/trading/indicators/calculations/wilder_average.py +68 -0
- contango/trading/indicators/indicator.py +34 -0
- contango/trading/indicators/state/__init__.py +27 -0
- contango/trading/indicators/state/bollinger_bands_state.py +58 -0
- contango/trading/indicators/state/ema_state.py +49 -0
- contango/trading/indicators/state/rsi_state.py +59 -0
- contango/trading/indicators/state/sma_state.py +49 -0
- contango/trading/indicators/state/vwap_state.py +54 -0
- contango/trading/indicators/state/wilder_average_state.py +49 -0
- contango/trading/optimizer/__init__.py +20 -0
- contango/trading/optimizer/analysis/__init__.py +28 -0
- contango/trading/optimizer/analysis/builder.py +227 -0
- contango/trading/optimizer/analysis/calculate_metrics.py +49 -0
- contango/trading/optimizer/analysis/calculators/__init__.py +20 -0
- contango/trading/optimizer/analysis/calculators/drawdown.py +85 -0
- contango/trading/optimizer/analysis/calculators/returns.py +104 -0
- contango/trading/optimizer/analysis/calculators/risk.py +152 -0
- contango/trading/optimizer/analysis/calculators/trades.py +226 -0
- contango/trading/optimizer/analysis/context.py +97 -0
- contango/trading/optimizer/analysis/metrics.py +131 -0
- contango/trading/optimizer/experiments/__init__.py +26 -0
- contango/trading/optimizer/experiments/backtest_experiment.py +39 -0
- contango/trading/optimizer/experiments/backtest_experiment_grid.py +62 -0
- contango/trading/optimizer/experiments/backtest_experiment_result.py +38 -0
- contango/trading/optimizer/experiments/backtest_experiment_runner.py +69 -0
- contango-0.1.0.dist-info/METADATA +865 -0
- contango-0.1.0.dist-info/RECORD +98 -0
- contango-0.1.0.dist-info/WHEEL +5 -0
- contango-0.1.0.dist-info/licenses/LICENSE +661 -0
- 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__ = []
|