Skip to content

Commit

Permalink
CandlestickType can be added from str mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
MerlinR committed Feb 4, 2024
1 parent 3d3fad3 commit 1f2150c
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html
- Added CandlestickType
- CandlestickType modular parent to convert candlesticks to alt types,
- E.G Auto convert candles to heikin-ashi
- Can be added as str 'candlestick_type="ha"' for heikin-ashi
- Converted Candle to Class from Dataclass
- Added Many Candle anaylsis methods to Candle:
- Positive, Negative, realbody, shadow_upper, shadow_lower, high_low
Expand Down
14 changes: 8 additions & 6 deletions hexital/core/hexital.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from hexital.core.indicator import Indicator
from hexital.exceptions import InvalidAnalysis, InvalidIndicator, MissingIndicator, MixedTimeframes
from hexital.indicators import INDICATOR_MAP
from hexital.utils.candlesticks import reading_by_index
from hexital.utils.candlesticks import reading_by_index, verify_candlesticktype
from hexital.utils.timeframe import TimeFrame


Expand All @@ -32,7 +32,7 @@ def __init__(
timeframe: Optional[str | TimeFrame] = None,
timeframe_fill: bool = False,
candles_lifespan: Optional[timedelta] = None,
candlestick_type: Optional[CandlestickType] = None,
candlestick_type: Optional[CandlestickType | str] = None,
):
self.name = name
self.description = description
Expand All @@ -44,15 +44,17 @@ def __init__(

self.timeframe_fill = timeframe_fill
self.candles_lifespan = candles_lifespan
self.candlestick_type = candlestick_type

if candlestick_type:
self.candlestick_type = verify_candlesticktype(candlestick_type)

self._candles = {
DEFAULT_CANDLES: CandleManager(
candles if isinstance(candles, list) else [],
candles_lifespan=candles_lifespan,
candles_lifespan=self.candles_lifespan,
timeframe=self.timeframe,
timeframe_fill=timeframe_fill,
candlestick_type=candlestick_type,
timeframe_fill=self.timeframe_fill,
candlestick_type=self.candlestick_type,
)
}

Expand Down
8 changes: 6 additions & 2 deletions hexital/core/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
reading_by_candle,
reading_count,
reading_period,
verify_candlesticktype,
)
from hexital.utils.indexing import round_values
from hexital.utils.timeframe import TimeFrame
Expand All @@ -29,7 +30,7 @@ class Indicator(ABC):
timeframe: Optional[str | TimeFrame] = None
timeframe_fill: bool = False
candles_lifespan: Optional[timedelta] = None
candlestick_type: Optional[CandlestickType] = None
candlestick_type: Optional[CandlestickType | str] = None

_candles: CandleManager = field(init=False, default_factory=CandleManager)
_output_name: str = field(init=False, default="")
Expand All @@ -47,12 +48,15 @@ def __post_init__(self):
elif isinstance(self.timeframe, TimeFrame):
self.timeframe = self.timeframe.value

if self.candlestick_type:
self.candlestick_type = verify_candlesticktype(self.candlestick_type)

self._candles = CandleManager(
self.candles,
self.candles_lifespan,
self.timeframe,
self.timeframe_fill,
self.candlestick_type,
self.candlestick_type if self.candlestick_type else None,
)

self.candles = self._candles.candles
Expand Down
5 changes: 5 additions & 0 deletions hexital/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ def __init__(self, message):
class CandleAlreadyTagged(Exception):
def __init__(self, message):
super().__init__(message)


class InvalidCandlestickType(Exception):
def __init__(self, message):
super().__init__(message)
17 changes: 17 additions & 0 deletions hexital/utils/candlesticks.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
from itertools import chain
from typing import List, Optional

from hexital.candlesticks import CANDLESTICK_MAP
from hexital.core.candle import Candle
from hexital.core.candlestick_type import CandlestickType
from hexital.exceptions import InvalidCandlestickType
from hexital.utils.indexing import absindex, valid_index


def verify_candlesticktype(
candlestick_type: CandlestickType | str | None,
) -> CandlestickType | None:
if isinstance(candlestick_type, CandlestickType):
return candlestick_type
elif isinstance(candlestick_type, str):
if not CANDLESTICK_MAP.get(candlestick_type):
raise InvalidCandlestickType(f"Candlestick type {candlestick_type} is Invalid")
requested_candlesticks = CANDLESTICK_MAP[candlestick_type]
return requested_candlesticks()

return None


def reading_by_index(candles: List[Candle], name: str, index: int = -1) -> float | dict | None:
"""Simple method to get a reading from the given indicator from it's index"""
if not valid_index(index, len(candles)):
Expand Down
26 changes: 25 additions & 1 deletion tests/core/test_hexital.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@

import pytest
from hexital import Candle, Hexital, TimeFrame
from hexital.candlesticks.heikinashi import HeikinAshi
from hexital.core.indicator import Indicator
from hexital.exceptions import InvalidAnalysis, InvalidIndicator, MissingIndicator, MixedTimeframes
from hexital.exceptions import (
InvalidAnalysis,
InvalidCandlestickType,
InvalidIndicator,
MissingIndicator,
MixedTimeframes,
)
from hexital.indicators import EMA, SMA


Expand Down Expand Up @@ -325,3 +332,20 @@ def test_hextial_movement(self, candles):
)
strat.calculate()
assert strat.has_reading("EMA_10") and strat.has_reading("Chained")


class TestCandlestickType:
@pytest.mark.usefixtures("candles")
def test_hextial_candlestick_type(self, candles):
strat = Hexital("Test Stratergy", candles, [EMA()], candlestick_type=HeikinAshi())
assert isinstance(strat.candlestick_type, HeikinAshi)

@pytest.mark.usefixtures("candles")
def test_hextial_candlestick_type_str(self, candles):
strat = Hexital("Test Stratergy", candles, [EMA()], candlestick_type="ha")
assert isinstance(strat.candlestick_type, HeikinAshi)

@pytest.mark.usefixtures("candles")
def test_hextial_candlestick_type_error(self, candles):
with pytest.raises(InvalidCandlestickType):
strat = Hexital("Test Stratergy", candles, [EMA()], candlestick_type="FUCK")
16 changes: 16 additions & 0 deletions tests/core/test_indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

import pytest
from hexital import Candle
from hexital.candlesticks.heikinashi import HeikinAshi
from hexital.core.indicator import Indicator
from hexital.exceptions import InvalidCandlestickType
from hexital.indicators.amorph import Amorph


Expand Down Expand Up @@ -267,3 +269,17 @@ def test_indicator_movement_candle(self, minimal_candles):
test_indicator = FakeIndicator(candles=minimal_candles)
test_indicator.calculate()
assert test_indicator.rising("close") is False


class TestCandlestickType:
def test_indicator_candlestick_type(self):
test_indicator = FakeIndicator(candles=[], candlestick_type=HeikinAshi())
assert isinstance(test_indicator.candlestick_type, HeikinAshi)

def test_indicator_candlestick_type_str(self):
test_indicator = FakeIndicator(candles=[], candlestick_type="ha")
assert isinstance(test_indicator.candlestick_type, HeikinAshi)

def test_indicator_candlestick_type_error(self):
with pytest.raises(InvalidCandlestickType):
test_indicator = FakeIndicator(candles=[], candlestick_type="FUCK")

0 comments on commit 1f2150c

Please sign in to comment.