mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-07-31 06:07:43 +00:00
638 lines
24 KiB
Python
638 lines
24 KiB
Python
"""Comprehensive tests for the candle module.
|
|
|
|
Tests cover:
|
|
- CandleProtocol compliance
|
|
- CandleBase methods and properties
|
|
- Candle class functionality
|
|
- Candles container operations
|
|
"""
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
import pandas as pd
|
|
from pandas import Series, DataFrame, Timestamp
|
|
|
|
from aiomql.lib.candle import Candle, Candles, CandleBase, CandleProtocol
|
|
from aiomql.core.constants import TimeFrame
|
|
from aiomql.ta_libs import pandas_ta_classic as ta
|
|
|
|
|
|
class TestCandleProtocol:
|
|
"""Test CandleProtocol type checking."""
|
|
|
|
def test_candle_implements_protocol(self):
|
|
"""Candle class should implement CandleProtocol."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
assert isinstance(candle, CandleProtocol)
|
|
|
|
def test_candlebase_implements_protocol(self):
|
|
"""CandleBase subclass should implement CandleProtocol."""
|
|
class CustomCandle(CandleBase):
|
|
def __init__(self, **kwargs):
|
|
self.open = kwargs['open']
|
|
self.high = kwargs['high']
|
|
self.low = kwargs['low']
|
|
self.close = kwargs['close']
|
|
|
|
candle = CustomCandle(open=100, high=110, low=95, close=105)
|
|
assert isinstance(candle, CandleProtocol)
|
|
|
|
def test_minimal_protocol_implementation(self):
|
|
"""Minimal class with OHLC should implement CandleProtocol."""
|
|
class MinimalCandle:
|
|
def __init__(self, **kwargs):
|
|
self.open = kwargs['open']
|
|
self.high = kwargs['high']
|
|
self.low = kwargs['low']
|
|
self.close = kwargs['close']
|
|
|
|
candle = MinimalCandle(open=100, high=110, low=95, close=105)
|
|
assert isinstance(candle, CandleProtocol)
|
|
|
|
|
|
class TestCandleBase:
|
|
"""Test CandleBase class methods and properties."""
|
|
|
|
@classmethod
|
|
def setup_class(cls):
|
|
"""Create test candles for CandleBase tests."""
|
|
# Bullish candle: close > open
|
|
cls.bullish = Candle(open=100.0, high=110.0, low=95.0, close=108.0)
|
|
# Bearish candle: close < open
|
|
cls.bearish = Candle(open=108.0, high=112.0, low=90.0, close=95.0)
|
|
# Doji candle: close == open
|
|
cls.doji = Candle(open=100.0, high=105.0, low=95.0, close=100.0)
|
|
|
|
def test_repr(self):
|
|
"""Test __repr__ method."""
|
|
repr_str = repr(self.bullish)
|
|
assert repr_str.startswith("Candle(")
|
|
assert "open=" in repr_str
|
|
assert "high=" in repr_str
|
|
assert "low=" in repr_str
|
|
assert "close=" in repr_str
|
|
|
|
def test_is_bullish(self):
|
|
"""Test is_bullish method."""
|
|
assert self.bullish.is_bullish() is True
|
|
assert self.bearish.is_bullish() is False
|
|
assert self.doji.is_bullish() is True # close == open is bullish
|
|
|
|
def test_is_bearish(self):
|
|
"""Test is_bearish method."""
|
|
assert self.bearish.is_bearish() is True
|
|
assert self.bullish.is_bearish() is False
|
|
assert self.doji.is_bearish() is False
|
|
|
|
def test_upper_wick(self):
|
|
"""Test upper_wick property."""
|
|
# Bullish: high - close = 110 - 108 = 2
|
|
assert self.bullish.upper_wick == 2.0
|
|
# Bearish: high - open = 112 - 108 = 4
|
|
assert self.bearish.upper_wick == 4.0
|
|
|
|
def test_lower_wick(self):
|
|
"""Test lower_wick property."""
|
|
# Bullish: open - low = 100 - 95 = 5
|
|
assert self.bullish.lower_wick == 5.0
|
|
# Bearish: close - low = 95 - 90 = 5
|
|
assert self.bearish.lower_wick == 5.0
|
|
|
|
def test_candle_range(self):
|
|
"""Test candle_range property."""
|
|
# Bullish: high - low = 110 - 95 = 15
|
|
assert self.bullish.candle_range == 15.0
|
|
# Bearish: high - low = 112 - 90 = 22
|
|
assert self.bearish.candle_range == 22.0
|
|
|
|
def test_candle_body(self):
|
|
"""Test candle_body property."""
|
|
# Bullish: |close - open| = |108 - 100| = 8
|
|
assert self.bullish.candle_body == 8.0
|
|
# Bearish: |95 - 108| = 13
|
|
assert self.bearish.candle_body == 13.0
|
|
# Doji: |100 - 100| = 0
|
|
assert self.doji.candle_body == 0.0
|
|
|
|
def test_upper_wick_percentage(self):
|
|
"""Test upper_wick_percentage property."""
|
|
# Bullish: (2 / 15) * 100 = 13.33...
|
|
assert abs(self.bullish.upper_wick_percentage - 13.333333) < 0.001
|
|
|
|
def test_lower_wick_percentage(self):
|
|
"""Test lower_wick_percentage property."""
|
|
# Bullish: (5 / 15) * 100 = 33.33...
|
|
assert abs(self.bullish.lower_wick_percentage - 33.333333) < 0.001
|
|
|
|
def test_candle_body_percentage(self):
|
|
"""Test candle_body_percentage property."""
|
|
# Bullish: (8 / 15) * 100 = 53.33...
|
|
assert abs(self.bullish.candle_body_percentage - 53.333333) < 0.001
|
|
|
|
def test_comparison_key(self):
|
|
"""Test _comparison_key static method."""
|
|
key = CandleBase._comparison_key(self.bullish)
|
|
assert key == (8.0, 15.0) # (body, range)
|
|
|
|
def test_comparison_key_with_dict(self):
|
|
"""Test _comparison_key works with dict-like objects."""
|
|
candle_dict = {'open': 100.0, 'high': 110.0, 'low': 95.0, 'close': 108.0}
|
|
key = CandleBase._comparison_key(candle_dict)
|
|
assert key == (8.0, 15.0)
|
|
|
|
def test_equality(self):
|
|
"""Test __eq__ based on body and range."""
|
|
# Same body and range
|
|
c1 = Candle(open=100, high=110, low=95, close=108) # body=8, range=15
|
|
c2 = Candle(open=102, high=112, low=97, close=110) # body=8, range=15
|
|
assert c1 == c2
|
|
|
|
def test_inequality(self):
|
|
"""Test __ne__ based on body and range."""
|
|
assert self.bullish != self.bearish
|
|
|
|
def test_less_than(self):
|
|
"""Test __lt__ comparison."""
|
|
# Bullish: body=8, range=15
|
|
# Bearish: body=13, range=22
|
|
assert self.bullish < self.bearish # smaller body
|
|
|
|
def test_less_than_or_equal(self):
|
|
"""Test __le__ comparison."""
|
|
c1 = Candle(open=100, high=110, low=95, close=108)
|
|
c2 = Candle(open=102, high=112, low=97, close=110) # Same key
|
|
assert c1 <= c2
|
|
assert self.bullish <= self.bearish
|
|
|
|
def test_greater_than(self):
|
|
"""Test __gt__ comparison."""
|
|
assert self.bearish > self.bullish
|
|
|
|
def test_greater_than_or_equal(self):
|
|
"""Test __ge__ comparison."""
|
|
c1 = Candle(open=100, high=110, low=95, close=108)
|
|
c2 = Candle(open=102, high=112, low=97, close=110)
|
|
assert c1 >= c2
|
|
assert self.bearish >= self.bullish
|
|
|
|
def test_hash(self):
|
|
"""Test __hash__ method."""
|
|
time = datetime.now().timestamp()
|
|
c1 = Candle(open=100, high=110, low=95, close=108, time=time)
|
|
c2 = Candle(open=100, high=110, low=95, close=108, time=time) # Same key
|
|
assert hash(c1) == hash(c2)
|
|
|
|
def test_getitem(self):
|
|
"""Test __getitem__ for dict-like access."""
|
|
assert self.bullish['open'] == 100.0
|
|
assert self.bullish['close'] == 108.0
|
|
|
|
def test_setitem(self):
|
|
"""Test __setitem__ for dict-like setting."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
candle['custom_attr'] = 42
|
|
assert candle['custom_attr'] == 42
|
|
assert candle.custom_attr == 42
|
|
|
|
def test_iter(self):
|
|
"""Test __iter__ for iterating over attributes."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
items = dict(candle)
|
|
assert 'open' in items
|
|
assert 'close' in items
|
|
assert items['open'] == 100
|
|
|
|
def test_keys(self):
|
|
"""Test keys method."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
keys = candle.keys()
|
|
assert 'open' in keys
|
|
assert 'high' in keys
|
|
assert 'low' in keys
|
|
assert 'close' in keys
|
|
|
|
def test_values(self):
|
|
"""Test values method."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
values = list(candle.values())
|
|
assert 100 in values
|
|
assert 105 in values
|
|
|
|
def test_set_attributes(self):
|
|
"""Test set_attributes method."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
candle.set_attributes(ema=20, sma=50)
|
|
assert candle.ema == 20
|
|
assert candle.sma == 50
|
|
|
|
def test_dict_method(self):
|
|
"""Test dict method."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
result = candle.dict()
|
|
assert 'open' in result
|
|
assert result['open'] == 100
|
|
|
|
def test_dict_exclude(self):
|
|
"""Test dict method with exclude parameter."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
result = candle.dict(exclude={'time', 'Index'})
|
|
assert 'time' not in result
|
|
assert 'Index' not in result
|
|
assert 'open' in result
|
|
|
|
def test_dict_include(self):
|
|
"""Test dict method with include parameter."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
result = candle.dict(include={'open', 'close'})
|
|
assert set(result.keys()) == {'open', 'close'}
|
|
|
|
def test_to_series(self):
|
|
"""Test to_series method."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
series = candle.to_series()
|
|
assert isinstance(series, Series)
|
|
assert series['open'] == 100
|
|
assert 'Index' not in series.index
|
|
assert 'index' not in series.index
|
|
|
|
|
|
class TestCandle:
|
|
"""Test Candle class specific functionality."""
|
|
|
|
def test_init_required_args(self):
|
|
"""Test Candle requires open, high, low, close."""
|
|
with pytest.raises(ValueError):
|
|
Candle(open=100, high=110, low=95) # Missing close
|
|
|
|
def test_init_with_defaults(self):
|
|
"""Test Candle initializes defaults for optional attributes."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
assert hasattr(candle, 'time')
|
|
assert hasattr(candle, 'Index')
|
|
assert hasattr(candle, 'index')
|
|
assert hasattr(candle, 'volume')
|
|
assert hasattr(candle, 'spread')
|
|
|
|
def test_init_with_custom_time(self):
|
|
"""Test Candle with custom time."""
|
|
custom_time = 1609459200.0 # 2021-01-01 00:00:00
|
|
candle = Candle(open=100, high=110, low=95, close=105, time=custom_time)
|
|
assert candle.time == custom_time
|
|
|
|
def test_init_with_volume(self):
|
|
"""Test Candle with volume attributes."""
|
|
candle = Candle(
|
|
open=100, high=110, low=95, close=105,
|
|
tick_volume=1000, real_volume=500
|
|
)
|
|
assert candle.tick_volume == 1000
|
|
assert candle.real_volume == 500
|
|
assert candle.volume == 500 # Uses real_volume if available
|
|
|
|
def test_init_volume_fallback(self):
|
|
"""Test volume falls back to tick_volume."""
|
|
candle = Candle(
|
|
open=100, high=110, low=95, close=105,
|
|
tick_volume=1000, real_volume=0
|
|
)
|
|
assert candle.volume == 1000
|
|
|
|
def test_repr(self):
|
|
"""Test Candle __repr__ includes all attributes."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
repr_str = repr(candle)
|
|
assert "Index=" in repr_str
|
|
assert "time=" in repr_str
|
|
assert "open=" in repr_str
|
|
assert "index=" in repr_str
|
|
|
|
def test_hash_includes_time(self):
|
|
"""Test Candle hash includes time."""
|
|
c1 = Candle(open=100, high=110, low=95, close=105, time=1000)
|
|
c2 = Candle(open=100, high=110, low=95, close=105, time=2000)
|
|
# Same OHLC but different time should have different hash
|
|
assert hash(c1) != hash(c2)
|
|
|
|
def test_index_is_timestamp(self):
|
|
"""Test index is a Pandas Timestamp."""
|
|
candle = Candle(open=100, high=110, low=95, close=105)
|
|
assert isinstance(candle.index, Timestamp)
|
|
|
|
|
|
class TestCandles:
|
|
"""Test Candles container class."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def candles(self, mt):
|
|
"""Create candles from MetaTrader data."""
|
|
start = datetime(day=5, month=10, year=2023)
|
|
rates = await mt.copy_rates_from("BTCUSD", mt.TIMEFRAME_H1, start, 200)
|
|
return Candles(data=rates)
|
|
|
|
@pytest.fixture(scope="class")
|
|
async def candles_2(self, mt):
|
|
"""Create another set of candles for merge tests."""
|
|
start = datetime(day=5, month=10, year=2023)
|
|
rates = await mt.copy_rates_from("BTCUSD", mt.TIMEFRAME_H1, start, 300)
|
|
return Candles(data=rates)
|
|
|
|
@pytest.fixture
|
|
def sample_candles(self):
|
|
"""Create sample candles from DataFrame for non-async tests."""
|
|
data = pd.DataFrame({
|
|
'time': [1609459200.0 + i * 3600 for i in range(10)],
|
|
'open': [100 + i for i in range(10)],
|
|
'high': [105 + i for i in range(10)],
|
|
'low': [95 + i for i in range(10)],
|
|
'close': [102 + i for i in range(10)],
|
|
'tick_volume': [1000 + i * 100 for i in range(10)],
|
|
'real_volume': [500 + i * 50 for i in range(10)],
|
|
'spread': [1 for _ in range(10)],
|
|
})
|
|
return Candles(data=data)
|
|
|
|
def test_init_from_dataframe(self, sample_candles):
|
|
"""Test Candles creation from DataFrame."""
|
|
assert len(sample_candles) == 10
|
|
assert isinstance(sample_candles.data, DataFrame)
|
|
|
|
def test_init_from_candles(self, sample_candles):
|
|
"""Test Candles creation from another Candles object."""
|
|
new_candles = Candles(data=sample_candles)
|
|
assert len(new_candles) == len(sample_candles)
|
|
|
|
def test_init_from_iterable(self):
|
|
"""Test Candles creation from iterable."""
|
|
data = [
|
|
{'time': 1609459200.0, 'open': 100, 'high': 105, 'low': 95, 'close': 102},
|
|
{'time': 1609462800.0, 'open': 102, 'high': 108, 'low': 100, 'close': 105},
|
|
]
|
|
candles = Candles(data=data)
|
|
assert len(candles) == 2
|
|
|
|
def test_init_flip(self, sample_candles):
|
|
"""Test Candles with flip=True reverses order."""
|
|
flipped = Candles(data=sample_candles.data, flip=True)
|
|
# First candle in flipped should be last in original
|
|
assert flipped[0].open == sample_candles[-1].open
|
|
|
|
def test_init_custom_candle_class(self, sample_candles):
|
|
"""Test Candles with custom candle class."""
|
|
class CustomCandle(CandleBase):
|
|
def __init__(self, **kwargs):
|
|
self.open = kwargs['open']
|
|
self.high = kwargs['high']
|
|
self.low = kwargs['low']
|
|
self.close = kwargs['close']
|
|
self.time = kwargs.get('time', 0)
|
|
self.Index = kwargs.get('Index', 0)
|
|
self.index = kwargs.get('index', Timestamp.now())
|
|
|
|
candles = Candles(data=sample_candles.data, candle_class=CustomCandle)
|
|
assert isinstance(candles[0], CustomCandle)
|
|
|
|
def test_repr(self, sample_candles):
|
|
"""Test __repr__ returns DataFrame repr."""
|
|
repr_str = repr(sample_candles)
|
|
assert 'open' in repr_str
|
|
|
|
def test_len(self, sample_candles):
|
|
"""Test __len__ returns correct count."""
|
|
assert len(sample_candles) == 10
|
|
|
|
def test_contains(self, sample_candles):
|
|
"""Test __contains__ checks candle presence."""
|
|
candle = sample_candles[0]
|
|
assert candle in sample_candles
|
|
|
|
def test_getitem_int(self, sample_candles):
|
|
"""Test __getitem__ with integer index."""
|
|
candle = sample_candles[0]
|
|
assert isinstance(candle, Candle)
|
|
assert candle.Index == 0
|
|
|
|
def test_getitem_negative_int(self, sample_candles):
|
|
"""Test __getitem__ with negative index."""
|
|
candle = sample_candles[-1]
|
|
assert isinstance(candle, Candle)
|
|
assert candle.Index == 9
|
|
|
|
def test_getitem_slice(self, sample_candles):
|
|
"""Test __getitem__ with slice."""
|
|
sliced = sample_candles[2:5]
|
|
assert isinstance(sliced, Candles)
|
|
assert len(sliced) == 3
|
|
|
|
def test_getitem_str(self, sample_candles):
|
|
"""Test __getitem__ with string column name."""
|
|
series = sample_candles['open']
|
|
assert isinstance(series, pd.Series)
|
|
assert len(series) == 10
|
|
|
|
def test_getitem_index_str(self, sample_candles):
|
|
"""Test __getitem__ with 'index' string."""
|
|
index = sample_candles['index']
|
|
assert isinstance(index, pd.DatetimeIndex)
|
|
|
|
def test_getitem_Index_str(self, sample_candles):
|
|
"""Test __getitem__ with 'Index' string."""
|
|
index_series = sample_candles['Index']
|
|
assert isinstance(index_series, pd.Series)
|
|
assert list(index_series) == list(range(10))
|
|
|
|
def test_setitem(self, sample_candles):
|
|
"""Test __setitem__ adds column."""
|
|
new_series = sample_candles.open * 2
|
|
sample_candles['double_open'] = new_series
|
|
assert 'double_open' in sample_candles.data.columns
|
|
|
|
def test_getattr_column(self, sample_candles):
|
|
"""Test __getattr__ for column access."""
|
|
open_series = sample_candles.open
|
|
assert isinstance(open_series, pd.Series)
|
|
|
|
def test_getattr_index(self, sample_candles):
|
|
"""Test __getattr__ for 'index'."""
|
|
index = sample_candles.index
|
|
assert isinstance(index, pd.DatetimeIndex)
|
|
|
|
def test_getattr_Index(self, sample_candles):
|
|
"""Test __getattr__ for 'Index'."""
|
|
Index = sample_candles.Index
|
|
assert isinstance(Index, pd.Series)
|
|
|
|
def test_getattr_invalid(self, sample_candles):
|
|
"""Test __getattr__ raises AttributeError for invalid attr."""
|
|
with pytest.raises(AttributeError):
|
|
_ = sample_candles.invalid_attribute
|
|
|
|
def test_iter(self, sample_candles):
|
|
"""Test __iter__ yields Candle objects."""
|
|
candles_list = list(sample_candles)
|
|
assert len(candles_list) == 10
|
|
assert all(isinstance(c, Candle) for c in candles_list)
|
|
|
|
def test_reversed(self, sample_candles):
|
|
"""Test __reversed__ yields candles in reverse order."""
|
|
reversed_list = list(reversed(sample_candles))
|
|
assert len(reversed_list) == 10
|
|
assert reversed_list[0].Index == 9
|
|
|
|
def test_timeframe(self, sample_candles):
|
|
"""Test timeframe property detection."""
|
|
tf = sample_candles.timeframe
|
|
assert tf == TimeFrame.H1
|
|
|
|
def test_columns(self, sample_candles):
|
|
"""Test columns property."""
|
|
cols = sample_candles.columns
|
|
assert 'open' in cols
|
|
assert 'close' in cols
|
|
|
|
def test_data_property(self, sample_candles):
|
|
"""Test data property returns DataFrame."""
|
|
assert isinstance(sample_candles.data, DataFrame)
|
|
|
|
def test_rename(self, sample_candles):
|
|
"""Test rename method."""
|
|
sample_candles.rename(inplace=True, open='open_price')
|
|
assert 'open_price' in sample_candles.data.columns
|
|
|
|
def test_iadd(self, candles, candles_2):
|
|
"""Test in-place addition of candles."""
|
|
original_len = len(candles)
|
|
candles += candles_2
|
|
assert len(candles) >= original_len
|
|
|
|
def test_add(self, candles, candles_2):
|
|
"""Test addition creates new Candles object."""
|
|
combined = candles + candles_2
|
|
assert isinstance(combined, Candles)
|
|
assert len(combined) == 300
|
|
|
|
def test_add_candle(self, sample_candles):
|
|
"""Test add method with Candle object."""
|
|
length = len(sample_candles)
|
|
candle = sample_candles[-1]
|
|
now = datetime.now()
|
|
candle.time = now.timestamp() + 3600 # 1 hour later
|
|
candle.index = pd.Timestamp(candle.time, unit="s", tz=now.astimezone().tzinfo)
|
|
sample_candles.add(candle)
|
|
assert len(sample_candles) == length + 1
|
|
|
|
def test_add_series(self, sample_candles):
|
|
"""Test add method with Series object."""
|
|
length = len(sample_candles)
|
|
candle = sample_candles[-1]
|
|
now = datetime.now()
|
|
candle.time = now.timestamp() + 7200 # 2 hours later
|
|
series = candle.to_series()
|
|
sample_candles.add(series)
|
|
assert len(sample_candles) == length + 1
|
|
|
|
def test_add_dataframe(self, sample_candles):
|
|
"""Test add method with DataFrame object."""
|
|
length = len(sample_candles)
|
|
new_data = pd.DataFrame({
|
|
'time': [datetime.now().timestamp() + 10800],
|
|
'open': [200],
|
|
'high': [210],
|
|
'low': [190],
|
|
'close': [205],
|
|
})
|
|
sample_candles.add(new_data)
|
|
assert len(sample_candles) == length + 1
|
|
|
|
def test_add_invalid_type(self, sample_candles):
|
|
"""Test add method raises TypeError for invalid input."""
|
|
with pytest.raises(TypeError):
|
|
sample_candles.add("invalid")
|
|
|
|
def test_ta_accessor(self, sample_candles):
|
|
"""Test ta property for pandas_ta access."""
|
|
ta = sample_candles.ta
|
|
assert ta is not None
|
|
|
|
def test_ta_lib_accessor(self, sample_candles):
|
|
"""Test ta_lib property returns pandas_ta_classic module."""
|
|
assert sample_candles.ta_lib is ta
|
|
|
|
def test_get_candle(self, candles):
|
|
"""Test getting single candle from live data."""
|
|
candle = candles[10]
|
|
assert isinstance(candle, Candle)
|
|
assert candle in candles
|
|
|
|
def test_slice_live(self, candles):
|
|
"""Test slicing live data."""
|
|
sliced = candles[10:15]
|
|
assert len(sliced) == 5
|
|
assert isinstance(sliced, Candles)
|
|
|
|
def test_timeframe_live(self, candles):
|
|
"""Test timeframe detection on live data."""
|
|
tf = candles.timeframe
|
|
assert tf == TimeFrame.H1
|
|
|
|
def test_ta_and_rename_live(self, candles):
|
|
"""Test ta operations on live data."""
|
|
ema = candles.ta.ema(close="open", length=10, append=True)
|
|
assert "EMA_10" in candles.data.columns
|
|
candles.rename(inplace=True, EMA_10="ema")
|
|
assert "ema" in candles.data.columns
|
|
|
|
def test_ta_lib_live(self, candles):
|
|
"""Test ta_lib operations on live data."""
|
|
fas = candles.ta_lib.above(candles.open, candles.close)
|
|
assert isinstance(fas, pd.Series)
|
|
candles["fas"] = fas
|
|
assert "fas" in candles.data.columns
|
|
|
|
|
|
class TestCandleComparisonsWithDict:
|
|
"""Test candle comparisons with dict-like objects."""
|
|
|
|
def test_equal_to_dict(self):
|
|
"""Test candle equality with dict."""
|
|
candle = Candle(open=100, high=110, low=95, close=108)
|
|
candle_dict = {'open': 100, 'high': 110, 'low': 95, 'close': 108}
|
|
assert candle == candle_dict
|
|
|
|
def test_less_than_dict(self):
|
|
"""Test candle less than comparison with dict."""
|
|
candle = Candle(open=100, high=105, low=98, close=102) # body=2, range=7
|
|
candle_dict = {'open': 100, 'high': 110, 'low': 90, 'close': 108} # body=8, range=20
|
|
assert candle < candle_dict
|
|
|
|
def test_greater_than_dict(self):
|
|
"""Test candle greater than comparison with dict."""
|
|
candle = Candle(open=100, high=120, low=80, close=115) # body=15, range=40
|
|
candle_dict = {'open': 100, 'high': 105, 'low': 98, 'close': 102} # body=2, range=7
|
|
assert candle > candle_dict
|
|
|
|
|
|
class TestCandleSorting:
|
|
"""Test candle sorting functionality."""
|
|
|
|
def test_sort_candles(self):
|
|
"""Test sorting candles by body and range."""
|
|
c1 = Candle(open=100, high=105, low=98, close=102) # body=2, range=7
|
|
c2 = Candle(open=100, high=110, low=90, close=108) # body=8, range=20
|
|
c3 = Candle(open=100, high=103, low=99, close=101) # body=1, range=4
|
|
|
|
candles = [c2, c1, c3]
|
|
sorted_candles = sorted(candles)
|
|
|
|
assert sorted_candles[0].candle_body == 1 # c3
|
|
assert sorted_candles[1].candle_body == 2 # c1
|
|
assert sorted_candles[2].candle_body == 8 # c2
|
|
|
|
def test_candles_in_set(self):
|
|
"""Test using candles in a set."""
|
|
c1 = Candle(open=100, high=110, low=95, close=108)
|
|
c2 = Candle(open=102, high=112, low=97, close=110) # Same key as c1
|
|
|
|
candle_set = {c1, c2}
|
|
# Both have same hash, so set should contain only one
|
|
assert len(candle_set) <= 2
|