├── .gitignore ├── README.md ├── __init__.py ├── chart_patterns ├── __init__.py ├── charts_utils.py ├── doubles.py ├── flag.py ├── head_and_shoulders.py ├── inverse_head_and_shoulders.py ├── pennant.py ├── pivot_points.py ├── plotting.py ├── triangles.py └── utils.py ├── data └── eurusd-4h.csv ├── poetry.lock ├── pyproject.toml ├── requirements.txt └── tests ├── __init__.py ├── test_double.py ├── test_flag.py ├── test_head_and_shoulders.py ├── test_inverse_head_and_shoulders.py ├── test_pennant.py └── test_triangle.py /.gitignore: -------------------------------------------------------------------------------- 1 | # sphinx build folder 2 | _build 3 | 4 | # Compiled source # 5 | ################### 6 | *.com 7 | *.class 8 | *.dll 9 | *.exe 10 | *.o 11 | *.so 12 | 13 | # Packages # 14 | ############ 15 | # it's better to unpack these files and commit the raw source 16 | # git has its own built in compression methods 17 | *.7z 18 | *.dmg 19 | *.gz 20 | *.iso 21 | *.jar 22 | *.rar 23 | *.tar 24 | *.zip 25 | 26 | # Logs and databases # 27 | ###################### 28 | *.log 29 | *.sql 30 | *.sqlite 31 | 32 | # OS generated files # 33 | ###################### 34 | .DS_Store? 35 | ehthumbs.db 36 | Icon? 37 | Thumbs.db 38 | 39 | # Editor backup files # 40 | ####################### 41 | *~ 42 | 43 | __pycache__/ 44 | # Virtual environment # 45 | ####################### 46 | venv/ 47 | 48 | images/ 49 | chart_patterns/__pycache__ 50 | tests/__pycache__ 51 | docs/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChartPatterns - Automate the detection of chart patterns with Python 2 | 3 | Make it easy to detect well-known chart patterns such as ascending triangles, head and shoulders, flag, etc with the `chart_patterns` python 4 | library. Like Thomas Bulkowski says in his book, `Encyclopedia of Chart Patterns`, "To knowledgeable investors, chart patterns are not squiggles on a price chart; they are the footprints of the smart money." This python package hopefully helps in this regard. 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Available Chart Patterns](#available-chart-patterns) 10 | * [Installation](#installation) 11 | * [Getting Started](#getting-started) 12 | * [Doubles](#doubles) 13 | * [Flag](#flag) 14 | * [Head and Shoulders](#head-and-shoulders) 15 | * [Inverse Head and Shoulders](#inverse-head-and-shoulders) 16 | * [Triangles](#triangles) 17 | * [Pennant](#pennant) 18 | * [Resources](#resources) 19 | 20 | 21 | ## Available Chart Patterns 22 | 23 | Here is a list of available chart patterns: 24 | 25 | - [x] Doubles 26 | 27 | - [x] Flag 28 | 29 | - [x] Head and Shoulders 30 | 31 | - [x] Inverse Head and Shoulders 32 | 33 | - [x] Triangles 34 | 35 | - [x] Pennant 36 | 37 | 38 | ## Installation 39 | 40 | Install the `chart_patterns` package by cloning this repo. Place the folder in your working directory. 41 | 42 | > git clone https://github.com/zeta-zetra/chart_patterns 43 | 44 | Make sure to install the required packages using the requirements.txt file. 45 | 46 | > pip install -r requirements.txt 47 | 48 | ## Getting Started 49 | 50 | Once you have installed the package, then you can get started. We provide detailed examples of each of the available chart patterns in the package. 51 | 52 | ### Doubles 53 | 54 | ``` 55 | import pandas as pd 56 | from chart_patterns.chart_patterns.doubles import find_doubles_pattern 57 | from chart_patterns.chart_patterns.plotting import display_chart_pattern 58 | 59 | 60 | 61 | # read in your ohlc data 62 | ohlc = pd.read_csv("eurusd-4h.csv") #headers include - open, high, low, close 63 | 64 | # Find the double bottom pattern 65 | ohlc = find_doubles_pattern(ohlc, double="bottoms") 66 | 67 | # Find the double tops pattern 68 | ohlc = find_doubles_pattern(ohlc, double="tops") 69 | 70 | 71 | # Plot the results 72 | display_chart_pattern(ohlc, pattern="double") # If multiple patterns were found, then plots will saved inside a folder named images/double 73 | 74 | ``` 75 | 76 | In the `find_doubles_pattern` function one can change the following: 77 | - The maximum ratio between the peak points in the tops chart pattern. See the `tops_max_ratio` parameter. 78 | - The minimum ratio between the trough points in the bottoms chart pattern. See the `bottoms_min_ratio` parameter. 79 | 80 | ### Flag 81 | 82 | ``` 83 | import pandas as pd 84 | from chart_patterns.chart_patterns.flag import find_flag_pattern 85 | from chart_patterns.chart_patterns.plotting import display_chart_pattern 86 | 87 | # read in your ohlc data 88 | ohlc = pd.read_csv("eurusd-4h.csv") #headers include - open, high, low, close 89 | 90 | # Plot the results 91 | display_chart_pattern(ohlc, pattern="flag") # If multiple patterns were found, then plots will saved inside a folder named images/flag 92 | 93 | ``` 94 | 95 | ### Head and Shoulders 96 | 97 | ``` 98 | import pandas as pd 99 | from chart_patterns.chart_patterns.head_and_shoulders import find_head_and_shoulders 100 | from chart_patterns.chart_patterns.plotting import display_chart_pattern 101 | 102 | # read in your ohlc data 103 | ohlc = pd.read_csv("eurusd-4h.csv") # headers must include - open, high, low, close 104 | 105 | # Find the head and shoulers pattern 106 | ohlc = find_head_and_shoulders(ohlc) 107 | 108 | # Plot the results 109 | display_chart_pattern(ohlc, pattern="hs") # If multiple patterns were found, then plots will saved inside a folder named images/hs 110 | 111 | 112 | ``` 113 | 114 | 115 | ### Inverse Head and Shoulders 116 | 117 | ``` 118 | import pandas as pd 119 | from chart_patterns.chart_patterns.inverse_head_and_shoulders import find_inverse_head_and_shoulders 120 | from chart_patterns.chart_patterns.plotting import display_chart_pattern 121 | 122 | # read in your ohlc data 123 | ohlc = pd.read_csv("eurusd-4h.csv") # headers must include - open, high, low, close 124 | 125 | # Find inversr head and shoulders 126 | ohlc = find_inverse_head_and_shoulders(ohlc) 127 | 128 | # Plot the results 129 | display_chart_pattern(ohlc, pattern="ihs") # If multiple patterns were found, then plots will saved inside a folder named images/ihs 130 | ``` 131 | 132 | 133 | ### Triangles 134 | 135 | ``` 136 | import pandas as pd 137 | from chart_patterns.chart_patterns.triangles import find_triangle_pattern 138 | from chart_patterns.chart_patterns.plotting import display_chart_pattern 139 | 140 | # read in your ohlc data 141 | ohlc = pd.read_csv("eurusd-4h.csv") # headers must include - open, high, low, close 142 | 143 | # Find the ascending triangle 144 | ohlc = find_triangle_pattern(ohlc) 145 | 146 | # Find the descending triangle 147 | ohlc = find_triangle_pattern(ohlc, triangle_type="descending") 148 | 149 | # Find the symmetrical triangle 150 | ohlc = find_triangle_pattern(ohlc, triangle_type="symmetrical") 151 | 152 | # Plot the results 153 | display_chart_pattern(ohlc, pattern="triangle") # If multiple patterns were found, then plots will saved inside a folder named images/triangle 154 | 155 | ``` 156 | 157 | 158 | ### Pennant 159 | 160 | ``` 161 | import pandas as pd 162 | from chart_patterns.chart_patterns.pennant import find_pennant 163 | from chart_patterns.chart_patterns.plotting import display_chart_pattern 164 | 165 | # read in your ohlc data 166 | ohlc = pd.read_csv("eurusd-4h.csv") # headers must include - open, high, low, close 167 | 168 | # Find the head and shoulers pattern 169 | ohlc = find_pennant(ohlc, progress=True) 170 | 171 | 172 | # Plot the results 173 | display_chart_pattern(ohlc, pattern="pennant") 174 | 175 | ``` 176 | 177 | 178 | ## Resources 179 | 180 | We have a [YouTube channel](https://www.youtube.com/@zetratrading/featured) where we go through the code of the chart patterns. In addition, we have a git [repo](https://github.com/zeta-zetra/code#automate-chart-patterns) with extra code covering other trading related material. -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeta-zetra/chart_patterns/44f8baa3b15dfa6b9d54b51d2307c85a255d3a40/__init__.py -------------------------------------------------------------------------------- /chart_patterns/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeta-zetra/chart_patterns/44f8baa3b15dfa6b9d54b51d2307c85a255d3a40/chart_patterns/__init__.py -------------------------------------------------------------------------------- /chart_patterns/charts_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Date : 2024-01-09 3 | Author: Zetra Team 4 | 5 | """ 6 | 7 | import numpy as np 8 | import pandas as pd 9 | 10 | 11 | from typing import Tuple 12 | 13 | 14 | def find_points(ohlc: pd.DataFrame, candle_idx: int, lookback: int) -> Tuple[np.array, np.array, np.array, int, int, int, int]: 15 | """ 16 | Find points provides all the necessary arrays and data of interest 17 | 18 | 19 | :params ohlc is the OHLC dataframe that has the pivot points 20 | :type :pd.DataFrame 21 | 22 | :params candle_idx is the candlestick index of interest 23 | :type :int 24 | 25 | :params lookback is the number of back candlesticks to use 26 | :type :int 27 | 28 | :return (Tuple[np.array, np.array, np.array, int, int, int, int]) 29 | """ 30 | 31 | maxim = np.array([]) 32 | minim = np.array([]) 33 | xxmin = np.array([]) 34 | xxmax = np.array([]) 35 | minbcount = 0 #minimas before head 36 | maxbcount = 0 #maximas before head 37 | minacount = 0 #minimas after head 38 | maxacount = 0 #maximas after head 39 | half_lookback = int(lookback/2) 40 | 41 | idx = candle_idx - half_lookback 42 | for i in range(idx-half_lookback, idx + half_lookback): 43 | if ohlc.loc[i,"short_pivot"] == 1: 44 | minim = np.append(minim, ohlc.loc[i, "low"]) 45 | xxmin = np.append(xxmin, i) 46 | if i < idx: 47 | minbcount+=1 48 | elif i> idx: 49 | minacount+=1 50 | if ohlc.loc[i, "short_pivot"] == 2: 51 | maxim = np.append(maxim, ohlc.loc[i, "high"]) 52 | xxmax = np.append(xxmax, i) 53 | if i < idx: 54 | maxbcount+=1 55 | elif i> idx: 56 | maxacount+=1 57 | 58 | return maxim, minim, xxmax, xxmin, maxacount, minacount, maxbcount, minbcount -------------------------------------------------------------------------------- /chart_patterns/doubles.py: -------------------------------------------------------------------------------- 1 | """ 2 | Date : 2023-01-06 3 | Author : Zetra Team 4 | Function used to find the Double chart patterns 5 | """ 6 | 7 | import numpy as np 8 | import pandas as pd 9 | import plotly.graph_objects as go 10 | 11 | 12 | from chart_patterns.chart_patterns.pivot_points import find_all_pivot_points 13 | from tqdm import tqdm 14 | 15 | def find_doubles_pattern(ohlc: pd.DataFrame, lookback: int = 25, double: str = "tops", 16 | tops_max_ratio: float = 1.01, bottoms_min_ratio: float = 0.98, 17 | progress: bool = False) -> pd.DataFrame: 18 | """ 19 | Find the Double chart patterns 20 | 21 | :params ohlc is the OHLC dataframe 22 | :type :pd.DataFrame 23 | 24 | :params lookback is the number of periods to use for back candles 25 | :type :int 26 | 27 | :params double is a options string variable of the type of doubles chart pattern that needs to be identifed. ['tops', 'bottoms', 'both'] 28 | :type :str 29 | 30 | :params tops_max_ratio is the max ratio between the peak points in the tops chart pattern 31 | :type :float 32 | 33 | params bottoms_min_ratio is the min ratio between the trough points in the bottoms chart pattern 34 | :type :float 35 | 36 | :params progress bar to be displayed or not 37 | :type:bool 38 | 39 | :return (pd.DataFrame) 40 | """ 41 | 42 | # Placeholders for the Double patterns 43 | ohlc["double_type"] = "" 44 | ohlc["chart_type"] = "" 45 | ohlc["double_idx"] = [np.array([]) for _ in range(len(ohlc)) ] 46 | ohlc["double_point"] = [np.array([]) for _ in range(len(ohlc)) ] 47 | 48 | 49 | # Find the pivot points 50 | ohlc = find_all_pivot_points(ohlc) 51 | 52 | if not progress: 53 | candle_iter = range(lookback, len(ohlc)) 54 | else: 55 | candle_iter = tqdm(range(lookback, len(ohlc)), desc=f"Finding doubles patterns...") 56 | 57 | for candle_idx in candle_iter: 58 | sub_ohlc = ohlc.loc[candle_idx-lookback: candle_idx,:] 59 | 60 | pivot_indx = [ i for i, p in zip(sub_ohlc["pivot"].index.values, sub_ohlc["pivot"].tolist()) if p != 0 ] 61 | if len(pivot_indx) != 5: 62 | continue 63 | 64 | if len(pivot_indx) == 5: # Must have only 5 pivots 65 | pivots = ohlc.loc[pivot_indx, "pivot_pos"].tolist() 66 | 67 | # Find Double Tops 68 | if double == "tops" or double == "both": 69 | if (pivots[0] < pivots[1]) and (pivots[0] < pivots[3]) and (pivots[2] < pivots[1]) and \ 70 | (pivots[2] < pivots[3]) and (pivots[4] < pivots[1]) and (pivots[4] < pivots[3]) and \ 71 | (pivots[1] > pivots[3]) and (pivots[1]/pivots[3] <= tops_max_ratio): 72 | ohlc.at[candle_idx, "double_idx"] = pivot_indx 73 | ohlc.at[candle_idx, "double_point"] = pivots 74 | ohlc.loc[candle_idx, "double_type"] = "tops" 75 | ohlc.loc[candle_idx, "chart_type"] = "double" 76 | 77 | 78 | # Find Double Bottoms 79 | elif double == "bottoms" or double == "both": 80 | if (pivots[0] > pivots[1]) and (pivots[0] > pivots[3]) and (pivots[2] > pivots[1]) and \ 81 | (pivots[2] > pivots[3]) and (pivots[4] > pivots[1]) and (pivots[4] > pivots[3]) and \ 82 | (pivots[1] < pivots[3]) and (pivots[1]/pivots[3] >= bottoms_min_ratio) : 83 | ohlc.at[candle_idx, "double_idx"] = pivot_indx 84 | ohlc.at[candle_idx, "double_point"] = pivots 85 | ohlc.loc[candle_idx, "double_type"] = "bottoms" 86 | ohlc.loc[candle_idx, "chart_type"] = "double" 87 | 88 | return ohlc 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /chart_patterns/flag.py: -------------------------------------------------------------------------------- 1 | """ 2 | Date : 2023-12-26 3 | Author: Zetra Team 4 | Function used to detect the Flag pattern 5 | """ 6 | 7 | 8 | 9 | import numpy as np 10 | import pandas as pd 11 | import plotly.graph_objects as go 12 | 13 | 14 | from chart_patterns.chart_patterns.pivot_points import find_all_pivot_points 15 | from scipy.stats import linregress 16 | from tqdm import tqdm 17 | 18 | def find_flag_pattern(ohlc: pd.DataFrame, lookback: int = 25, min_points: int = 3, 19 | r_max: float = 0.9, r_min: float = 0.9, slope_max: float = 0, slope_min: float = 0, 20 | lower_ratio_slope: float = 0.9, upper_ratio_slope: float = 1.05, 21 | progress: bool = False) -> pd.DataFrame: 22 | """ 23 | Find the flag pattern 24 | 25 | :params ohlc is the OHLC dataframe 26 | :type :pd.DataFrame 27 | 28 | :params lookback is the number of periods to use for back candles 29 | :type :int 30 | 31 | :params min_points is the minimum of pivot points to use to detect a flag pattern 32 | :type :int 33 | 34 | :params r_max is the R-sqaured fit for the high pivot points 35 | :type :float 36 | 37 | :params r_min is the R-sqaured fit for the low pivot points 38 | :type :float 39 | 40 | :params slope_max is the slope for the high pivot points 41 | :type :float 42 | 43 | :params slope_min is the slope for the low pivot points 44 | :type :float 45 | 46 | :params lower_ratio_slope is the lower limit for the ratio of the slope min to slope max 47 | :type :float 48 | 49 | :params upper_ratio_slope is the upper limit for the ratio of the slope min to slope max 50 | :type :float 51 | 52 | :params progress bar to be displayed or not 53 | :type :bool 54 | 55 | :return (pd.DataFrame) 56 | """ 57 | ohlc["chart_type"] = "" 58 | ohlc["flag_point"] = np.nan 59 | ohlc["flag_highs_idx"] = [np.array([]) for _ in range(len(ohlc)) ] 60 | ohlc["flag_lows_idx"] = [np.array([]) for _ in range(len(ohlc)) ] 61 | ohlc["flag_highs"] = [np.array([]) for _ in range(len(ohlc)) ] 62 | ohlc["flag_lows"] = [np.array([]) for _ in range(len(ohlc)) ] 63 | ohlc["flag_slmax"] = np.nan 64 | ohlc["flag_slmin"] = np.nan 65 | ohlc["flag_intercmin"] = np.nan 66 | ohlc["flag_intercmax"] = np.nan 67 | 68 | # Find the pivot points 69 | ohlc = find_all_pivot_points(ohlc) 70 | 71 | 72 | if not progress: 73 | candle_iter = range(lookback, len(ohlc)) 74 | else: 75 | candle_iter = tqdm(range(lookback, len(ohlc)), desc="Finding flag patterns...") 76 | 77 | for candle_idx in candle_iter: 78 | 79 | maxim = np.array([]) 80 | minim = np.array([]) 81 | xxmin = np.array([]) 82 | xxmax = np.array([]) 83 | 84 | for i in range(candle_idx - lookback, candle_idx+1): 85 | if ohlc.loc[i,"pivot"] == 1: 86 | minim = np.append(minim, ohlc.loc[i, "low"]) 87 | xxmin = np.append(xxmin, i) 88 | if ohlc.loc[i,"pivot"] == 2: 89 | maxim = np.append(maxim, ohlc.loc[i,"high"]) 90 | xxmax = np.append(xxmax, i) 91 | 92 | 93 | # Check if the correct number of pivot points have been found 94 | if (xxmax.size < min_points and xxmin.size < min_points) or xxmax.size==0 or xxmin.size==0: 95 | continue 96 | 97 | # Check the order condition of the pivot points is met 98 | if (np.any(np.diff(minim) < 0)) or (np.any(np.diff(maxim) < 0)): 99 | continue 100 | 101 | # Run the regress to get the slope, intercepts and r-squared 102 | slmin, intercmin, rmin, _, _ = linregress(xxmin, minim) 103 | slmax, intercmax, rmax, _, _ = linregress(xxmax, maxim) 104 | 105 | # Check if the lines are parallel 106 | if abs(rmax)>=r_max and abs(rmin)>=r_min and (slmin > slope_min and slmax > slope_max ) or (slmin < slope_min and slmax < slope_max): 107 | if (slmin/slmax > lower_ratio_slope and slmin/slmax < upper_ratio_slope): 108 | ohlc.loc[candle_idx,"chart_type"] = "flag" 109 | ohlc.loc[candle_idx, "flag_point"] = candle_idx 110 | ohlc.at[candle_idx, "flag_highs"] = maxim 111 | ohlc.at[candle_idx, "flag_lows"] = minim 112 | ohlc.at[candle_idx, "flag_highs_idx"] = xxmax 113 | ohlc.at[candle_idx, "flag_lows_idx"] = xxmin 114 | ohlc.loc[candle_idx, "flag_slmax"] = slmax 115 | ohlc.loc[candle_idx, "flag_slmin"] = slmin 116 | ohlc.loc[candle_idx, "flag_intercmin"] = intercmin 117 | ohlc.loc[candle_idx, "flag_intercmax"] = intercmax 118 | 119 | return ohlc 120 | 121 | -------------------------------------------------------------------------------- /chart_patterns/head_and_shoulders.py: -------------------------------------------------------------------------------- 1 | """ 2 | Date : 2023-01-07 3 | Author : Zetra Team 4 | Function used to detect the Head and Shoulders pattern 5 | """ 6 | 7 | import numpy as np 8 | import pandas as pd 9 | import plotly.graph_objects as go 10 | 11 | from chart_patterns.chart_patterns.charts_utils import find_points 12 | from chart_patterns.chart_patterns.pivot_points import find_all_pivot_points 13 | from scipy.stats import linregress 14 | from tqdm import tqdm 15 | from typing import Tuple 16 | 17 | 18 | 19 | def find_head_and_shoulders(ohlc: pd.DataFrame, lookback: int = 60, pivot_interval: int = 10, short_pivot_interval: int = 5, 20 | head_ratio_before: float = 1.0002, head_ratio_after: float = 1.0002, 21 | upper_slmin: float = 1e-4, progress: bool = False) -> pd.DataFrame: 22 | """ 23 | Find all head and shoulder chart patterns 24 | 25 | :params ohlc is the OHLC dataframe 26 | :type :pd.DataFrame 27 | 28 | :params lookback is the number of periods to use for back candles 29 | :type :int 30 | 31 | :params pivot_interval is the number of candles to consider when detecting a pivot point 32 | :type :int 33 | 34 | :params short_pivot_interval is same as pivot_interval but must be less than it. 35 | :type :int 36 | 37 | :params head_ratio_before is the ratio between the head value and the shoulder value to the left 38 | :type :float 39 | 40 | :params head_ratio_after is the ratio between the head value and the shoulder value to the right 41 | :type :float 42 | 43 | :params upper_slmin is the upper limit of the neckline slope of the pattern 44 | :type :float 45 | 46 | :params progress bar to be displayed or not 47 | :type :bool 48 | 49 | :return (pd.DataFrame) 50 | """ 51 | 52 | 53 | if short_pivot_interval <= 0 or pivot_interval <= 0: 54 | raise ValueError("Value cannot be less or equal to 0") 55 | 56 | if short_pivot_interval >= pivot_interval: 57 | raise ValueError(f"short_pivot_interval must be less than pivot_interval") 58 | 59 | ohlc.loc[:,"hs_lookback"] = lookback 60 | ohlc.loc[:,"chart_type"] = "" 61 | ohlc.loc[:,"hs_idx"] = [np.array([]) for _ in range(len(ohlc)) ] 62 | ohlc.loc[:,"hs_point"] = [np.array([]) for _ in range(len(ohlc)) ] 63 | 64 | # Find the pivot points 65 | ohlc = find_all_pivot_points(ohlc, left_count=pivot_interval, right_count=pivot_interval) 66 | ohlc = find_all_pivot_points(ohlc, left_count=short_pivot_interval, right_count=short_pivot_interval, name_pivot="short_pivot") 67 | 68 | if not progress: 69 | candle_iter = range(lookback, len(ohlc)) 70 | else: 71 | candle_iter = tqdm(range(lookback, len(ohlc)), desc="Finding head and shoulders patterns...") 72 | 73 | for candle_idx in candle_iter: 74 | 75 | if ohlc.loc[candle_idx, "pivot"] != 2 or ohlc.loc[candle_idx, "short_pivot"] != 2: 76 | continue 77 | 78 | 79 | maxim, minim, xxmax, xxmin, maxacount, minacount, maxbcount, minbcount = find_points(ohlc, candle_idx, lookback) 80 | 81 | if minbcount<1 or minacount<1 or maxbcount<1 or maxacount<1: 82 | continue 83 | 84 | slmin, _, _, _, _ = linregress(xxmin, minim) 85 | headidx = np.argmax(maxim, axis=0) 86 | 87 | # If the head index is the last value, then continue 88 | if len(maxim) - 1 == headidx: 89 | continue 90 | 91 | 92 | if maxim[headidx]-maxim[headidx-1] > 0 and maxim[headidx]/maxim[headidx-1] > head_ratio_before and \ 93 | maxim[headidx]-maxim[headidx+1]>0 and maxim[headidx]/maxim[headidx+1] > head_ratio_after and \ 94 | abs(slmin)<=upper_slmin and xxmin[0]>xxmax[headidx-1] and xxmin[1] pd.DataFrame: 21 | """ 22 | Find all the inverse head and shoulders chart patterns 23 | 24 | :params ohlc is the OHLC dataframe 25 | :type :pd.DataFrame 26 | 27 | :params lookback is the number of periods to use for back candles 28 | :type :int 29 | 30 | :params pivot_interval is the number of candles to consider when detecting a pivot point 31 | :type :int 32 | 33 | :params short_pivot_interval is same as pivot_interval but must be less than it. 34 | :type :int 35 | 36 | :params head_ratio_before is the ratio between the head value and the shoulder value to the left 37 | :type :float 38 | 39 | :params head_ratio_after is the ratio between the head value and the shoulder value to the right 40 | :type :float 41 | 42 | :params upper_slmax is the upper limit of the neckline slope of the pattern 43 | :type :float 44 | 45 | :params progress bar to be displayed or not 46 | :type :bool 47 | 48 | :return (pd.DataFrame) 49 | """ 50 | if short_pivot_interval <= 0 or pivot_interval <= 0: 51 | raise ValueError("Value cannot be less or equal to 0") 52 | 53 | if short_pivot_interval >= pivot_interval: 54 | raise ValueError(f"short_pivot_interval must be less than pivot_interval") 55 | 56 | ohlc["ihs_lookback"] = lookback 57 | ohlc["chart_type"] = "" 58 | ohlc["ihs_idx"] = [np.array([]) for _ in range(len(ohlc)) ] 59 | ohlc["ihs_point"] = [np.array([]) for _ in range(len(ohlc)) ] 60 | 61 | # Find the pivot points 62 | ohlc = find_all_pivot_points(ohlc, left_count=pivot_interval, right_count=pivot_interval) 63 | ohlc = find_all_pivot_points(ohlc, left_count=short_pivot_interval, right_count=short_pivot_interval, name_pivot="short_pivot") 64 | 65 | 66 | if not progress: 67 | candle_iter = range(lookback, len(ohlc)) 68 | else: 69 | candle_iter = tqdm(range(lookback, len(ohlc)), desc="Finding inverse head and shoulder patterns...") 70 | 71 | for candle_idx in candle_iter: 72 | 73 | if ohlc.loc[candle_idx, "pivot"] != 1 or ohlc.loc[candle_idx,"short_pivot"] != 1: 74 | continue 75 | 76 | 77 | maxim, minim, xxmax, xxmin, maxacount, minacount, maxbcount, minbcount = find_points(ohlc, candle_idx, lookback) 78 | if minbcount<1 or minacount<1 or maxbcount<1 or maxacount<1: 79 | continue 80 | 81 | slmax, _, _, _, _ = linregress(xxmax, maxim) 82 | 83 | headidx = np.argmin(minim, axis=0) 84 | 85 | # If the head index is the last value, then continue 86 | if len(minim) - 1 == headidx: 87 | continue 88 | 89 | 90 | if minim[headidx-1]-minim[headidx]>0 and (minim[headidx]/minim[headidx-1] < 1 and minim[headidx]/minim[headidx-1] >= head_ratio_before) and \ 91 | (minim[headidx]/minim[headidx+1] < 1 and minim[headidx]/minim[headidx+1] >= head_ratio_after) and \ 92 | minim[headidx+1]-minim[headidx]> 0 and abs(slmax)<=upper_slmax and \ 93 | xxmax[0]>xxmin[headidx-1] and xxmax[1] pd.DataFrame: 22 | """ 23 | Find the pennant pattern point 24 | 25 | :params ohlc is the OHLC dataframe 26 | :type :pd.DataFrame 27 | 28 | :params lookback is the number of periods to use for back candles 29 | :type :int 30 | 31 | :params min_points is the minimum of pivot points to use to detect a flag pattern 32 | :type :int 33 | 34 | :params r_max is the R-sqaured fit for the high pivot points 35 | :type :float 36 | 37 | :params r_min is the R-sqaured fit for the low pivot points 38 | :type :float 39 | 40 | :params slope_max is the slope for the high pivot points 41 | :type :float 42 | 43 | :params slope_min is the slope for the low pivot points 44 | :type :float 45 | 46 | :params lower_ratio_slope is the lower limit for the ratio of the slope min to slope max 47 | :type :float 48 | 49 | :params upper_ratio_slope is the upper limit for the ratio of the slope min to slope max 50 | :type :float 51 | 52 | :params progress bar to be displayed or not 53 | :type :bool 54 | 55 | :return (pd.DataFrame) 56 | """ 57 | 58 | ohlc["chart_type"] = "" 59 | ohlc["pennant_point"] = np.nan 60 | ohlc["pennant_highs_idx"] = [np.array([]) for _ in range(len(ohlc)) ] 61 | ohlc["pennant_lows_idx"] = [np.array([]) for _ in range(len(ohlc)) ] 62 | ohlc["pennant_highs"] = [np.array([]) for _ in range(len(ohlc)) ] 63 | ohlc["pennant_lows"] = [np.array([]) for _ in range(len(ohlc)) ] 64 | ohlc["pennant_slmax"] = np.nan 65 | ohlc["pennant_slmin"] = np.nan 66 | ohlc["pennant_intercmin"] = np.nan 67 | ohlc["pennant_intercmax"] = np.nan 68 | 69 | # Find the pivot points 70 | ohlc = find_all_pivot_points(ohlc, progress=progress) 71 | 72 | 73 | if not progress: 74 | candle_iter = range(lookback, len(ohlc)) 75 | else: 76 | candle_iter = tqdm(range(lookback, len(ohlc)), desc="Finding pennant patterns...") 77 | 78 | for candle_idx in candle_iter: 79 | 80 | maxim = np.array([]) 81 | minim = np.array([]) 82 | xxmin = np.array([]) 83 | xxmax = np.array([]) 84 | 85 | for i in range(candle_idx-lookback, candle_idx+1): 86 | if ohlc.loc[i,"pivot"] == 1: 87 | minim = np.append(minim, ohlc.loc[i, "low"]) 88 | xxmin = np.append(xxmin, i) 89 | if ohlc.loc[i,"pivot"] == 2: 90 | maxim = np.append(maxim, ohlc.loc[i,"high"]) 91 | xxmax = np.append(xxmax, i) 92 | 93 | 94 | # Check the correct number of pivot points have been found 95 | if (xxmax.size < min_points and xxmin.size < min_points) or xxmax.size==0 or xxmin.size==0: 96 | continue 97 | 98 | # Run the regress to get the slope, intercepts and r-squared 99 | slmin, intercmin, rmin, pmin, semin = linregress(xxmin, minim) 100 | slmax, intercmax, rmax, pmax, semax = linregress(xxmax, maxim) 101 | 102 | 103 | 104 | if abs(rmax)>=r_max and abs(rmin)>=r_min and slmin>=slope_min and slmax<= slope_max and abs(slmax/slmin) > lower_ratio_slope and abs(slmax/slmin) < upper_ratio_slope: 105 | ohlc.loc[candle_idx,"chart_type"] = "pennant" 106 | ohlc.loc[candle_idx, "pennant_point"] = candle_idx 107 | ohlc.at[candle_idx, "pennant_highs"] = maxim 108 | ohlc.at[candle_idx, "pennant_lows"] = minim 109 | ohlc.at[candle_idx, "pennant_highs_idx"] = xxmax 110 | ohlc.at[candle_idx, "pennant_lows_idx"] = xxmin 111 | ohlc.loc[candle_idx, "pennant_slmax"] = slmax 112 | ohlc.loc[candle_idx, "pennant_slmin"] = slmin 113 | ohlc.loc[candle_idx, "pennant_intercmin"] = intercmin 114 | ohlc.loc[candle_idx, "pennant_intercmax"] = intercmax 115 | 116 | return ohlc -------------------------------------------------------------------------------- /chart_patterns/pivot_points.py: -------------------------------------------------------------------------------- 1 | """ 2 | Date : 2023-12-25 3 | Author: Zetra Team 4 | 5 | """ 6 | 7 | import numpy as np 8 | import pandas as pd 9 | 10 | 11 | from chart_patterns.chart_patterns.utils import check_ohlc_names 12 | from tqdm import tqdm 13 | from typing import Union 14 | 15 | 16 | 17 | def find_pivot_point(ohlc: pd.DataFrame, current_row: int, left_count:int = 3, right_count:int =3, 18 | progress: bool = False) -> int: 19 | """ 20 | Check if the current row (i.e. point) is a pivot point 21 | 22 | :params ohlc is a dataframe with Open, High, Low, Close data 23 | :type :pd.DataFrame 24 | 25 | :params current_row is the index number of the row 26 | :type :int 27 | 28 | :params left_count is the number of candles to the left to consider 29 | :type :int 30 | 31 | :params right_count is the number of candles to right to consider 32 | :type :int 33 | 34 | :return (int) 35 | """ 36 | 37 | # Check if ohlc dataframe meets certain conditions 38 | check_ohlc_names(ohlc) 39 | 40 | 41 | # Check if the length conditions are met 42 | if current_row - left_count < 0 or current_row + right_count >= len(ohlc): 43 | return 0 44 | 45 | pivot_low = 1 46 | pivot_high = 1 47 | 48 | if not progress: 49 | index_iter = range(current_row - left_count, current_row + right_count + 1) 50 | else: 51 | index_iter = tqdm(range(current_row - left_count, current_row + right_count + 1), desc="Finding all pivot points...") 52 | 53 | for idx in index_iter: 54 | if(ohlc.loc[current_row, "low"] > ohlc.loc[idx, "low"]): 55 | pivot_low = 0 56 | 57 | if(ohlc.loc[current_row, "high"] < ohlc.loc[idx, "high"]): 58 | pivot_high = 0 59 | 60 | if pivot_low and pivot_high: 61 | return 3 62 | 63 | elif pivot_low: 64 | return 1 65 | 66 | elif pivot_high: 67 | return 2 68 | else: 69 | return 0 70 | 71 | def find_all_pivot_points(ohlc: pd.DataFrame, left_count:int = 3, right_count:int = 3, name_pivot: Union[None, str] = None, 72 | progress: bool = False ) -> pd.DataFrame: 73 | """ 74 | Find the all the pivot points for the given OHLC dataframe 75 | 76 | :params ohlc is a dataframe with Open, High, Low, Close data 77 | :type :pd.DataFrame 78 | 79 | :params left_count is the number of candles to the left to consider 80 | :type :int 81 | 82 | :params right_count is the number of candles to right to consider 83 | :type :int 84 | 85 | :params progress bar to be displayed or not 86 | :type :bool 87 | 88 | :return (pd.DataFrame) 89 | """ 90 | 91 | 92 | if name_pivot != None: 93 | ohlc.loc[:,name_pivot] = ohlc.apply(lambda row: find_pivot_point(ohlc, row.name, left_count, right_count), axis=1) 94 | ohlc.loc[:,f"{name_pivot}_pos"] = ohlc.apply(lambda row: find_pivot_point_position(row), axis=1) 95 | else: 96 | # Get the pivot points 97 | ohlc.loc[:,"pivot"] = ohlc.apply(lambda row: find_pivot_point(ohlc, row.name, left_count, right_count), axis=1) 98 | ohlc.loc[:,'pivot_pos'] = ohlc.apply(lambda row: find_pivot_point_position(row), axis=1) 99 | 100 | 101 | return ohlc 102 | 103 | 104 | def find_pivot_point_position(row: pd.Series) -> float: 105 | """ 106 | Get the Pivot Point position and assign the Low or High value. 107 | 108 | :params row to assign the pivot point position value if applicable. There must be a 'pivot' value 109 | :type :pd.Series 110 | 111 | :return (float) 112 | """ 113 | 114 | 115 | try: 116 | if row['pivot']==1: 117 | return row['low']-1e-3 118 | elif row['pivot']==2: 119 | return row['high']+1e-3 120 | else: 121 | return np.nan 122 | 123 | except Exception as e: 124 | print(f"Error: {e}") 125 | return np.nan 126 | 127 | if __name__ == "__main__": 128 | import os 129 | # print(os.path.realpath('').split("\patterns")[0]) 130 | data = pd.read_csv(os.path.join(os.path.realpath('').split("\patterns")[0],"data","eurusd-4h.csv")) 131 | 132 | # print(find_all_pivot_points(data)) 133 | data = find_all_pivot_points(data) 134 | display_pivot_points(data) 135 | -------------------------------------------------------------------------------- /chart_patterns/plotting.py: -------------------------------------------------------------------------------- 1 | """ 2 | Date : 2023-12-25 3 | Author: Zetra Team 4 | 5 | For slider: https://community.plotly.com/t/multiple-traces-with-a-single-slider-in-plotly/16356/2 6 | """ 7 | 8 | import os 9 | import pandas as pd 10 | import plotly.graph_objects as go 11 | import sys 12 | 13 | 14 | from chart_patterns.chart_patterns.utils import check_ohlc_names 15 | from plotly.subplots import make_subplots 16 | from tqdm import tqdm 17 | from typing import Dict, List, Union 18 | 19 | 20 | def set_theme(fig: go.Candlestick, theme: Dict[str,str] = {"bg_color": "black", "up_color":"#3D9970", 21 | "down_color": "#FF4136", "legend_font_color": "white", "xaxes_color": "white", "yaxes_color": "white"}) -> go.Candlestick: 22 | """ 23 | Set the aesthetics of the plot 24 | 25 | :params fig is the candlestick object 26 | :type :go.Candlestick 27 | 28 | :params theme is dictionary with the settings 29 | :type :Dict[str, str] 30 | 31 | :return (go.Candlestick) 32 | """ 33 | 34 | fig.update_layout(xaxis_rangeslider_visible=False, plot_bgcolor=theme["bg_color"], paper_bgcolor=theme["bg_color"], 35 | xaxis=dict(showgrid=False), yaxis=dict(showgrid=False, side="right"), legend_font_color=theme["legend_font_color"], 36 | legend=dict(yanchor="bottom", y=0.99, xanchor="left", x=0.01) ) 37 | 38 | fig.update_traces(increasing_fillcolor=theme["up_color"], selector=dict(type='candlestick')) 39 | fig.update_traces(decreasing_fillcolor=theme["down_color"], selector=dict(type='candlestick')) 40 | 41 | fig.update_xaxes(color=theme["xaxes_color"]) 42 | fig.update_yaxes(color=theme["yaxes_color"]) 43 | 44 | return fig 45 | 46 | 47 | 48 | def _plot_candlestick(ohlc: pd.DataFrame, plot_obs:int = 500, fig =None ) -> go.Candlestick: 49 | """ 50 | :params ohlc is a dataframe with Open, High, Low, Close 51 | :type :pd.DataFrame 52 | 53 | :params plot_obs is the total number of observations to plot 54 | :type :int 55 | 56 | :return (go.Candlestick) 57 | """ 58 | 59 | # Check if OHLC columns are present 60 | check_ohlc_names(ohlc) 61 | 62 | # Has the user run find_all_pivot_points? 63 | if ohlc.columns.str.contains("pivot_pos").sum() == 0: 64 | print(f"-> Column `pivot_pos` was not found. Did you run `find_all_pivot_points`?") 65 | sys.exit() 66 | 67 | 68 | # Find the number of obs. Only plot 500 observations 69 | if len(ohlc) > plot_obs + 1: 70 | print(f"Note only the {plot_obs} points will be plotted") 71 | 72 | ohlc = ohlc.iloc[:plot_obs,] 73 | 74 | 75 | # Plot the candlesticks 76 | if fig is not None : 77 | fig.add_trace(go.Candlestick( 78 | x = ohlc.index, 79 | open = ohlc.open, 80 | high = ohlc.high, 81 | low = ohlc.low, 82 | close = ohlc.close, name="OHLC" 83 | )) 84 | else: 85 | fig = go.Figure(data=[go.Candlestick( 86 | x = ohlc.index, 87 | open = ohlc.open, 88 | high = ohlc.high, 89 | low = ohlc.low, 90 | close = ohlc.close, name="OHLC" 91 | )]) 92 | 93 | 94 | return fig 95 | 96 | 97 | def _plot_pivot_points(ohlc: pd.DataFrame, fig: go.Candlestick, pivot_name: int = "pivot") -> go.Candlestick: 98 | """ 99 | Plot the pivot points. It are assumes there is a "pivot" column 100 | 101 | :params ohlc is a dataframe with Open, High, Low, Close 102 | :type :pd.DataFrame 103 | 104 | :params fig is the figure object that has the candlestick 105 | :type :go.Candlestick 106 | 107 | :params pivot_name is the name of the column that has the pivot points. Note the column should have int values 108 | where `1` is pivot lows and `2` is pivot highs 109 | :type :str 110 | 111 | 112 | :return (go.Candlestick) 113 | """ 114 | try: 115 | pivot_lows = ohlc.loc[ohlc[pivot_name] == 1,] 116 | pivot_highs = ohlc.loc[ohlc[pivot_name] == 2,] 117 | except Exception as e: 118 | print(f"No column named `{pivot_name}`. Did you run `find_all_pivot_points`?") 119 | sys.exit() 120 | 121 | fig.add_scatter( 122 | x = pivot_lows.index , 123 | y = pivot_lows[f"{pivot_name}_pos"], 124 | mode="markers", marker=dict(size=20, color="red"), name="Pivot Low" 125 | ) 126 | 127 | fig.add_scatter( 128 | x=pivot_highs.index, 129 | y=pivot_highs[f"{pivot_name}_pos"] , 130 | mode="markers", marker=dict(size=20, color="green"), name="Pivot High" 131 | ) 132 | 133 | return fig 134 | 135 | def display_pivot_points(ohlc : pd.DataFrame, 136 | theme: Dict[str, str] = {"bg_color": "black", "up_color":"#3D9970", 137 | "down_color": "#FF4136", "legend_font_color": "white", "xaxes_color": "white", "yaxes_color": "white"}, 138 | plot_obs:int = 500 ) -> None: 139 | """ 140 | Display the pivot points and the OHLC data in a graph 141 | 142 | :params ohlc is a dataframe with Open, High, Low, Close and the pivot_pos 143 | :type :pd.DataFrame 144 | 145 | :params theme is the set of parameters to set the aesthetics of the graph 146 | :type :Dict[str, str] 147 | 148 | :params plot_obs is the total number of observations to plot 149 | :type :int 150 | 151 | :return (None) 152 | """ 153 | 154 | # Get the Candlestick figure object 155 | fig = _plot_candlestick(ohlc, plot_obs) 156 | 157 | # Add the pivot points 158 | fig = _plot_pivot_points(ohlc, fig) 159 | 160 | # Set the theme 161 | fig = set_theme(fig, theme) 162 | 163 | 164 | fig.show() 165 | 166 | 167 | def _add_head_shoulder_pattern_plot(row: Union[tuple, pd.DataFrame], fig: go.Candlestick, 168 | x_val: str = "hs_idx", y_val: str = "hs_point") -> go.Candlestick: 169 | """ 170 | Add the Head and Shoulders pattern to the given figure object 171 | 172 | :params row is either a pandas dataframe or a row that has the flag chart pattern info. 173 | :type :Union[tuple, pd.DataFrame] 174 | 175 | :params fig is the figure object 176 | :type :go.Candlestick 177 | 178 | :return (go.Candlestick) 179 | """ 180 | 181 | if isinstance(row, tuple): 182 | x_values = row[1][x_val] 183 | y_values = row[1][y_val] 184 | else: 185 | x_values = row[x_val] 186 | y_values = row[y_val] 187 | 188 | fig.add_scatter( 189 | x = x_values , y = y_values, 190 | mode='lines', 191 | name=None, line=dict(color='royalblue', width=4), showlegend=False ) 192 | 193 | return fig 194 | 195 | def _add_doubles_pattern_plot(row: Union[tuple, pd.DataFrame], fig: go.Candlestick) -> go.Candlestick: 196 | """ 197 | Add the the Double chart patterns to the given figure object 198 | 199 | :params row is either a pandas dataframe or a row that has the flag chart pattern info. 200 | :type :Union[tuple, pd.DataFrame] 201 | 202 | :params fig is the figure object 203 | :type :go.Candlestick 204 | 205 | :return (go.Candlestick) 206 | """ 207 | 208 | if isinstance(row, tuple): 209 | double_idx = row[1]["double_idx"] 210 | double_pts = row[1]["double_point"] 211 | else: 212 | double_idx = row["double_idx"] 213 | double_pts = row["double_point"] 214 | 215 | fig.add_scatter(x = double_idx , y = double_pts, 216 | mode='lines', 217 | name=None, line=dict(color='royalblue', width=4), showlegend=False ) 218 | 219 | return fig 220 | 221 | def _add_triangle_pattern_plot(row: Union[tuple, pd.DataFrame], fig: go.Candlestick) -> go.Candlestick: 222 | """ 223 | Add the triangle pattern to the figure object 224 | 225 | :params row is either a pandas dataframe or a row that has the flag chart pattern info. 226 | :type :Union[tuple, pd.DataFrame] 227 | 228 | :params fig is the figure object 229 | :type :go.Candlestick 230 | 231 | :return (go.Candlestick) 232 | """ 233 | 234 | if isinstance(row, pd.DataFrame): 235 | high_idx = row["triangle_high_idx"] 236 | low_idx = row["triangle_low_idx"] 237 | intercmin = row["triangle_intercmin"] 238 | intercmax = row["triangle_intercmax"] 239 | slmax = row["triangle_slmax"] 240 | slmin = row["triangle_slmin"] 241 | 242 | else: 243 | high_idx = row[1]["triangle_high_idx"].tolist() 244 | low_idx = row[1]["triangle_low_idx"].tolist() 245 | intercmin = row[1]["triangle_intercmin"] 246 | intercmax = row[1]["triangle_intercmax"] 247 | slmax = row[1]["triangle_slmax"] 248 | slmin = row[1]["triangle_slmin"] 249 | 250 | fig.add_scatter(x = [int(idx) for idx in high_idx] , y = [int(idx)*slmax + intercmax for idx in high_idx], 251 | mode='lines', 252 | name=None, line=dict(color='royalblue', width=4), showlegend=False ) 253 | 254 | fig.add_scatter(x = [int(idx) for idx in low_idx] , y = [int(idx)*slmin + intercmin for idx in low_idx], 255 | mode='lines', 256 | name=None, line=dict(color='royalblue', width=4), showlegend=False ) 257 | 258 | return fig 259 | 260 | 261 | def _add_pennant_pattern_plot(row: Union[tuple, pd.DataFrame], fig: go.Candlestick) -> go.Candlestick: 262 | """ 263 | Add the pennant pattern to the figure object 264 | 265 | :params row is either a pandas dataframe or a row that has the pennant chart pattern info. 266 | :type :Union[tuple, pd.DataFrame] 267 | 268 | :params fig is the figure object 269 | :type :go.Candlestick 270 | 271 | :return (go.Candlestick) 272 | """ 273 | 274 | if isinstance(row, pd.DataFrame): 275 | 276 | x_low_vals = row["pennant_lows_idx"].tolist()[0].tolist() 277 | y_low_vals_arr = row["pennant_slmin"]*row["pennant_lows_idx"] + row["pennant_intercmin"] 278 | y_low_vals = y_low_vals_arr.tolist()[0].tolist() 279 | 280 | x_high_vals = row["pennant_highs_idx"].tolist()[0].tolist() 281 | y_high_vals_arr = row["pennant_slmax"]*row["pennant_highs_idx"] + row["pennant_intercmax"] 282 | y_high_vals = y_high_vals_arr.tolist()[0].tolist() 283 | 284 | else: 285 | 286 | x_low_vals = row[1]["pennant_lows_idx"] 287 | y_low_vals_arr = row[1]["pennant_slmin"]*row[1]["pennant_lows_idx"] + row[1]["pennant_intercmin"] 288 | y_low_vals = y_low_vals_arr 289 | 290 | x_high_vals = row[1]["pennant_highs_idx"] 291 | y_high_vals_arr = row[1]["pennant_slmax"]*row[1]["pennant_highs_idx"] + row[1]["pennant_intercmax"] 292 | y_high_vals = y_high_vals_arr 293 | 294 | fig.add_scatter(x = x_low_vals , y = y_low_vals, 295 | mode='lines', 296 | name=None, line=dict(color='royalblue', width=4), showlegend=False ) 297 | 298 | fig.add_scatter(x = x_high_vals , y = y_high_vals, 299 | mode='lines', 300 | name=None, line=dict(color='royalblue', width=4), showlegend=False) 301 | 302 | return fig 303 | 304 | 305 | def _add_flag_pattern_plot(row: Union[tuple, pd.DataFrame], fig: go.Candlestick) -> go.Candlestick: 306 | """ 307 | Add the flag pattern to the figure object 308 | 309 | :params row is either a pandas dataframe or a row that has the flag chart pattern info. 310 | :type :Union[tuple, pd.DataFrame] 311 | 312 | :params fig is the figure object 313 | :type :go.Candlestick 314 | 315 | :return (go.Candlestick) 316 | """ 317 | 318 | if isinstance(row, pd.DataFrame): 319 | 320 | x_low_vals = row["flag_lows_idx"].tolist()[0].tolist() 321 | y_low_vals_arr = row["flag_slmin"]*row["flag_lows_idx"] + row["flag_intercmin"] 322 | y_low_vals = y_low_vals_arr.tolist()[0].tolist() 323 | 324 | x_high_vals = row["flag_highs_idx"].tolist()[0].tolist() 325 | y_high_vals_arr = row["flag_slmax"]*row["flag_highs_idx"] + row["flag_intercmax"] 326 | y_high_vals = y_high_vals_arr.tolist()[0].tolist() 327 | 328 | else: 329 | 330 | x_low_vals = row[1]["flag_lows_idx"] 331 | y_low_vals_arr = row[1]["flag_slmin"]*row[1]["flag_lows_idx"] + row[1]["flag_intercmin"] 332 | y_low_vals = y_low_vals_arr 333 | 334 | x_high_vals = row[1]["flag_highs_idx"] 335 | y_high_vals_arr = row[1]["flag_slmax"]*row[1]["flag_highs_idx"] + row[1]["flag_intercmax"] 336 | y_high_vals = y_high_vals_arr 337 | 338 | fig.add_scatter(x = x_low_vals , y = y_low_vals, 339 | mode='lines', 340 | name=None, line=dict(color='royalblue', width=4), showlegend=False ) 341 | 342 | fig.add_scatter(x = x_high_vals , y = y_high_vals, 343 | mode='lines', 344 | name=None, line=dict(color='royalblue', width=4), showlegend=False) 345 | 346 | return fig 347 | 348 | def save_chart_pattern(fig: go.Candlestick, pattern: str, row: Union[None,tuple]) -> None: 349 | """ 350 | Save the chart pattern plot 351 | 352 | :params fig is the Candlestick object 353 | :type :go.Candlestick 354 | 355 | :params pattern is the name of the chart pattern to save 356 | :type :str 357 | 358 | :params row is the pandas Series that has the index value 359 | :type :Union[None,tuple] 360 | 361 | :return (None) 362 | """ 363 | 364 | # Create the images/flag folder if it does not exist 365 | if not os.path.exists(os.path.join(os.path.realpath(''), "images")): 366 | os.mkdir(os.path.join(os.path.realpath(''), "images")) 367 | 368 | if not os.path.exists(os.path.join(os.path.realpath(''), "images", pattern)): 369 | os.mkdir(os.path.join(os.path.realpath(''), "images", pattern)) 370 | 371 | if row: 372 | fig.write_image(os.path.join(os.path.realpath(''), "images", pattern, f"fig{row[0]}.png")) 373 | else: 374 | fig.write_image(os.path.join(os.path.realpath(''), "images", pattern, f"fig-{pattern}.png")) 375 | 376 | def display_chart_pattern(ohlc: pd.DataFrame, pattern: str = "flag", 377 | save: bool = True, lookback: int = 60, pivot_name: str = "pivot") -> None: 378 | """ 379 | Display the specified chart pattern. 380 | 381 | :params ohlc is the dataframe that contains the OHLC data and the chart pattern points 382 | :type :pd.DataFrame 383 | 384 | :params pattern is the name of the pattern to plot 385 | :type :str 386 | 387 | :params save is whether to save the plot(s) and not display them 388 | :type :bool 389 | 390 | :params lookback is the number of candlesticks to plot 391 | :type :int 392 | 393 | :params pivot_name is the name of the column that has the pivot points. Note the column should have int values 394 | where `1` is pivot lows and `2` is pivot highs 395 | :type :str 396 | 397 | 398 | :return (None) 399 | """ 400 | 401 | # Check if the columns have the `pattern` results 402 | if ohlc.columns.str.lower().str.contains(pattern).sum() == 0: 403 | print(f"No columns for the pattern `{pattern}`. Did you run the function to get the pattern?") 404 | sys.exit() 405 | 406 | 407 | if pattern == "flag": 408 | pattern_points = ohlc.loc[ohlc["chart_type"]== "flag"] 409 | elif pattern == "double": 410 | pattern_points = ohlc.loc[ohlc["chart_type"]== "double"] 411 | elif pattern == "hs": 412 | pattern_points = ohlc.loc[ohlc["chart_type"]=="hs"] 413 | elif pattern == "ihs": 414 | pattern_points = ohlc.loc[ohlc["chart_type"]=="ihs"] 415 | elif pattern == "triangle": 416 | pattern_points = ohlc.loc[ohlc["chart_type"]=="triangle"] 417 | elif pattern == "pennant": 418 | pattern_points = ohlc.loc[ohlc["chart_type"]=="pennant"] 419 | 420 | 421 | if len(pattern_points) == 0: # There is no pattern found 422 | print(f"There are no `{pattern}` patterns detected.") 423 | elif len(pattern_points) == 1: 424 | # Get the Candlestick figure object 425 | fig = _plot_candlestick(ohlc) 426 | 427 | # Add the pivot points 428 | fig = _plot_pivot_points(ohlc, fig, pivot_name) 429 | 430 | # Set the theme 431 | fig = set_theme(fig) 432 | 433 | if pattern == "flag": 434 | # Plot the Flag pattern 435 | fig = _add_flag_pattern_plot(pattern_points, fig) 436 | elif pattern == "double": 437 | fig = _add_doubles_pattern_plot(pattern_points, fig) 438 | elif pattern == "hs": 439 | fig = _add_head_shoulder_pattern_plot(pattern_points, fig) 440 | elif pattern == "ihs": 441 | fig = _add_head_shoulder_pattern_plot(pattern_points, fig, "ihs_idx", "ihs_point") 442 | elif pattern == "triangle": 443 | fig = _add_triangle_pattern_plot(pattern_points, fig) 444 | elif pattern == "pennant": 445 | fig = _add_pennant_pattern_plot(pattern_points, fig) 446 | 447 | if save: 448 | save_chart_pattern(fig, pattern, None) 449 | else: 450 | fig.show() 451 | elif len(pattern_points) > 1: 452 | 453 | for row in tqdm(pattern_points.iterrows(), desc=f"Saving the {pattern} charts..."): 454 | # Get the row index 455 | pattern_point = row[0] 456 | 457 | # Make sure at least 50 candlesticks are available, if possible 458 | if pattern_point - lookback < 0: 459 | start = 0 460 | else: 461 | start = pattern_point - lookback 462 | 463 | # Get a subset of the ohlc plus chart pattens included 464 | ohlc_copy = ohlc.loc[start:pattern_point,] 465 | 466 | # Add the ohlc data 467 | fig = _plot_candlestick(ohlc_copy) 468 | 469 | # Add the pivot points 470 | fig = _plot_pivot_points(ohlc_copy, fig, pivot_name) 471 | 472 | # Set the theme 473 | fig = set_theme(fig) 474 | 475 | if pattern == "flag": 476 | # Add the flag pattern 477 | fig = _add_flag_pattern_plot(row, fig) 478 | elif pattern == "double": 479 | # Add the double pattern 480 | fig = _add_doubles_pattern_plot(row, fig) 481 | elif pattern == "hs": 482 | fig = _add_head_shoulder_pattern_plot(row, fig) 483 | elif pattern == "ihs": 484 | fig = _add_head_shoulder_pattern_plot(row, fig, "ihs_idx", "ihs_point") 485 | elif pattern == "triangle": 486 | fig = _add_triangle_pattern_plot(row, fig) 487 | elif pattern == "pennant": 488 | fig = _add_pennant_pattern_plot(row, fig) 489 | 490 | # Save the figures 491 | save_chart_pattern(fig, pattern, row) 492 | 493 | if save: 494 | sys.exit() 495 | 496 | -------------------------------------------------------------------------------- /chart_patterns/triangles.py: -------------------------------------------------------------------------------- 1 | """ 2 | Date : 2024-01-13 3 | Author : Zetra Team 4 | Detect Triangle Patterns - Ascending, Descending and Symmetrical 5 | """ 6 | 7 | import numpy as np 8 | import pandas as pd 9 | import plotly.graph_objects as go 10 | 11 | 12 | from chart_patterns.chart_patterns.pivot_points import find_all_pivot_points 13 | from scipy.stats import linregress 14 | from tqdm import tqdm 15 | 16 | def find_triangle_pattern(ohlc: pd.DataFrame, lookback: int = 25, min_points: int = 3, rlimit: int = 0.9, 17 | slmax_limit: float = 0.00001, slmin_limit: float = 0.00001, 18 | triangle_type: str = "ascending", progress: bool = False ) -> pd.DataFrame: 19 | """ 20 | Find the specified triangle pattern 21 | 22 | :params ohlc is the OHLC dataframe 23 | :type :pd.DataFrame 24 | 25 | :params lookback is the number of periods to use for back candles 26 | :type :int 27 | 28 | :params min_points is the minimum of pivot points to use to detect a flag pattern 29 | :type :int 30 | 31 | :params rlimit is the R-squared fit lower limit for the pivot points 32 | :type :float 33 | 34 | :params slmax_limit is the limit for the slope of the pivot highs 35 | :type :float 36 | 37 | :params slmin_limit is the limit for the slope of the pivot lows 38 | :type :float 39 | 40 | :params triangle_type is the type of triangle pattern to detect. Options - ["ascending", "descending", "symmetrical"] 41 | :type :str 42 | 43 | :params progress bar to be displayed or not 44 | :type :bool 45 | 46 | :return (pd.DataFrame) 47 | """ 48 | 49 | 50 | ohlc["chart_type"] = "" 51 | ohlc["triangle_type"] = "" 52 | ohlc["triangle_slmax"] = np.nan 53 | ohlc["triangle_slmin"] = np.nan 54 | ohlc["triangle_intercmin"] = np.nan 55 | ohlc["triangle_intercmax"] = np.nan 56 | ohlc["triangle_high_idx"] = [np.array([]) for _ in range(len(ohlc)) ] 57 | ohlc["triangle_low_idx"] = [np.array([]) for _ in range(len(ohlc)) ] 58 | 59 | 60 | # Find the pivot points 61 | ohlc = find_all_pivot_points(ohlc) 62 | 63 | if not progress: 64 | candle_iter = range(lookback, len(ohlc)) 65 | else: 66 | candle_iter = tqdm(range(lookback, len(ohlc)), desc="Finding triangle patterns") 67 | 68 | for candle_idx in candle_iter: 69 | 70 | maxim = np.array([]) 71 | minim = np.array([]) 72 | xxmin = np.array([]) 73 | xxmax = np.array([]) 74 | 75 | for i in range(candle_idx - lookback, candle_idx+1): 76 | if ohlc.loc[i,"pivot"] == 1: 77 | minim = np.append(minim, ohlc.loc[i, "low"]) 78 | xxmin = np.append(xxmin, i) 79 | if ohlc.loc[i,"pivot"] == 2: 80 | maxim = np.append(maxim, ohlc.loc[i,"high"]) 81 | xxmax = np.append(xxmax, i) 82 | 83 | 84 | if (xxmax.size < min_points and xxmin.size < min_points) or xxmax.size==0 or xxmin.size==0: 85 | continue 86 | 87 | slmin, intercmin, rmin, _, _ = linregress(xxmin, minim) 88 | slmax, intercmax, rmax, _, _ = linregress(xxmax, maxim) 89 | 90 | if triangle_type == "symmetrical": 91 | if abs(rmax)>=rlimit and abs(rmin)>=rlimit and slmin>=slmin_limit and slmax<=-1*slmax_limit: 92 | ohlc.loc[candle_idx, "chart_type"] = "triangle" 93 | ohlc.loc[candle_idx, "triangle_type"] = "symmetrical" 94 | ohlc.loc[candle_idx, "triangle_slmax"] = slmax 95 | ohlc.loc[candle_idx, "triangle_slmin"] = slmin 96 | ohlc.loc[candle_idx, "triangle_intercmin"] = intercmin 97 | ohlc.loc[candle_idx, "triangle_intercmax"] = intercmax 98 | ohlc.at[candle_idx, "triangle_high_idx"] = xxmax 99 | ohlc.at[candle_idx, "triangle_low_idx"] = xxmin 100 | ohlc.loc[candle_idx, "triangle_point"] = candle_idx 101 | 102 | 103 | elif triangle_type == "ascending": 104 | if abs(rmax)>=rlimit and abs(rmin)>=rlimit and slmin>=slmin_limit and (slmax>=-1*slmax_limit and slmax <= slmax_limit): 105 | ohlc.loc[candle_idx, "chart_type"] = "triangle" 106 | ohlc.loc[candle_idx, "triangle_type"] = "ascending" 107 | ohlc.loc[candle_idx, "triangle_slmax"] = slmax 108 | ohlc.loc[candle_idx, "triangle_slmin"] = slmin 109 | ohlc.loc[candle_idx, "triangle_intercmin"] = intercmin 110 | ohlc.loc[candle_idx, "triangle_intercmax"] = intercmax 111 | ohlc.at[candle_idx, "triangle_high_idx"] = xxmax 112 | ohlc.at[candle_idx, "triangle_low_idx"] = xxmin 113 | ohlc.loc[candle_idx, "triangle_point"] = candle_idx 114 | 115 | 116 | elif triangle_type == "descending": 117 | if abs(rmax)>=rlimit and abs(rmin)>=rlimit and slmax<=-1*slmax_limit and (slmin>=-1*slmin_limit and slmin <= slmin_limit): 118 | ohlc.loc[candle_idx, "chart_type"] = "triangle" 119 | ohlc.loc[candle_idx, "triangle_type"] = "descending" 120 | ohlc.loc[candle_idx, "triangle_slmax"] = slmax 121 | ohlc.loc[candle_idx, "triangle_slmin"] = slmin 122 | ohlc.loc[candle_idx, "triangle_intercmin"] = intercmin 123 | ohlc.loc[candle_idx, "triangle_intercmax"] = intercmax 124 | ohlc.at[candle_idx, "triangle_high_idx"] = xxmax 125 | ohlc.at[candle_idx, "triangle_low_idx"] = xxmin 126 | ohlc.loc[candle_idx, "triangle_point"] = candle_idx 127 | print(f"Found pattern at index: {candle_idx}") 128 | return ohlc 129 | 130 | 131 | -------------------------------------------------------------------------------- /chart_patterns/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Date : 2023-12-25 3 | Author: Zetra Team 4 | 5 | """ 6 | 7 | 8 | import numpy as np 9 | import pandas as pd 10 | import sys 11 | 12 | from typing import Union 13 | 14 | def columns_message(msg: str) -> None: 15 | print(f"No `{msg.title()}` or `{msg}` price column ") 16 | sys.exit() 17 | 18 | def check_ohlc_names(ohlc: pd.DataFrame) -> Union[pd.DataFrame, None]: 19 | """ 20 | Check if the OHLC dataframe has the open, high, low and close columns 21 | 22 | :params ohlc is a dataframe with Open, High, Low, Close data 23 | :type :pd.DataFrame 24 | 25 | :return (Union[pd.DataFrame, None]) 26 | """ 27 | 28 | for name in ["open", "high", "low", "close"]: 29 | if ohlc.columns.str.lower().str.contains(name).sum() == 0: 30 | columns_message(name) 31 | else: 32 | 33 | result = ohlc.columns.str.lower().str.contains(name).tolist() 34 | index = np.where(result)[0][0] 35 | column = ohlc.columns[index] 36 | ohlc.rename(columns = {column: name }, inplace=True) 37 | 38 | 39 | return ohlc -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | category = "main" 8 | optional = false 9 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 10 | files = [ 11 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 12 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 13 | ] 14 | 15 | [[package]] 16 | name = "exceptiongroup" 17 | version = "1.2.0" 18 | description = "Backport of PEP 654 (exception groups)" 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.7" 22 | files = [ 23 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 24 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 25 | ] 26 | 27 | [package.extras] 28 | test = ["pytest (>=6)"] 29 | 30 | [[package]] 31 | name = "iniconfig" 32 | version = "2.0.0" 33 | description = "brain-dead simple config-ini parsing" 34 | category = "main" 35 | optional = false 36 | python-versions = ">=3.7" 37 | files = [ 38 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 39 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 40 | ] 41 | 42 | [[package]] 43 | name = "kaleido" 44 | version = "0.1.0.post1" 45 | description = "Static image export for web-based visualization libraries with zero dependencies" 46 | category = "main" 47 | optional = false 48 | python-versions = "*" 49 | files = [ 50 | {file = "kaleido-0.1.0.post1-py2.py3-none-win32.whl", hash = "sha256:636aedcd89f359f54687b4fe331776a7224f7c31c27d03230e4e13c7cf4cb66a"}, 51 | {file = "kaleido-0.1.0.post1-py2.py3-none-win_amd64.whl", hash = "sha256:2a942606a13c70dfd0a02e092ec140a1083e093ae06661c5e1b1179e477a9e44"}, 52 | ] 53 | 54 | [[package]] 55 | name = "numpy" 56 | version = "1.24.4" 57 | description = "Fundamental package for array computing in Python" 58 | category = "main" 59 | optional = false 60 | python-versions = ">=3.8" 61 | files = [ 62 | {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, 63 | {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, 64 | {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, 65 | {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, 66 | {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, 67 | {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, 68 | {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, 69 | {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, 70 | {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, 71 | {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, 72 | {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, 73 | {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, 74 | {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, 75 | {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, 76 | {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, 77 | {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, 78 | {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, 79 | {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, 80 | {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, 81 | {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, 82 | {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, 83 | {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, 84 | {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, 85 | {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, 86 | {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, 87 | {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, 88 | {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, 89 | {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, 90 | ] 91 | 92 | [[package]] 93 | name = "packaging" 94 | version = "23.2" 95 | description = "Core utilities for Python packages" 96 | category = "main" 97 | optional = false 98 | python-versions = ">=3.7" 99 | files = [ 100 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 101 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 102 | ] 103 | 104 | [[package]] 105 | name = "pandas" 106 | version = "1.3.5" 107 | description = "Powerful data structures for data analysis, time series, and statistics" 108 | category = "main" 109 | optional = false 110 | python-versions = ">=3.7.1" 111 | files = [ 112 | {file = "pandas-1.3.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:62d5b5ce965bae78f12c1c0df0d387899dd4211ec0bdc52822373f13a3a022b9"}, 113 | {file = "pandas-1.3.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:adfeb11be2d54f275142c8ba9bf67acee771b7186a5745249c7d5a06c670136b"}, 114 | {file = "pandas-1.3.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60a8c055d58873ad81cae290d974d13dd479b82cbb975c3e1fa2cf1920715296"}, 115 | {file = "pandas-1.3.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd541ab09e1f80a2a1760032d665f6e032d8e44055d602d65eeea6e6e85498cb"}, 116 | {file = "pandas-1.3.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2651d75b9a167cc8cc572cf787ab512d16e316ae00ba81874b560586fa1325e0"}, 117 | {file = "pandas-1.3.5-cp310-cp310-win_amd64.whl", hash = "sha256:aaf183a615ad790801fa3cf2fa450e5b6d23a54684fe386f7e3208f8b9bfbef6"}, 118 | {file = "pandas-1.3.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:344295811e67f8200de2390093aeb3c8309f5648951b684d8db7eee7d1c81fb7"}, 119 | {file = "pandas-1.3.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552020bf83b7f9033b57cbae65589c01e7ef1544416122da0c79140c93288f56"}, 120 | {file = "pandas-1.3.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cce0c6bbeb266b0e39e35176ee615ce3585233092f685b6a82362523e59e5b4"}, 121 | {file = "pandas-1.3.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d28a3c65463fd0d0ba8bbb7696b23073efee0510783340a44b08f5e96ffce0c"}, 122 | {file = "pandas-1.3.5-cp37-cp37m-win32.whl", hash = "sha256:a62949c626dd0ef7de11de34b44c6475db76995c2064e2d99c6498c3dba7fe58"}, 123 | {file = "pandas-1.3.5-cp37-cp37m-win_amd64.whl", hash = "sha256:8025750767e138320b15ca16d70d5cdc1886e8f9cc56652d89735c016cd8aea6"}, 124 | {file = "pandas-1.3.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fe95bae4e2d579812865db2212bb733144e34d0c6785c0685329e5b60fcb85dd"}, 125 | {file = "pandas-1.3.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f261553a1e9c65b7a310302b9dbac31cf0049a51695c14ebe04e4bfd4a96f02"}, 126 | {file = "pandas-1.3.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b6dbec5f3e6d5dc80dcfee250e0a2a652b3f28663492f7dab9a24416a48ac39"}, 127 | {file = "pandas-1.3.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3bc49af96cd6285030a64779de5b3688633a07eb75c124b0747134a63f4c05f"}, 128 | {file = "pandas-1.3.5-cp38-cp38-win32.whl", hash = "sha256:b6b87b2fb39e6383ca28e2829cddef1d9fc9e27e55ad91ca9c435572cdba51bf"}, 129 | {file = "pandas-1.3.5-cp38-cp38-win_amd64.whl", hash = "sha256:a395692046fd8ce1edb4c6295c35184ae0c2bbe787ecbe384251da609e27edcb"}, 130 | {file = "pandas-1.3.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd971a3f08b745a75a86c00b97f3007c2ea175951286cdda6abe543e687e5f2f"}, 131 | {file = "pandas-1.3.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37f06b59e5bc05711a518aa10beaec10942188dccb48918bb5ae602ccbc9f1a0"}, 132 | {file = "pandas-1.3.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c21778a688d3712d35710501f8001cdbf96eb70a7c587a3d5613573299fdca6"}, 133 | {file = "pandas-1.3.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3345343206546545bc26a05b4602b6a24385b5ec7c75cb6059599e3d56831da2"}, 134 | {file = "pandas-1.3.5-cp39-cp39-win32.whl", hash = "sha256:c69406a2808ba6cf580c2255bcf260b3f214d2664a3a4197d0e640f573b46fd3"}, 135 | {file = "pandas-1.3.5-cp39-cp39-win_amd64.whl", hash = "sha256:32e1a26d5ade11b547721a72f9bfc4bd113396947606e00d5b4a5b79b3dcb006"}, 136 | {file = "pandas-1.3.5.tar.gz", hash = "sha256:1e4285f5de1012de20ca46b188ccf33521bff61ba5c5ebd78b4fb28e5416a9f1"}, 137 | ] 138 | 139 | [package.dependencies] 140 | numpy = [ 141 | {version = ">=1.17.3", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""}, 142 | {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""}, 143 | {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""}, 144 | {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, 145 | ] 146 | python-dateutil = ">=2.7.3" 147 | pytz = ">=2017.3" 148 | 149 | [package.extras] 150 | test = ["hypothesis (>=3.58)", "pytest (>=6.0)", "pytest-xdist"] 151 | 152 | [[package]] 153 | name = "plotly" 154 | version = "5.18.0" 155 | description = "An open-source, interactive data visualization library for Python" 156 | category = "main" 157 | optional = false 158 | python-versions = ">=3.6" 159 | files = [ 160 | {file = "plotly-5.18.0-py3-none-any.whl", hash = "sha256:23aa8ea2f4fb364a20d34ad38235524bd9d691bf5299e800bca608c31e8db8de"}, 161 | {file = "plotly-5.18.0.tar.gz", hash = "sha256:360a31e6fbb49d12b007036eb6929521343d6bee2236f8459915821baefa2cbb"}, 162 | ] 163 | 164 | [package.dependencies] 165 | packaging = "*" 166 | tenacity = ">=6.2.0" 167 | 168 | [[package]] 169 | name = "pluggy" 170 | version = "1.3.0" 171 | description = "plugin and hook calling mechanisms for python" 172 | category = "main" 173 | optional = false 174 | python-versions = ">=3.8" 175 | files = [ 176 | {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, 177 | {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, 178 | ] 179 | 180 | [package.extras] 181 | dev = ["pre-commit", "tox"] 182 | testing = ["pytest", "pytest-benchmark"] 183 | 184 | [[package]] 185 | name = "pytest" 186 | version = "7.4.4" 187 | description = "pytest: simple powerful testing with Python" 188 | category = "main" 189 | optional = false 190 | python-versions = ">=3.7" 191 | files = [ 192 | {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, 193 | {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, 194 | ] 195 | 196 | [package.dependencies] 197 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 198 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 199 | iniconfig = "*" 200 | packaging = "*" 201 | pluggy = ">=0.12,<2.0" 202 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 203 | 204 | [package.extras] 205 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 206 | 207 | [[package]] 208 | name = "python-dateutil" 209 | version = "2.8.2" 210 | description = "Extensions to the standard Python datetime module" 211 | category = "main" 212 | optional = false 213 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 214 | files = [ 215 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 216 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 217 | ] 218 | 219 | [package.dependencies] 220 | six = ">=1.5" 221 | 222 | [[package]] 223 | name = "pytz" 224 | version = "2023.3.post1" 225 | description = "World timezone definitions, modern and historical" 226 | category = "main" 227 | optional = false 228 | python-versions = "*" 229 | files = [ 230 | {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, 231 | {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, 232 | ] 233 | 234 | [[package]] 235 | name = "scipy" 236 | version = "1.8.0" 237 | description = "SciPy: Scientific Library for Python" 238 | category = "main" 239 | optional = false 240 | python-versions = ">=3.8,<3.11" 241 | files = [ 242 | {file = "scipy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87b01c7d5761e8a266a0fbdb9d88dcba0910d63c1c671bdb4d99d29f469e9e03"}, 243 | {file = "scipy-1.8.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ae3e327da323d82e918e593460e23babdce40d7ab21490ddf9fc06dec6b91a18"}, 244 | {file = "scipy-1.8.0-cp310-cp310-macosx_12_0_universal2.macosx_10_9_x86_64.whl", hash = "sha256:16e09ef68b352d73befa8bcaf3ebe25d3941fe1a58c82909d5589856e6bc8174"}, 245 | {file = "scipy-1.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c17a1878d00a5dd2797ccd73623ceca9d02375328f6218ee6d921e1325e61aff"}, 246 | {file = "scipy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937d28722f13302febde29847bbe554b89073fbb924a30475e5ed7b028898b5f"}, 247 | {file = "scipy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:8f4d059a97b29c91afad46b1737274cb282357a305a80bdd9e8adf3b0ca6a3f0"}, 248 | {file = "scipy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:38aa39b6724cb65271e469013aeb6f2ce66fd44f093e241c28a9c6bc64fd79ed"}, 249 | {file = "scipy-1.8.0-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:559a8a4c03a5ba9fe3232f39ed24f86457e4f3f6c0abbeae1fb945029f092720"}, 250 | {file = "scipy-1.8.0-cp38-cp38-macosx_12_0_universal2.macosx_10_9_x86_64.whl", hash = "sha256:f4a6d3b9f9797eb2d43938ac2c5d96d02aed17ef170c8b38f11798717523ddba"}, 251 | {file = "scipy-1.8.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b2c2af4183ed09afb595709a8ef5783b2baf7f41e26ece24e1329c109691a7"}, 252 | {file = "scipy-1.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a279e27c7f4566ef18bab1b1e2c37d168e365080974758d107e7d237d3f0f484"}, 253 | {file = "scipy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad5be4039147c808e64f99c0e8a9641eb5d2fa079ff5894dcd8240e94e347af4"}, 254 | {file = "scipy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:3d9dd6c8b93a22bf9a3a52d1327aca7e092b1299fb3afc4f89e8eba381be7b59"}, 255 | {file = "scipy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:5e73343c5e0d413c1f937302b2e04fb07872f5843041bcfd50699aef6e95e399"}, 256 | {file = "scipy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:de2e80ee1d925984c2504812a310841c241791c5279352be4707cdcd7c255039"}, 257 | {file = "scipy-1.8.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:c2bae431d127bf0b1da81fc24e4bba0a84d058e3a96b9dd6475dfcb3c5e8761e"}, 258 | {file = "scipy-1.8.0-cp39-cp39-macosx_12_0_universal2.macosx_10_9_x86_64.whl", hash = "sha256:723b9f878095ed994756fa4ee3060c450e2db0139c5ba248ee3f9628bd64e735"}, 259 | {file = "scipy-1.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:011d4386b53b933142f58a652aa0f149c9b9242abd4f900b9f4ea5fbafc86b89"}, 260 | {file = "scipy-1.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f0cd9c0bd374ef834ee1e0f0999678d49dcc400ea6209113d81528958f97c7"}, 261 | {file = "scipy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3720d0124aced49f6f2198a6900304411dbbeed12f56951d7c66ebef05e3df6"}, 262 | {file = "scipy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:3d573228c10a3a8c32b9037be982e6440e411b443a6267b067cac72f690b8d56"}, 263 | {file = "scipy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bb7088e89cd751acf66195d2f00cf009a1ea113f3019664032d9075b1e727b6c"}, 264 | {file = "scipy-1.8.0.tar.gz", hash = "sha256:31d4f2d6b724bc9a98e527b5849b8a7e589bf1ea630c33aa563eda912c9ff0bd"}, 265 | ] 266 | 267 | [package.dependencies] 268 | numpy = ">=1.17.3,<1.25.0" 269 | 270 | [[package]] 271 | name = "six" 272 | version = "1.16.0" 273 | description = "Python 2 and 3 compatibility utilities" 274 | category = "main" 275 | optional = false 276 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 277 | files = [ 278 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 279 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 280 | ] 281 | 282 | [[package]] 283 | name = "tenacity" 284 | version = "8.2.3" 285 | description = "Retry code until it succeeds" 286 | category = "main" 287 | optional = false 288 | python-versions = ">=3.7" 289 | files = [ 290 | {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, 291 | {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, 292 | ] 293 | 294 | [package.extras] 295 | doc = ["reno", "sphinx", "tornado (>=4.5)"] 296 | 297 | [[package]] 298 | name = "tomli" 299 | version = "2.0.1" 300 | description = "A lil' TOML parser" 301 | category = "main" 302 | optional = false 303 | python-versions = ">=3.7" 304 | files = [ 305 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 306 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 307 | ] 308 | 309 | [[package]] 310 | name = "tqdm" 311 | version = "4.66.4" 312 | description = "Fast, Extensible Progress Meter" 313 | category = "main" 314 | optional = false 315 | python-versions = ">=3.7" 316 | files = [ 317 | {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, 318 | {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, 319 | ] 320 | 321 | [package.dependencies] 322 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 323 | 324 | [package.extras] 325 | dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] 326 | notebook = ["ipywidgets (>=6)"] 327 | slack = ["slack-sdk"] 328 | telegram = ["requests"] 329 | 330 | [metadata] 331 | lock-version = "2.0" 332 | python-versions = "^3.8,<3.11" 333 | content-hash = "b9330a2d8404255a2780b5a6df6e8048a101313e76d0061cbfd3cead4f62a170" 334 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "chart-patterns" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Info Zetra "] 6 | readme = "README.md" 7 | packages = [{include = "chart_patterns"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.8,<3.11" 11 | pandas = "1.3.5" 12 | plotly = "^5.18.0" 13 | scipy = "1.8.0" 14 | pytest = "^7.4.4" 15 | kaleido = "0.1.0post1" 16 | tqdm = "^4.66.4" 17 | 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | colorama==0.4.6 ; python_version >= "3.8" and python_version < "3.11" and sys_platform == "win32" or python_version >= "3.8" and python_version < "3.11" and platform_system == "Windows" 2 | exceptiongroup==1.2.0 ; python_version >= "3.8" and python_version < "3.11" 3 | iniconfig==2.0.0 ; python_version >= "3.8" and python_version < "3.11" 4 | kaleido==0.1.0.post1 ; python_version >= "3.8" and python_version < "3.11" 5 | numpy==1.24.4 ; python_version < "3.11" and python_version >= "3.8" 6 | packaging==23.2 ; python_version >= "3.8" and python_version < "3.11" 7 | pandas==1.3.5 ; python_version >= "3.8" and python_version < "3.11" 8 | plotly==5.18.0 ; python_version >= "3.8" and python_version < "3.11" 9 | pluggy==1.3.0 ; python_version >= "3.8" and python_version < "3.11" 10 | pytest==7.4.4 ; python_version >= "3.8" and python_version < "3.11" 11 | python-dateutil==2.8.2 ; python_version >= "3.8" and python_version < "3.11" 12 | pytz==2023.3.post1 ; python_version >= "3.8" and python_version < "3.11" 13 | scipy==1.8.0 ; python_version >= "3.8" and python_version < "3.11" 14 | six==1.16.0 ; python_version >= "3.8" and python_version < "3.11" 15 | tenacity==8.2.3 ; python_version >= "3.8" and python_version < "3.11" 16 | tomli==2.0.1 ; python_version >= "3.8" and python_version < "3.11" 17 | tqdm==4.66.4 ; python_version >= "3.8" and python_version < "3.11" 18 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeta-zetra/chart_patterns/44f8baa3b15dfa6b9d54b51d2307c85a255d3a40/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_double.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | import os 4 | import sys 5 | 6 | from chart_patterns.chart_patterns.doubles import find_doubles_pattern 7 | 8 | 9 | 10 | def test_find_doubles_bottom_pattern(): 11 | """ 12 | Test finding doubles bottom pattern 13 | """ 14 | ohlc = pd.read_csv("./data/eurusd-4h.csv") 15 | 16 | ohlc = ohlc.iloc[:37,:] 17 | ohlc = ohlc.reset_index() 18 | ohlc = find_doubles_pattern(ohlc, double="bottoms") 19 | df = ohlc[ohlc["double_idx"].str.len()>0] 20 | assert df.shape[0] == 4 21 | 22 | 23 | def test_find_doubles_top_pattern(): 24 | """ 25 | Test finding doubles top pattern 26 | """ 27 | 28 | ohlc = pd.read_csv("./data/eurusd-4h.csv") 29 | ohlc = ohlc.iloc[400:440,:].reset_index() 30 | 31 | ohlc = find_doubles_pattern(ohlc, double="tops") 32 | df = ohlc[ohlc["double_idx"].str.len()>0] 33 | assert df.shape[0] == 2 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/test_flag.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | import os 4 | 5 | from chart_patterns.chart_patterns.flag import find_flag_pattern 6 | 7 | 8 | def test_find_flag_pattern(): 9 | """ 10 | Test finding the flag pattern 11 | """ 12 | ohlc = pd.read_csv("./data/eurusd-4h.csv") 13 | ohlc = ohlc.iloc[900:1200,:].reset_index() 14 | ohlc = find_flag_pattern(ohlc) 15 | df = ohlc[ohlc["flag_point"]>0] 16 | assert df.shape[0] == 4 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/test_head_and_shoulders.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | import os 4 | 5 | 6 | 7 | from chart_patterns.chart_patterns.head_and_shoulders import find_head_and_shoulders 8 | 9 | 10 | def test_find_head_and_shoulders(): 11 | """ 12 | Test finding the head and shoulders pattern 13 | """ 14 | 15 | ohlc = pd.read_csv("./data/eurusd-4h.csv") 16 | ohlc = ohlc.iloc[4100:4400,:].reset_index() 17 | ohlc = find_head_and_shoulders(ohlc) 18 | df = ohlc[ohlc["hs_idx"].str.len()>0] 19 | assert df.shape[0] == 1 -------------------------------------------------------------------------------- /tests/test_inverse_head_and_shoulders.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | import os 4 | 5 | 6 | from chart_patterns.chart_patterns.inverse_head_and_shoulders import find_inverse_head_and_shoulders 7 | 8 | 9 | def test_find_inverse_head_and_shoulders(): 10 | """ 11 | Test finding the inverse head and shoulders 12 | """ 13 | 14 | ohlc = pd.read_csv("./data/eurusd-4h.csv") 15 | ohlc = ohlc.iloc[4700:5000,:].reset_index() 16 | ohlc = find_inverse_head_and_shoulders(ohlc) 17 | df = ohlc[ohlc["ihs_idx"].str.len()>0] 18 | assert df.shape[0] == 1 -------------------------------------------------------------------------------- /tests/test_pennant.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | import os 4 | 5 | from chart_patterns.chart_patterns.pennant import find_pennant 6 | 7 | 8 | def test_find_pennant(): 9 | """ 10 | Test finding the pennant patterns 11 | """ 12 | 13 | ohlc = pd.read_csv("./data/eurusd-4h.csv") 14 | ohlc = ohlc.iloc[3400:3600,:].reset_index() 15 | ohlc = find_pennant(ohlc) 16 | df = ohlc[ohlc["pennant_point"]>0] 17 | assert df.shape[0] == 4 -------------------------------------------------------------------------------- /tests/test_triangle.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | import os 4 | 5 | 6 | from chart_patterns.chart_patterns.triangles import find_triangle_pattern 7 | 8 | 9 | 10 | def test_find_ascending_triangle(): 11 | """ Test finding the ascending triangle pattern """ 12 | 13 | ohlc = pd.read_csv("./data/eurusd-4h.csv") 14 | ohlc = ohlc.iloc[7200:7400,:].reset_index() 15 | ohlc = find_triangle_pattern(ohlc, triangle_type = "ascending") 16 | df = ohlc[ohlc["triangle_point"]>0] 17 | assert df.shape[0] == 1 18 | 19 | def test_find_descending_triangle(): 20 | """ Test finding the descending triangle pattern """ 21 | 22 | ohlc = pd.read_csv("./data/eurusd-4h.csv") 23 | ohlc = ohlc.iloc[19100:19280,:].reset_index() 24 | ohlc = find_triangle_pattern(ohlc, triangle_type = "descending") 25 | df = ohlc[ohlc["triangle_point"]>0] 26 | assert df.shape[0] == 6 27 | 28 | 29 | def test_find_symmetrical_triangle(): 30 | """ Test finding the symmetrical triangle pattern """ 31 | ohlc = pd.read_csv("./data/eurusd-4h.csv") 32 | ohlc = ohlc.iloc[:160,:].reset_index() 33 | ohlc = find_triangle_pattern(ohlc, triangle_type = "symmetrical") 34 | df = ohlc[ohlc["triangle_point"]>0] 35 | assert df.shape[0] == 3 --------------------------------------------------------------------------------