TA-Lib / ta-lib-python

Python wrapper for TA-Lib (http://ta-lib.org/).
http://ta-lib.github.io/ta-lib-python
Other
9.56k stars 1.75k forks source link

Candle pattern 'Shooting Star' is not detected #647

Open Tzion opened 5 months ago

Tzion commented 5 months ago

The pattern 'shooting star' is not detected on sample data (talib.CDLSHOOTINGSTAR)

Here is a test file to reproduce it - you can run it directly and see the plot printed to the screen. The shooting star candle is the second from the end.

You can run this simple python file and see that there's no detection and the pattern on the chart:

import unittest
import talib
import numpy as np
import pandas as pd
import pandas_ta as pta

import mplfinance as mpf
import pandas as pd

def plot_candles_simple(candles):
    df = pd.DataFrame(candles)

    # Convert the index to a datetime index (required by mplfinance)
    if df.index.dtype != 'datetime64[ns]':
        df.index = pd.date_range(start=pd.Timestamp.today().strftime('%Y-%m-%d'), periods=len(df))

    mpf.plot(df, type='candle', style='charles', title='Candlestick Chart', ylabel='Price')

class TestgCandlesRegocnition(unittest.TestCase):

    def verify_pattern(self, pattern_func, candles, detection_idx=-1, force_chart=False, **pattern_func_args):
        open_prices = pd.Series(candles['open'])
        high_prices = pd.Series(candles['high'])
        low_prices = pd.Series(candles['low'])
        close_prices = pd.Series(candles['close'])
        pattern_detection = pattern_func(open_prices, high_prices, low_prices, close_prices, **pattern_func_args)
        last_candle_detection = pattern_detection.iloc[detection_idx] != 0

        if not last_candle_detection:
            plot_candles_simple(candles)
            self.fail("Pattern was not detected")
            print(pattern_detection)
        if force_chart:
            plot_candles_simple(candles)

    def test_shooting_star(self):
        candles = {
            'open': [8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 7.9, 8.2, 9.4, 9],
            'high': [8.8, 8.8, 8.8, 8.8, 8.8, 8.8, 8.8, 8.8, 9.0, 9.0, 11.9, 9.0],
            'low': [7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.8, 7.9, 9.2, 8.0],
            'close': [8.5, 8.7, 8.7, 8.7, 8.7, 8.7, 8.7, 8.7, 8.1, 9.0, 9.2, 8.0]
        }
        self.verify_pattern(talib.CDLSHOOTINGSTAR, candles, detection_idx=-2)

if __name__ == '__main__':
    unittest.main()

This is the chart:

image

I followed and made sure that the definition of my candles are correct according to the documentation in the C source code of the Shooting star pattern:

   /* Proceed with the calculation for the requested range.
    * Must have:
    * - small real body
    * - long upper shadow
    * - no, or very short, lower shadow
    * - gap up from prior real body
    * The meaning of "short", "very short" and "long" is specified with TA_SetCandleSettings;
    * outInteger is negative (-1 to -100): shooting star is always bearish;
    * the user should consider that a shooting star must appear in an uptrend, while this function does not consider it
    */
   outIdx = 0;

And I also made sure that the candles follows the default global definitions:

const TA_CandleSetting TA_CandleDefaultSettings[] = {
        /* real body is long when it's longer than the average of the 10 previous candles' real body */
        { TA_BodyLong, TA_RangeType_RealBody, 10, 1.0 },
        /* real body is very long when it's longer than 3 times the average of the 10 previous candles' real body */
        { TA_BodyVeryLong, TA_RangeType_RealBody, 10, 3.0 },
        /* real body is short when it's shorter than the average of the 10 previous candles' real bodies */
        { TA_BodyShort, TA_RangeType_RealBody, 10, 1.0 },
        /* real body is like doji's body when it's shorter than 10% the average of the 10 previous candles' high-low range */
        { TA_BodyDoji, TA_RangeType_HighLow, 10, 0.1 },
        /* shadow is long when it's longer than the real body */
        { TA_ShadowLong, TA_RangeType_RealBody, 0, 1.0 },
        /* shadow is very long when it's longer than 2 times the real body */
        { TA_ShadowVeryLong, TA_RangeType_RealBody, 0, 2.0 },
        /* shadow is short when it's shorter than half the average of the 10 previous candles' sum of shadows */
        { TA_ShadowShort, TA_RangeType_Shadows, 10, 1.0 },
        /* shadow is very short when it's shorter than 10% the average of the 10 previous candles' high-low range */
        { TA_ShadowVeryShort, TA_RangeType_HighLow, 10, 0.1 },
        /* when measuring distance between parts of candles or width of gaps */
        /* "near" means "<= 20% of the average of the 5 previous candles' high-low range" */
        { TA_Near, TA_RangeType_HighLow, 5, 0.2 },
        /* when measuring distance between parts of candles or width of gaps */
        /* "far" means ">= 60% of the average of the 5 previous candles' high-low range" */
        { TA_Far, TA_RangeType_HighLow, 5, 0.6 },
        /* when measuring distance between parts of candles or width of gaps */
        /* "equal" means "<= 5% of the average of the 5 previous candles' high-low range" */
        { TA_Equal, TA_RangeType_HighLow, 5, 0.05 }
    };
mrjbq7 commented 4 months ago

So you think the candle is defined in such a way that C code should trigger and it isn't? Or are you wondering if there is a definition issue where the example prices don't trigger and should?

Tzion commented 4 months ago

I think that the C code doesn't trigger the pattern when it should - according to the pattern describe in the documentation

Tzion commented 4 months ago

It is solved when I'm adding another candle from the left:

        candles = {
            'open': pd.Series([8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 7.9, 8.2, 9.4, 9.0]),
            'high': pd.Series([8.8, 8.8, 8.8, 8.8, 8.8, 8.8, 8.8, 8.8, 9.0, 9.0, 10.5, 9.0]),
            'low': pd.Series([7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.5, 7.8, 7.9, 9.2, 8.0]),
            'close': pd.Series([8.5, 8.7, 8.7, 8.7, 8.7, 8.7, 8.7, 8.7, 8.1, 9.0, 9.2, 8.0])
        }

So the issue is about the definitions: Instead of average of the 10 previous candles' in practice it's checking the average of the 11 previous candles'

How can I change the global candles settings (TA_CandleSetting) when using the python wrapper?

mrjbq7 commented 4 months ago

What do you need to change?

We currently expose that through a talib._ta_lib._ta_set_candle_settings function.

Tzion commented 4 months ago

Thanks @mrjbq7

it is minor bug of off-by-one - the calculation should be over 10 candles and not 11.