trdr 0.0.1__py2.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.
- trdr/__init__.py +0 -0
- trdr/conftest.py +148 -0
- trdr/core/__init__.py +0 -0
- trdr/core/bar_provider/__init__.py +0 -0
- trdr/core/bar_provider/base_bar_provider.py +115 -0
- trdr/core/bar_provider/exceptions.py +40 -0
- trdr/core/bar_provider/models.py +38 -0
- trdr/core/bar_provider/yf_bar_provider/__init__.py +0 -0
- trdr/core/bar_provider/yf_bar_provider/test_yf_bar_provider.py +73 -0
- trdr/core/bar_provider/yf_bar_provider/yf_bar_provider.py +301 -0
- trdr/core/broker/__init__.py +0 -0
- trdr/core/broker/alpaca_broker/README.md +68 -0
- trdr/core/broker/alpaca_broker/__init__.py +0 -0
- trdr/core/broker/alpaca_broker/alpaca_broker.py +404 -0
- trdr/core/broker/base_broker.py +391 -0
- trdr/core/broker/exceptions.py +6 -0
- trdr/core/broker/mock_broker/__init__.py +0 -0
- trdr/core/broker/mock_broker/mock_broker.py +130 -0
- trdr/core/broker/mock_broker/test_mock_broker.py +394 -0
- trdr/core/broker/mock_broker/test_mock_broker_pdt_validation.py +277 -0
- trdr/core/broker/models.py +145 -0
- trdr/core/broker/pdt/README.md +82 -0
- trdr/core/broker/pdt/__init__.py +0 -0
- trdr/core/broker/pdt/base_pdt_strategy.py +32 -0
- trdr/core/broker/pdt/exceptions.py +16 -0
- trdr/core/broker/pdt/models.py +40 -0
- trdr/core/broker/pdt/nun_strategy.py +86 -0
- trdr/core/broker/pdt/test_pdt_evaluation.py +247 -0
- trdr/core/broker/pdt/wiggle_strategy.py +65 -0
- trdr/core/broker/pdt/yolo_strategy.py +48 -0
- trdr/core/broker/test_order_model.py +407 -0
- trdr/core/broker/test_position_model.py +638 -0
- trdr/core/security_provider/__init__.py +0 -0
- trdr/core/security_provider/base_security_provider.py +108 -0
- trdr/core/security_provider/models.py +197 -0
- trdr/core/security_provider/security_provider.py +43 -0
- trdr/core/security_provider/test_security_model.py +74 -0
- trdr/core/security_provider/test_security_provider.py +30 -0
- trdr/core/shared/__init__.py +0 -0
- trdr/core/shared/exceptions.py +2 -0
- trdr/core/shared/models.py +299 -0
- trdr/core/trading_context/__init__.py +0 -0
- trdr/core/trading_context/exceptions.py +2 -0
- trdr/core/trading_context/test_trading_context.py +481 -0
- trdr/core/trading_context/trading_context.py +318 -0
- trdr/core/trading_engine/__init__.py +0 -0
- trdr/core/trading_engine/trading_engine.py +222 -0
- trdr/dsl/__init__.py +0 -0
- trdr/dsl/dsl_ast.py +375 -0
- trdr/dsl/dsl_loader.py +99 -0
- trdr/dsl/lexer.py +202 -0
- trdr/dsl/parser.py +288 -0
- trdr/test_utils/__init__.py +0 -0
- trdr/test_utils/fake_yf_download.py +37 -0
- trdr/test_utils/order_generator.py +149 -0
- trdr/test_utils/position_generator.py +152 -0
- trdr/test_utils/security_generator.py +162 -0
- trdr/test_utils/strategies/always_buy.trdr +13 -0
- trdr/test_utils/strategies/always_sell.trdr +13 -0
- trdr/test_utils/strategies/test_strat.trdr +28 -0
- trdr/test_utils/test_symbols.py +505 -0
- trdr-0.0.1.dist-info/METADATA +13 -0
- trdr-0.0.1.dist-info/RECORD +64 -0
- trdr-0.0.1.dist-info/WHEEL +5 -0
trdr/__init__.py
ADDED
|
File without changes
|
trdr/conftest.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import pytest
|
|
3
|
+
import yfinance as yf
|
|
4
|
+
import random
|
|
5
|
+
import datetime
|
|
6
|
+
|
|
7
|
+
from .core.security_provider.security_provider import SecurityProvider
|
|
8
|
+
from .core.bar_provider.yf_bar_provider.yf_bar_provider import YFBarProvider
|
|
9
|
+
from .test_utils.fake_yf_download import fake_yf_download
|
|
10
|
+
from .test_utils.security_generator import SecurityGenerator, SecurityCriteria
|
|
11
|
+
from .test_utils.position_generator import PositionGenerator, PositionCriteria
|
|
12
|
+
from .test_utils.order_generator import OrderGenerator, OrderCriteria
|
|
13
|
+
from .core.broker.mock_broker.mock_broker import MockBroker
|
|
14
|
+
from .core.broker.pdt.nun_strategy import NunStrategy
|
|
15
|
+
from .core.broker.pdt.wiggle_strategy import WiggleStrategy
|
|
16
|
+
from .core.broker.pdt.yolo_strategy import YoloStrategy
|
|
17
|
+
from .core.trading_context.trading_context import TradingContext
|
|
18
|
+
from .core.shared.models import TradingDateTime
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture(scope="function")
|
|
22
|
+
def weekday_trading_datetime():
|
|
23
|
+
trading_now = TradingDateTime.now()
|
|
24
|
+
while trading_now.is_weekend:
|
|
25
|
+
trading_now = trading_now + datetime.timedelta(days=1)
|
|
26
|
+
return trading_now
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture(scope="function")
|
|
30
|
+
def weekend_trading_datetime():
|
|
31
|
+
trading_now = TradingDateTime.now()
|
|
32
|
+
while not trading_now.is_weekend:
|
|
33
|
+
trading_now = trading_now + datetime.timedelta(days=1)
|
|
34
|
+
return trading_now
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture(scope="function")
|
|
38
|
+
def yf_bar_provider_with_fake_data(monkeypatch):
|
|
39
|
+
monkeypatch.setattr(yf, "download", fake_yf_download)
|
|
40
|
+
monkeypatch.setattr(yf.shared, "_ERRORS", {"ABCDEFG": "YFTzMissingError()", "AMZN": "JSONDecodeError()"})
|
|
41
|
+
return asyncio.run(YFBarProvider.create(["AAPL", "MSFT", "ABCDEFG", "AMZN"]))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.fixture(scope="function")
|
|
45
|
+
def security_provider_with_fake_data(yf_bar_provider_with_fake_data):
|
|
46
|
+
return asyncio.run(SecurityProvider.create(yf_bar_provider_with_fake_data))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.fixture(scope="function")
|
|
50
|
+
def random_security(symbol="AAPL", bar_count=200):
|
|
51
|
+
"""Create a random security with the given symbol and bar count.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
symbol: The ticker symbol to use
|
|
55
|
+
count: The number of bars to generate
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A Security instance with randomly generated price and volume data
|
|
59
|
+
"""
|
|
60
|
+
generator = SecurityGenerator(SecurityCriteria(bar_count=bar_count))
|
|
61
|
+
security = generator.find_suitable_security()
|
|
62
|
+
# Override the symbol if requested
|
|
63
|
+
if symbol != security.symbol:
|
|
64
|
+
security.symbol = symbol
|
|
65
|
+
return security
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.fixture(scope="function")
|
|
69
|
+
def get_random_security():
|
|
70
|
+
"""Legacy fixture name for backward compatibility."""
|
|
71
|
+
generator = SecurityGenerator(SecurityCriteria(bar_count=200))
|
|
72
|
+
return generator.find_suitable_security()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.fixture(scope="module")
|
|
76
|
+
def security_generator():
|
|
77
|
+
"""Return a configured SecurityGenerator instance."""
|
|
78
|
+
return SecurityGenerator(SecurityCriteria(bar_count=200))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.fixture(scope="function")
|
|
82
|
+
def short_dummy_position():
|
|
83
|
+
"""Create a test position with default values."""
|
|
84
|
+
position = PositionGenerator(criteria=PositionCriteria(bar_count=1, net_position_bias=0)).generate_positions()[0]
|
|
85
|
+
return position
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.fixture(scope="function")
|
|
89
|
+
def long_dummy_position():
|
|
90
|
+
"""Create a test position with default values."""
|
|
91
|
+
position = PositionGenerator(criteria=PositionCriteria(bar_count=1, net_position_bias=1)).generate_positions()[0]
|
|
92
|
+
return position
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@pytest.fixture(scope="function")
|
|
96
|
+
def dummy_positions():
|
|
97
|
+
"""Create a dictionary of test positions."""
|
|
98
|
+
num_positions = random.randint(1, 9)
|
|
99
|
+
positions = PositionGenerator(
|
|
100
|
+
criteria=PositionCriteria(bar_count=num_positions, net_position_bias=0.5)
|
|
101
|
+
).generate_positions()
|
|
102
|
+
return positions
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.fixture(scope="function")
|
|
106
|
+
def mock_broker_with_nun_strategy():
|
|
107
|
+
nun_strategy = NunStrategy.create()
|
|
108
|
+
broker = asyncio.run(MockBroker.create(pdt_strategy=nun_strategy))
|
|
109
|
+
yield broker
|
|
110
|
+
asyncio.run(broker._session.close())
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@pytest.fixture(scope="function")
|
|
114
|
+
def mock_broker_with_wiggle_strategy():
|
|
115
|
+
pdt_strategy = WiggleStrategy.create()
|
|
116
|
+
pdt_strategy.wiggle_room = 2
|
|
117
|
+
broker = asyncio.run(MockBroker.create(pdt_strategy=pdt_strategy))
|
|
118
|
+
yield broker
|
|
119
|
+
asyncio.run(broker._session.close())
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@pytest.fixture(scope="function")
|
|
123
|
+
def mock_broker_with_yolo_strategy():
|
|
124
|
+
pdt_strategy = YoloStrategy.create()
|
|
125
|
+
broker = asyncio.run(MockBroker.create(pdt_strategy=pdt_strategy))
|
|
126
|
+
yield broker
|
|
127
|
+
asyncio.run(broker._session.close())
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@pytest.fixture(scope="function")
|
|
131
|
+
def mock_trading_context(security_provider_with_fake_data, mock_broker_with_nun_strategy):
|
|
132
|
+
return asyncio.run(TradingContext.create(security_provider_with_fake_data, mock_broker_with_nun_strategy))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@pytest.fixture(scope="function")
|
|
136
|
+
def create_order():
|
|
137
|
+
|
|
138
|
+
def _create_order(symbol: str, side=None):
|
|
139
|
+
return OrderGenerator(criteria=OrderCriteria(count=1, symbol=symbol, side=side)).generate_orders()[0]
|
|
140
|
+
|
|
141
|
+
return _create_order
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@pytest.fixture(scope="function")
|
|
145
|
+
def prepared_trading_context(mock_trading_context: TradingContext):
|
|
146
|
+
"""Prepare trading context with a valid symbol and security."""
|
|
147
|
+
asyncio.run(mock_trading_context.next_symbol())
|
|
148
|
+
return mock_trading_context
|
trdr/core/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from typing import List, Type, Optional, TypeVar
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from opentelemetry import trace
|
|
4
|
+
|
|
5
|
+
from .models import Bar
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T", bound="BaseBarProvider")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseBarProvider(ABC):
|
|
11
|
+
"""
|
|
12
|
+
Abstract base class for retrieving and managing OHLCV bar data for securities.
|
|
13
|
+
|
|
14
|
+
This class defines the interface that all bar providers must implement, setting
|
|
15
|
+
a standardized way to retrieve historical and current price data. Bar providers
|
|
16
|
+
serve as the data foundation for trading strategies.
|
|
17
|
+
|
|
18
|
+
All implementations should maintain a data cache for efficient data retrieval
|
|
19
|
+
and support OpenTelemetry instrumentation for monitoring and tracing.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
_data_cache: Internal cache for storing retrieved bar data
|
|
23
|
+
_tracer: OpenTelemetry tracer for instrumenting operations
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
tracer: trace.Tracer,
|
|
29
|
+
):
|
|
30
|
+
self._data_cache = {}
|
|
31
|
+
self._tracer = tracer
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
async def create(cls: Type[T], symbols: List[str], tracer: Optional[trace.Tracer] = trace.NoOpTracer()) -> T:
|
|
35
|
+
"""
|
|
36
|
+
Factory method to create and initialize a bar provider instance.
|
|
37
|
+
|
|
38
|
+
This async factory method pattern ensures proper initialization of resources
|
|
39
|
+
and error handling during the creation process. It invokes the implementation-specific
|
|
40
|
+
_initialize method which must be defined by concrete subclasses.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
symbols: List of ticker symbols to initialize the provider with
|
|
44
|
+
tracer: OpenTelemetry tracer for instrumenting operations
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
An initialized instance of the concrete bar provider
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
Various exceptions depending on the implementation, typically related to
|
|
51
|
+
data availability or connectivity issues
|
|
52
|
+
"""
|
|
53
|
+
self = cls.__new__(cls)
|
|
54
|
+
BaseBarProvider.__init__(self, tracer)
|
|
55
|
+
with self._tracer.start_as_current_span("BaseBarProvider.create") as span:
|
|
56
|
+
try:
|
|
57
|
+
await self._initialize(symbols)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
span.record_exception(e)
|
|
60
|
+
span.set_status(trace.StatusCode.ERROR)
|
|
61
|
+
raise
|
|
62
|
+
else:
|
|
63
|
+
span.set_status(trace.StatusCode.OK)
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
async def _initialize(self, symbols: List[str]) -> None:
|
|
68
|
+
"""
|
|
69
|
+
This abstract method MUST be implemented by user defined data providers.
|
|
70
|
+
- This method SHOULD use the symbols argument to initialize the data cache.
|
|
71
|
+
- After the method has completed, the self._data_cache dictionary MUST be a dictionary where the keys are the symbols and the values are lists of Bars.
|
|
72
|
+
- The self._data_cache dictionary must only contain key,value pairs for symbols that have data available.
|
|
73
|
+
"""
|
|
74
|
+
raise NotImplementedError("This method must be implemented by user defined data providers")
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
async def get_symbols(self) -> List[str]:
|
|
78
|
+
"""
|
|
79
|
+
Get the list of symbols with data available.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
List[str]: List of symbols.
|
|
83
|
+
"""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
async def get_bars(self, symbol: str, lookback: Optional[int] = None) -> List[Bar]:
|
|
88
|
+
"""Get bars for a specific symbol.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
symbol: The ticker symbol to get bars for
|
|
92
|
+
lookback: The number of bars to return
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
NoBarsForSymbolException: If the symbol is not found in the data cache
|
|
96
|
+
InsufficientBarsException: If the number of bars requested is greater than the number of bars available
|
|
97
|
+
"""
|
|
98
|
+
raise NotImplementedError("This method must be implemented by user defined data providers")
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
async def get_current_bar(self, symbol: str) -> Bar:
|
|
102
|
+
"""
|
|
103
|
+
Get the current bar for a symbol.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
symbol (str): The symbol to get the current bar for.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Bar: The current bar for the symbol.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
BarProviderException: If we receive an error not associated with the symbol of interest or if we receive an error not related to a no data error.
|
|
113
|
+
NoBarsForSymbolException: If we didn't receive any data for the symbol of interest.
|
|
114
|
+
"""
|
|
115
|
+
pass
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class BarValidationException(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BarProviderException(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BarConversionException(BarProviderException):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DataSourceException(BarProviderException):
|
|
14
|
+
"""
|
|
15
|
+
This exception represents the case where the data source returns an error other than a NoBarsForSymbolException.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NoBarsForSymbolException(DataSourceException):
|
|
22
|
+
"""
|
|
23
|
+
This exception represents the case wehre Yahoo Fincance cannot return any data for a symbol.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
symbol: str
|
|
27
|
+
|
|
28
|
+
def __init__(self, symbol: str):
|
|
29
|
+
self.symbol = symbol
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return f"No bars found for symbol: {self.symbol}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TimeframeNotSupportedException(BarProviderException):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class InsufficientBarsException(BarProviderException):
|
|
40
|
+
pass
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pydantic import BaseModel, model_validator
|
|
2
|
+
|
|
3
|
+
from ..shared.models import TradingDateTime, Money
|
|
4
|
+
from .exceptions import BarValidationException
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Bar(BaseModel):
|
|
8
|
+
trading_datetime: TradingDateTime
|
|
9
|
+
open: Money
|
|
10
|
+
high: Money
|
|
11
|
+
low: Money
|
|
12
|
+
close: Money
|
|
13
|
+
volume: int
|
|
14
|
+
|
|
15
|
+
@model_validator(mode="after")
|
|
16
|
+
def check_values(self) -> "Bar":
|
|
17
|
+
# Validate that the low price is less than or equal to high price.
|
|
18
|
+
if self.low.amount > self.high.amount:
|
|
19
|
+
raise BarValidationException("Low price must be less than or equal to high price")
|
|
20
|
+
# Validate that open price is between low and high.
|
|
21
|
+
if not (self.low.amount <= self.open.amount <= self.high.amount):
|
|
22
|
+
raise BarValidationException("Open price must be between low and high prices")
|
|
23
|
+
# Validate that close price is between low and high.
|
|
24
|
+
if not (self.low.amount <= self.close.amount <= self.high.amount):
|
|
25
|
+
raise BarValidationException("Close price must be between low and high prices")
|
|
26
|
+
# Validate that the volume is non-negative.
|
|
27
|
+
if self.volume < 0:
|
|
28
|
+
raise BarValidationException("Volume cannot be negative")
|
|
29
|
+
return self
|
|
30
|
+
|
|
31
|
+
def to_json(self) -> str:
|
|
32
|
+
return self.model_dump_json(indent=2)
|
|
33
|
+
|
|
34
|
+
def __str__(self) -> str:
|
|
35
|
+
return (
|
|
36
|
+
f"Bar(timestamp={self.trading_datetime}, open={self.open}, "
|
|
37
|
+
f"high={self.high}, low={self.low}, close={self.close}, volume={self.volume})"
|
|
38
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import asyncio
|
|
3
|
+
import yfinance as yf
|
|
4
|
+
|
|
5
|
+
from .yf_bar_provider import YFBarProvider
|
|
6
|
+
from ..exceptions import NoBarsForSymbolException, BarProviderException, InsufficientBarsException
|
|
7
|
+
from ....test_utils.fake_yf_download import fake_yf_download
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_that_only_symbols_with_data_are_in_the_data_cache(yf_bar_provider_with_fake_data):
|
|
11
|
+
data_cache_symbols = set(yf_bar_provider_with_fake_data._data_cache.keys())
|
|
12
|
+
assert data_cache_symbols == {"AAPL", "MSFT"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_get_symbols_returns_a_list_of_symbols(yf_bar_provider_with_fake_data):
|
|
16
|
+
expected_symbols = {"AAPL", "MSFT"}
|
|
17
|
+
actual_symbols = yf_bar_provider_with_fake_data.get_symbols()
|
|
18
|
+
assert isinstance(actual_symbols, list)
|
|
19
|
+
# convert to set so order doesn't matter when we compare
|
|
20
|
+
actual_symbols = set(actual_symbols)
|
|
21
|
+
assert actual_symbols == expected_symbols
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_get_symbols_returns_only_symbols_that_have_bars(yf_bar_provider_with_fake_data):
|
|
25
|
+
expected_symbols = {"AAPL", "MSFT"}
|
|
26
|
+
actual_symbols = set(yf_bar_provider_with_fake_data.get_symbols())
|
|
27
|
+
assert actual_symbols == expected_symbols
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_gets_bars_throws_exception_when_no_bars_are_available_for_a_symbol(yf_bar_provider_with_fake_data):
|
|
31
|
+
with pytest.raises(NoBarsForSymbolException):
|
|
32
|
+
bars = asyncio.run(yf_bar_provider_with_fake_data.get_bars("ABCDEFG"))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_get_bars_throws_exception_when_symbol_is_not_in_data_cache(yf_bar_provider_with_fake_data):
|
|
36
|
+
with pytest.raises(NoBarsForSymbolException):
|
|
37
|
+
bars = asyncio.run(yf_bar_provider_with_fake_data.get_bars("ABCDEFG"))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_provider_throws_exception_when_data_source_returns_error(monkeypatch):
|
|
41
|
+
# ensure we raise an exception when the data source returns an error that is not a missing symbol error
|
|
42
|
+
with pytest.raises(BarProviderException):
|
|
43
|
+
monkeypatch.setattr(yf, "download", fake_yf_download)
|
|
44
|
+
monkeypatch.setattr(yf.shared, "_ERRORS", {"ABCDEFG": "RandomYFError()"})
|
|
45
|
+
bars = asyncio.run(YFBarProvider.create(["ABCDEFG"]))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_get_bars_throws_exception_when_lookback_is_greater_than_the_number_of_bars_available(
|
|
49
|
+
yf_bar_provider_with_fake_data,
|
|
50
|
+
):
|
|
51
|
+
with pytest.raises(InsufficientBarsException):
|
|
52
|
+
bars = asyncio.run(yf_bar_provider_with_fake_data.get_bars("AAPL", 7))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_get_current_bar_throws_exception_when_no_bars_are_available_for_a_symbol(monkeypatch):
|
|
56
|
+
with pytest.raises(NoBarsForSymbolException):
|
|
57
|
+
"""
|
|
58
|
+
Any time yf.download() is called, we clear the yf.shared._ERRORS dictionary after we are done inspecting it. Therefore, when we call yf.download() in the get_current_bar() method, we need to set the yf.shared._ERRORS dictionary again to the expected value of the test.
|
|
59
|
+
"""
|
|
60
|
+
monkeypatch.setattr(yf, "download", fake_yf_download)
|
|
61
|
+
monkeypatch.setattr(yf.shared, "_ERRORS", {"ABCDEFG": "YFTzMissingError()"})
|
|
62
|
+
yf_bar_provider = asyncio.run(YFBarProvider.create(["ABCDEFG"]))
|
|
63
|
+
# yf.shared._ERRORS is currently {} as we reset it after the yf.download() call that occurs during initialization of the bar provider.
|
|
64
|
+
monkeypatch.setattr(yf.shared, "_ERRORS", {"ABCDEFG": "YFTzMissingError()"})
|
|
65
|
+
bar = asyncio.run(yf_bar_provider.get_current_bar("ABCDEFG"))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_get_current_bar_throws_exception_when_data_source_returns_error(monkeypatch):
|
|
69
|
+
with pytest.raises(BarProviderException):
|
|
70
|
+
monkeypatch.setattr(yf, "download", fake_yf_download)
|
|
71
|
+
yf_bar_provider = asyncio.run(YFBarProvider.create(["ABCDEFG"]))
|
|
72
|
+
monkeypatch.setattr(yf.shared, "_ERRORS", {"ABCDEFG": "RandomYFError()"})
|
|
73
|
+
bar = asyncio.run(yf_bar_provider.get_current_bar("ABCDEFG"))
|