yfinance-ta-patterns 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 eminsk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: yfinance-ta-patterns
3
+ Version: 0.1.1
4
+ Summary: CLI and helpers to scan yfinance data for TA-Lib candlestick patterns.
5
+ Author: eminsk
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 eminsk
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/eminsk/yfinance-ta-patterns
29
+ Project-URL: Homepage, https://github.com/eminsk/yfinance-ta-patterns
30
+ Requires-Python: >=3.12
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: ta-lib>=0.6.8
34
+ Requires-Dist: yfinance>=0.2.66
35
+ Requires-Dist: pandas>=2.2.0
36
+ Requires-Dist: pytz>=2024.1
37
+ Requires-Dist: numpy>=1.26
38
+ Dynamic: license-file
39
+
40
+ ## Forex Candlestick Scanner
41
+
42
+ Python package + CLI that downloads Yahoo Finance data via `yfinance` and runs TA-Lib candlestick detectors for your symbol, timeframe, and date filters.
43
+
44
+ ### Quick start
45
+ ```bash
46
+ # create env
47
+ python -m venv .venv
48
+ .\.venv\Scripts\activate # PowerShell; adjust for your shell
49
+
50
+ # install the package (editable for local dev)
51
+ pip install -e .
52
+
53
+ # run CLI (two entrypoints)
54
+ yfinance-ta-patterns --pattern KICKING --symbol EURUSD --timeframe 5m --period 60d
55
+ # or
56
+ yftp --all-patterns --symbol EURUSD --timeframe 5m --period 60d --date 2025-04-01
57
+ ```
58
+ `pyproject.toml` targets Python `>=3.12`.
59
+
60
+ ### CLI usage
61
+ ```
62
+ yfinance-ta-patterns [--pattern NAME | --all-patterns]
63
+ [--symbol EURUSD] [--period 60d]
64
+ [--timeframe 15m] [--date YYYY-MM-DD]
65
+ [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD]
66
+ ```
67
+ - `--pattern`: Single candlestick name (with or without `CDL` prefix).
68
+ - `--all-patterns`: Scan every TA-Lib candlestick detector.
69
+ - `--symbol`: Ticker without suffix; `=X` is appended automatically for Forex (default `EURUSD`).
70
+ - `--period`: History window passed to `yfinance` (e.g., `60d`, `1mo`).
71
+ - `--timeframe`: Use `M1/M5/M15/M30/H1/D1` or raw `yfinance` intervals (`1m`, `5m`, `1h`, `1d`, etc.).
72
+ - `--date`: Filter signals for a single day.
73
+ - `--start-date` / `--end-date`: Inclusive range filter (cannot be combined with `--date`).
74
+
75
+ ### Examples
76
+ - All patterns for a single day:
77
+ ```bash
78
+ yftp --all-patterns --symbol EURUSD --timeframe 5m --period 60d --date 2025-04-01
79
+ ```
80
+ - All patterns across a range:
81
+ ```bash
82
+ yftp --all-patterns --symbol EURUSD --timeframe 5m --period 60d --start-date 2025-04-01 --end-date 2025-04-10
83
+ ```
84
+ - One pattern without date filter:
85
+ ```bash
86
+ yftp --pattern KICKING --symbol EURUSD --timeframe 5m --period 60d
87
+ ```
88
+
89
+ ### Data loader
90
+ `yfinance_ta_patterns/forex_data_loader.py` fetches and normalizes OHLC data. It appends `=X` to symbols when missing and converts timestamps to UTC before shifting to the configured timezone (`Europe/Moscow` by default).
91
+
92
+ ### Pattern analysis
93
+ `yfinance_ta_patterns/pattern_analyzer.py` wraps TA-Lib's `CDL*` functions, returning non-zero signals and applying optional date filters. When `--all-patterns` is used, it iterates over the full catalog and prints hits per pattern.
94
+
95
+ ### Pattern ranking helper (optional)
96
+ `yfinance_ta_patterns/pattern_tester.py` contains a backtesting-style ranking tool. It depends on `utils.pattern_helper.PatternHelper` to enumerate patterns; add that helper before running comparisons or exports.
97
+
98
+ ### Project layout
99
+ - `yfinance_ta_patterns/cli.py`: CLI entry point and argument parsing.
100
+ - `yfinance_ta_patterns/forex_data_loader.py`: Data download and timezone normalization.
101
+ - `yfinance_ta_patterns/pattern_analyzer.py`: Candlestick signal extraction.
102
+ - `yfinance_ta_patterns/pattern_tester.py`: Experimental ranking/backtest utilities.
103
+ - `main.py`: Thin wrapper to launch the CLI.
104
+
105
+ ### Packaging and releases
106
+ - Nightly GitHub Actions workflow builds onefile Nuitka binaries for Windows/macOS/Linux and publishes nightly prereleases.
107
+ - A PyPI publish workflow can be enabled by adding a secret `PYPI_API_TOKEN`; tags like `v0.1.0` will build sdist/wheel and upload.
108
+ - TA-Lib is installed from PyPI on Windows; Linux/macOS CI builds the TA-Lib C library from source for the binaries. For local installs, PyPI wheels (`ta-lib` >=0.6.8) cover CPython 3.9–3.14.
@@ -0,0 +1,69 @@
1
+ ## Forex Candlestick Scanner
2
+
3
+ Python package + CLI that downloads Yahoo Finance data via `yfinance` and runs TA-Lib candlestick detectors for your symbol, timeframe, and date filters.
4
+
5
+ ### Quick start
6
+ ```bash
7
+ # create env
8
+ python -m venv .venv
9
+ .\.venv\Scripts\activate # PowerShell; adjust for your shell
10
+
11
+ # install the package (editable for local dev)
12
+ pip install -e .
13
+
14
+ # run CLI (two entrypoints)
15
+ yfinance-ta-patterns --pattern KICKING --symbol EURUSD --timeframe 5m --period 60d
16
+ # or
17
+ yftp --all-patterns --symbol EURUSD --timeframe 5m --period 60d --date 2025-04-01
18
+ ```
19
+ `pyproject.toml` targets Python `>=3.12`.
20
+
21
+ ### CLI usage
22
+ ```
23
+ yfinance-ta-patterns [--pattern NAME | --all-patterns]
24
+ [--symbol EURUSD] [--period 60d]
25
+ [--timeframe 15m] [--date YYYY-MM-DD]
26
+ [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD]
27
+ ```
28
+ - `--pattern`: Single candlestick name (with or without `CDL` prefix).
29
+ - `--all-patterns`: Scan every TA-Lib candlestick detector.
30
+ - `--symbol`: Ticker without suffix; `=X` is appended automatically for Forex (default `EURUSD`).
31
+ - `--period`: History window passed to `yfinance` (e.g., `60d`, `1mo`).
32
+ - `--timeframe`: Use `M1/M5/M15/M30/H1/D1` or raw `yfinance` intervals (`1m`, `5m`, `1h`, `1d`, etc.).
33
+ - `--date`: Filter signals for a single day.
34
+ - `--start-date` / `--end-date`: Inclusive range filter (cannot be combined with `--date`).
35
+
36
+ ### Examples
37
+ - All patterns for a single day:
38
+ ```bash
39
+ yftp --all-patterns --symbol EURUSD --timeframe 5m --period 60d --date 2025-04-01
40
+ ```
41
+ - All patterns across a range:
42
+ ```bash
43
+ yftp --all-patterns --symbol EURUSD --timeframe 5m --period 60d --start-date 2025-04-01 --end-date 2025-04-10
44
+ ```
45
+ - One pattern without date filter:
46
+ ```bash
47
+ yftp --pattern KICKING --symbol EURUSD --timeframe 5m --period 60d
48
+ ```
49
+
50
+ ### Data loader
51
+ `yfinance_ta_patterns/forex_data_loader.py` fetches and normalizes OHLC data. It appends `=X` to symbols when missing and converts timestamps to UTC before shifting to the configured timezone (`Europe/Moscow` by default).
52
+
53
+ ### Pattern analysis
54
+ `yfinance_ta_patterns/pattern_analyzer.py` wraps TA-Lib's `CDL*` functions, returning non-zero signals and applying optional date filters. When `--all-patterns` is used, it iterates over the full catalog and prints hits per pattern.
55
+
56
+ ### Pattern ranking helper (optional)
57
+ `yfinance_ta_patterns/pattern_tester.py` contains a backtesting-style ranking tool. It depends on `utils.pattern_helper.PatternHelper` to enumerate patterns; add that helper before running comparisons or exports.
58
+
59
+ ### Project layout
60
+ - `yfinance_ta_patterns/cli.py`: CLI entry point and argument parsing.
61
+ - `yfinance_ta_patterns/forex_data_loader.py`: Data download and timezone normalization.
62
+ - `yfinance_ta_patterns/pattern_analyzer.py`: Candlestick signal extraction.
63
+ - `yfinance_ta_patterns/pattern_tester.py`: Experimental ranking/backtest utilities.
64
+ - `main.py`: Thin wrapper to launch the CLI.
65
+
66
+ ### Packaging and releases
67
+ - Nightly GitHub Actions workflow builds onefile Nuitka binaries for Windows/macOS/Linux and publishes nightly prereleases.
68
+ - A PyPI publish workflow can be enabled by adding a secret `PYPI_API_TOKEN`; tags like `v0.1.0` will build sdist/wheel and upload.
69
+ - TA-Lib is installed from PyPI on Windows; Linux/macOS CI builds the TA-Lib C library from source for the binaries. For local installs, PyPI wheels (`ta-lib` >=0.6.8) cover CPython 3.9–3.14.
@@ -0,0 +1,33 @@
1
+ [project]
2
+ name = "yfinance-ta-patterns"
3
+ version = "0.1.1"
4
+ description = "CLI and helpers to scan yfinance data for TA-Lib candlestick patterns."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = { file = "LICENSE" }
8
+ authors = [
9
+ { name = "eminsk" },
10
+ ]
11
+ dependencies = [
12
+ "ta-lib>=0.6.8",
13
+ "yfinance>=0.2.66",
14
+ "pandas>=2.2.0",
15
+ "pytz>=2024.1",
16
+ "numpy>=1.26",
17
+ ]
18
+
19
+ [project.scripts]
20
+ yfinance-ta-patterns = "yfinance_ta_patterns.cli:main"
21
+ yftp = "yfinance_ta_patterns.cli:main"
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/eminsk/yfinance-ta-patterns"
25
+ Homepage = "https://github.com/eminsk/yfinance-ta-patterns"
26
+
27
+ [build-system]
28
+ requires = ["setuptools>=68", "wheel"]
29
+ build-backend = "setuptools.build_meta"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["."]
33
+ include = ["yfinance_ta_patterns*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,15 @@
1
+ from yfinance_ta_patterns.cli import normalize_timeframe
2
+
3
+
4
+ def test_normalize_timeframe_aliases():
5
+ assert normalize_timeframe("M5") == "5m"
6
+ assert normalize_timeframe("h1") == "1h"
7
+
8
+
9
+ def test_normalize_timeframe_invalid():
10
+ try:
11
+ normalize_timeframe("invalid")
12
+ except ValueError as exc:
13
+ assert "Unsupported timeframe" in str(exc)
14
+ else:
15
+ raise AssertionError("Expected ValueError for invalid timeframe")
@@ -0,0 +1,13 @@
1
+ """YFinance TA Patterns package."""
2
+
3
+ from importlib.metadata import version, PackageNotFoundError
4
+
5
+ try:
6
+ __version__ = version("yfinance-ta-patterns")
7
+ except PackageNotFoundError: # pragma: no cover - during editable installs
8
+ __version__ = "0.0.0"
9
+
10
+ from .forex_data_loader import ForexDataLoader
11
+ from .pattern_analyzer import PatternAnalyzer
12
+
13
+ __all__ = ["ForexDataLoader", "PatternAnalyzer", "__version__"]
@@ -0,0 +1,160 @@
1
+ import argparse
2
+
3
+ from .forex_data_loader import ForexDataLoader
4
+ from .pattern_analyzer import PatternAnalyzer
5
+
6
+
7
+ TIMEFRAME_MAP = {
8
+ "M1": "1m",
9
+ "M5": "5m",
10
+ "M15": "15m",
11
+ "M30": "30m",
12
+ "H1": "1h",
13
+ "D1": "1d",
14
+ }
15
+ VALID_INTERVALS = {
16
+ "1m",
17
+ "2m",
18
+ "5m",
19
+ "15m",
20
+ "30m",
21
+ "60m",
22
+ "90m",
23
+ "1h",
24
+ "1d",
25
+ "5d",
26
+ "1wk",
27
+ "1mo",
28
+ "3mo",
29
+ }
30
+
31
+
32
+ def normalize_timeframe(timeframe: str) -> str:
33
+ """Convert human-friendly timeframe names into yfinance intervals."""
34
+ tf = timeframe.upper()
35
+ interval = TIMEFRAME_MAP.get(tf, timeframe.lower())
36
+ if interval not in VALID_INTERVALS:
37
+ allowed = ", ".join(sorted(VALID_INTERVALS))
38
+ raise ValueError(f"Unsupported timeframe '{timeframe}'. Allowed: {allowed}")
39
+ return interval
40
+
41
+
42
+ def parse_args() -> argparse.Namespace:
43
+ parser = argparse.ArgumentParser(
44
+ description="Show TA-Lib candlestick pattern signals for a symbol.",
45
+ formatter_class=argparse.RawTextHelpFormatter,
46
+ epilog=(
47
+ "Examples:\n"
48
+ " # All patterns on a single day\n"
49
+ " python main.py --all-patterns --symbol EURUSD --timeframe 5m --period 60d --date 2025-04-01\n\n"
50
+ " # All patterns over a date range\n"
51
+ " python main.py --all-patterns --symbol EURUSD --timeframe 5m --period 60d "
52
+ "--start-date 2025-04-01 --end-date 2025-04-10\n\n"
53
+ " # Single pattern without date filter\n"
54
+ " python main.py --pattern KICKING --symbol EURUSD --timeframe 5m --period 60d\n"
55
+ ),
56
+ )
57
+ parser.add_argument(
58
+ "--symbol",
59
+ default="EURUSD",
60
+ help="Ticker without suffix (e.g. EURUSD, GBPUSD); '=X' will be appended automatically.",
61
+ )
62
+ parser.add_argument("--period", default="60d", help="History period, e.g. 60d")
63
+ parser.add_argument(
64
+ "--timeframe",
65
+ default="15m",
66
+ help="Timeframe (M1, M5, M15, M30, H1, D1) or raw yfinance interval (1m, 5m, 15m, 1h, 1d...).",
67
+ )
68
+ group = parser.add_mutually_exclusive_group(required=True)
69
+ group.add_argument(
70
+ "--pattern",
71
+ help="Single candlestick pattern, e.g. KICKING (CDL prefix optional).",
72
+ )
73
+ group.add_argument(
74
+ "--all-patterns",
75
+ action="store_true",
76
+ help="Scan and show signals for all TA-Lib candlestick patterns.",
77
+ )
78
+ parser.add_argument(
79
+ "--date",
80
+ help="Optional filter by date (YYYY-MM-DD). If omitted, show all signals.",
81
+ )
82
+ parser.add_argument(
83
+ "--start-date",
84
+ help="Optional start date (YYYY-MM-DD) for range filter (inclusive).",
85
+ )
86
+ parser.add_argument(
87
+ "--end-date",
88
+ help="Optional end date (YYYY-MM-DD) for range filter (inclusive).",
89
+ )
90
+ return parser.parse_args()
91
+
92
+
93
+ def main() -> None:
94
+ args = parse_args()
95
+ interval = normalize_timeframe(args.timeframe)
96
+
97
+ if args.date and (args.start_date or args.end_date):
98
+ raise ValueError("Use either --date or --start-date/--end-date, not both.")
99
+
100
+ data = ForexDataLoader(
101
+ args.symbol,
102
+ period=args.period,
103
+ interval=interval,
104
+ ).get_data()
105
+
106
+ analyzer = PatternAnalyzer(data)
107
+ range_info = ""
108
+ if args.date:
109
+ range_info = f" on {args.date}"
110
+ elif args.start_date or args.end_date:
111
+ range_info = f" from {args.start_date or 'beginning'} to {args.end_date or 'end'}"
112
+
113
+ if args.pattern:
114
+ signals = analyzer.get_signals(
115
+ args.pattern,
116
+ date=args.date,
117
+ start_date=args.start_date,
118
+ end_date=args.end_date,
119
+ )
120
+
121
+ if signals.empty:
122
+ print(
123
+ f"No signals for pattern {args.pattern} "
124
+ f"on period {args.period} timeframe {interval}{range_info}"
125
+ )
126
+ else:
127
+ print(f"Found signals for {args.pattern} ({interval}, {args.period}){range_info}:")
128
+ print(signals.to_string())
129
+ else:
130
+ print(
131
+ f"Scanning all patterns for {args.symbol} "
132
+ f"({interval}, {args.period}){range_info}..."
133
+ )
134
+ found_any = False
135
+ for pattern in sorted(analyzer.pattern_functions):
136
+ signals = analyzer.get_signals(
137
+ pattern,
138
+ date=args.date,
139
+ start_date=args.start_date,
140
+ end_date=args.end_date,
141
+ )
142
+ pattern_name = pattern.replace("CDL", "")
143
+ if signals.empty:
144
+ print(f"{pattern_name}: no signals")
145
+ continue
146
+
147
+ found_any = True
148
+ print(f"{pattern_name}:")
149
+ print(signals.to_string())
150
+ print("-" * 40)
151
+
152
+ if not found_any:
153
+ print(
154
+ f"No signals for any pattern on period {args.period} "
155
+ f"timeframe {interval}{range_info}"
156
+ )
157
+
158
+
159
+ if __name__ == "__main__":
160
+ main()
@@ -0,0 +1,53 @@
1
+ import pandas as pd
2
+ import yfinance as yf
3
+ import pytz
4
+
5
+ class ForexDataLoader:
6
+ """
7
+ Generic Data Loader for Yahoo Finance tickers (forex, stocks, crypto, etc.).
8
+ """
9
+
10
+ def __init__(
11
+ self,
12
+ symbol: str,
13
+ period: str = '60d',
14
+ interval: str = '15m',
15
+ timezone: str = 'Europe/Moscow'
16
+ ):
17
+ # Allow full tickers or append suffix if missing
18
+ self.ticker = symbol if symbol.endswith(suffix := '=X') else f"{symbol}{suffix}"
19
+ self.period = period
20
+ self.interval = interval
21
+ self.timezone = timezone
22
+
23
+ def fetch(self) -> pd.DataFrame:
24
+ """Fetch raw data via yfinance."""
25
+ return yf.download(
26
+ self.ticker,
27
+ period=self.period,
28
+ interval=self.interval,
29
+ auto_adjust=True
30
+ )
31
+
32
+ def process(self, data: pd.DataFrame) -> pd.DataFrame:
33
+ """Normalize columns and timezone."""
34
+ # Drop extra MultiIndex level if present
35
+ data.columns = data.columns.droplevel(1) if data.columns.nlevels > 1 else data.columns
36
+
37
+ if data.index.tz is None:
38
+ data.index = data.index.tz_localize(pytz.UTC)
39
+ else:
40
+ data.index = data.index.tz_convert(pytz.UTC)
41
+ # Потом конвертируем в нужную зону
42
+ data.index = data.index.tz_convert(self.timezone)
43
+ return data
44
+
45
+ def get_data(self) -> pd.DataFrame:
46
+ """Full pipeline: fetch then process."""
47
+ return self.process(self.fetch())
48
+
49
+ if __name__ == "__main__":
50
+ # Example usage
51
+ loader = ForexDataLoader("EURUSD=X")
52
+ data = loader.get_data()
53
+ print(data)
@@ -0,0 +1,101 @@
1
+ from typing import Iterable, Optional, Tuple
2
+
3
+ import pandas as pd
4
+ import talib
5
+
6
+
7
+ class PatternAnalyzer:
8
+ def __init__(self, data: pd.DataFrame):
9
+ self.data = data
10
+ self.pattern_functions = [f for f in dir(talib) if f.startswith("CDL")]
11
+
12
+ def _normalize_pattern(self, pattern: str) -> str:
13
+ pattern_upper = pattern.upper()
14
+ return pattern_upper if pattern_upper.startswith("CDL") else f"CDL{pattern_upper}"
15
+
16
+ def _normalize_dates(
17
+ self, date: Optional[str], start_date: Optional[str], end_date: Optional[str]
18
+ ) -> Tuple[Optional[pd.Timestamp], Optional[pd.Timestamp], Optional[pd.Timestamp]]:
19
+ tz = self.data.index.tz
20
+
21
+ def convert(dt: Optional[str]) -> Optional[pd.Timestamp]:
22
+ if dt is None:
23
+ return None
24
+ parsed = pd.to_datetime(dt)
25
+ if parsed.tzinfo is None:
26
+ parsed = parsed.tz_localize(tz)
27
+ else:
28
+ parsed = parsed.tz_convert(tz)
29
+ return parsed.normalize()
30
+
31
+ return convert(date), convert(start_date), convert(end_date)
32
+
33
+ def get_signals(
34
+ self,
35
+ pattern: str,
36
+ date: Optional[str] = None,
37
+ start_date: Optional[str] = None,
38
+ end_date: Optional[str] = None,
39
+ ) -> pd.Series:
40
+ """Return non-zero signals for a single candlestick pattern with optional date filters."""
41
+ normalized = self._normalize_pattern(pattern)
42
+ if normalized not in self.pattern_functions:
43
+ available = ", ".join(p.replace("CDL", "") for p in self.pattern_functions)
44
+ raise ValueError(f"Unknown pattern '{pattern}'. Available: {available}")
45
+
46
+ pattern_func = getattr(talib, normalized)
47
+ result = pattern_func(
48
+ self.data["Open"],
49
+ self.data["High"],
50
+ self.data["Low"],
51
+ self.data["Close"],
52
+ )
53
+ series = pd.Series(result, index=self.data.index, name=normalized)
54
+ signals = series[series != 0]
55
+
56
+ target_date, start_dt, end_dt = self._normalize_dates(date, start_date, end_date)
57
+
58
+ if target_date:
59
+ day_index = signals.index.normalize()
60
+ signals = signals[day_index == target_date]
61
+
62
+ if start_dt or end_dt:
63
+ day_index = signals.index.normalize()
64
+ mask = pd.Series(True, index=signals.index)
65
+ if start_dt:
66
+ mask &= day_index >= start_dt
67
+ if end_dt:
68
+ mask &= day_index <= end_dt
69
+ signals = signals[mask]
70
+
71
+ return signals
72
+
73
+ def analyze_all_for_date(self, date: str) -> Iterable[str]:
74
+ """Keep legacy all-patterns behavior for a specific date."""
75
+ messages = []
76
+ for pattern in self.pattern_functions:
77
+ pattern_func = getattr(talib, pattern)
78
+ try:
79
+ result = pattern_func(
80
+ self.data["Open"],
81
+ self.data["High"],
82
+ self.data["Low"],
83
+ self.data["Close"],
84
+ )
85
+ values = result.loc[date]
86
+ values_series = (
87
+ values
88
+ if isinstance(values, pd.Series)
89
+ else pd.Series([values], index=[pd.Timestamp(date)])
90
+ )
91
+ non_zero = values_series[values_series != 0]
92
+ messages.append(
93
+ f"{pattern} on {date} (non-zero values):\n{non_zero.to_string()}"
94
+ if not non_zero.empty
95
+ else f"{pattern} on {date}: all values are 0"
96
+ )
97
+ except KeyError:
98
+ messages.append(f"{pattern}: No data available for {date}")
99
+ except Exception as e:
100
+ messages.append(f"Error in {pattern}: {e}")
101
+ return messages
@@ -0,0 +1,258 @@
1
+ """Pattern ranking tester - find best performing candlestick patterns."""
2
+
3
+ import pandas as pd
4
+ import numpy as np
5
+ import talib
6
+ from typing import Dict, List, Tuple, Optional
7
+ from dataclasses import dataclass
8
+ from datetime import datetime, timedelta
9
+ from utils.pattern_helper import PatternHelper
10
+
11
+
12
+ @dataclass
13
+ class PatternResult:
14
+ """Result for a single pattern test."""
15
+ pattern_name: str
16
+ total_signals: int
17
+ winning_trades: int
18
+ losing_trades: int
19
+ win_rate: float
20
+ total_pnl: float
21
+ avg_pnl: float
22
+ max_profit: float
23
+ max_loss: float
24
+ sharpe_ratio: float
25
+
26
+
27
+ class PatternRankingTester:
28
+ """
29
+ Test all candlestick patterns and rank them by performance.
30
+ Can filter by economic news events.
31
+ """
32
+
33
+ __slots__ = ('_data', '_initial_capital', '_position_size', '_results', '_news_dates')
34
+
35
+ def __init__(
36
+ self,
37
+ data: pd.DataFrame,
38
+ initial_capital: float = 10000.0,
39
+ position_size: float = 100.0,
40
+ news_dates: Optional[List[str]] = None
41
+ ):
42
+ """
43
+ Initialize pattern tester.
44
+
45
+ Parameters:
46
+ -----------
47
+ data : pd.DataFrame
48
+ OHLCV data
49
+ initial_capital : float
50
+ Initial capital
51
+ position_size : float
52
+ Position size per trade
53
+ news_dates : list, optional
54
+ List of dates with important news (YYYY-MM-DD)
55
+ """
56
+ self._data = data
57
+ self._initial_capital = initial_capital
58
+ self._position_size = position_size
59
+ self._results: List[PatternResult] = []
60
+ self._news_dates = set(news_dates) if news_dates else set()
61
+
62
+ def test_all_patterns(self, filter_news: bool = False) -> List[PatternResult]:
63
+ """
64
+ Test all patterns and return ranked results.
65
+
66
+ Parameters:
67
+ -----------
68
+ filter_news : bool
69
+ If True, exclude trades during news events
70
+
71
+ Returns:
72
+ --------
73
+ list : Ranked pattern results
74
+ """
75
+ self._results = []
76
+
77
+ # Get all patterns dynamically from TA-Lib
78
+ all_patterns = PatternHelper.get_all_patterns()
79
+
80
+ for pattern_name in all_patterns:
81
+ try:
82
+ result = self._test_single_pattern(pattern_name, filter_news)
83
+ if result and result.total_signals > 0:
84
+ self._results.append(result)
85
+ except Exception as e:
86
+ print(f"Error testing {pattern_name}: {e}")
87
+ continue
88
+
89
+ # Sort by win rate, then by total PnL
90
+ self._results.sort(key=lambda x: (x.win_rate, x.total_pnl), reverse=True)
91
+
92
+ return self._results
93
+
94
+ def _test_single_pattern(
95
+ self,
96
+ pattern_name: str,
97
+ filter_news: bool
98
+ ) -> Optional[PatternResult]:
99
+ """Test a single pattern."""
100
+ # Detect pattern
101
+ pattern_func = getattr(talib, pattern_name, None)
102
+ if not pattern_func:
103
+ print(f"Pattern function not found: {pattern_name}")
104
+ return None
105
+
106
+ try:
107
+ pattern_values = pattern_func(
108
+ self._data['Open'].values,
109
+ self._data['High'].values,
110
+ self._data['Low'].values,
111
+ self._data['Close'].values
112
+ )
113
+ except Exception as e:
114
+ print(f"Error detecting pattern {pattern_name}: {e}")
115
+ return None
116
+
117
+ # Generate signals
118
+ signals = np.zeros(len(self._data))
119
+ for i in range(len(pattern_values)):
120
+ if pattern_values[i] > 0: # Bullish pattern
121
+ signals[i] = 1
122
+ elif pattern_values[i] < 0: # Bearish pattern
123
+ signals[i] = -1
124
+
125
+ # Filter by news if requested
126
+ if filter_news and self._news_dates:
127
+ for i in range(len(signals)):
128
+ date_str = self._data.index[i].strftime('%Y-%m-%d')
129
+ if date_str in self._news_dates:
130
+ signals[i] = 0
131
+
132
+ # Calculate trades
133
+ trades = self._calculate_trades(signals)
134
+
135
+ # Return None if no trades or too few signals
136
+ if not trades:
137
+ # Check if there were any signals at all
138
+ total_signals = int(np.sum(signals != 0))
139
+ if total_signals == 0:
140
+ print(f"{pattern_name}: No signals generated")
141
+ else:
142
+ print(f"{pattern_name}: {total_signals} signals but no completed trades")
143
+ return None
144
+
145
+ # Calculate statistics
146
+ winning_trades = [t for t in trades if t['pnl'] > 0]
147
+ losing_trades = [t for t in trades if t['pnl'] <= 0]
148
+
149
+ total_pnl = sum(t['pnl'] for t in trades)
150
+ avg_pnl = total_pnl / len(trades) if trades else 0
151
+ win_rate = len(winning_trades) / len(trades) * 100 if trades else 0
152
+
153
+ max_profit = max([t['pnl'] for t in trades]) if trades else 0
154
+ max_loss = min([t['pnl'] for t in trades]) if trades else 0
155
+
156
+ # Calculate Sharpe ratio
157
+ pnls = [t['pnl'] for t in trades]
158
+ sharpe = (np.mean(pnls) / np.std(pnls)) * np.sqrt(252) if len(pnls) > 1 and np.std(pnls) > 0 else 0
159
+
160
+ return PatternResult(
161
+ pattern_name=pattern_name.replace('CDL', ''),
162
+ total_signals=int(np.sum(signals != 0)),
163
+ winning_trades=len(winning_trades),
164
+ losing_trades=len(losing_trades),
165
+ win_rate=win_rate,
166
+ total_pnl=total_pnl,
167
+ avg_pnl=avg_pnl,
168
+ max_profit=max_profit,
169
+ max_loss=max_loss,
170
+ sharpe_ratio=sharpe
171
+ )
172
+
173
+ def _calculate_trades(self, signals: np.ndarray) -> List[Dict]:
174
+ """Calculate trades from signals."""
175
+ trades = []
176
+ position = 0
177
+ entry_idx = None
178
+
179
+ closes = self._data['Close'].values
180
+ times = self._data.index.to_numpy()
181
+
182
+ for i in range(len(signals)):
183
+ signal = signals[i]
184
+
185
+ # BUY signal
186
+ if signal == 1 and position == 0:
187
+ entry_idx = i
188
+ position = self._position_size / closes[i]
189
+
190
+ # SELL signal
191
+ elif signal == -1 and position > 0 and entry_idx is not None:
192
+ exit_price = closes[i]
193
+ entry_price = closes[entry_idx]
194
+ pnl = position * (exit_price - entry_price)
195
+
196
+ trades.append({
197
+ 'entry_time': times[entry_idx],
198
+ 'exit_time': times[i],
199
+ 'entry_price': entry_price,
200
+ 'exit_price': exit_price,
201
+ 'pnl': pnl
202
+ })
203
+
204
+ position = 0
205
+ entry_idx = None
206
+
207
+ return trades
208
+
209
+ def get_top_patterns(self, n: int = 10) -> List[PatternResult]:
210
+ """Get top N patterns by performance."""
211
+ return self._results[:n]
212
+
213
+ def get_comparison_report(self) -> pd.DataFrame:
214
+ """Get comparison report with/without news filter."""
215
+ # Test without news filter
216
+ results_no_filter = self.test_all_patterns(filter_news=False)
217
+
218
+ # Test with news filter
219
+ results_with_filter = self.test_all_patterns(filter_news=True)
220
+
221
+ # Create comparison DataFrame
222
+ data = []
223
+ for r_no, r_yes in zip(results_no_filter[:20], results_with_filter[:20]):
224
+ data.append({
225
+ 'Pattern': r_no.pattern_name,
226
+ 'Win Rate (No News Filter)': f"{r_no.win_rate:.1f}%",
227
+ 'Win Rate (With News Filter)': f"{r_yes.win_rate:.1f}%",
228
+ 'Total PnL (No Filter)': f"${r_no.total_pnl:.2f}",
229
+ 'Total PnL (With Filter)': f"${r_yes.total_pnl:.2f}",
230
+ 'Signals (No Filter)': r_no.total_signals,
231
+ 'Signals (With Filter)': r_yes.total_signals,
232
+ 'Sharpe (No Filter)': f"{r_no.sharpe_ratio:.2f}",
233
+ 'Sharpe (With Filter)': f"{r_yes.sharpe_ratio:.2f}"
234
+ })
235
+
236
+ return pd.DataFrame(data)
237
+
238
+ def export_results(self, filename: str = 'pattern_ranking.csv') -> None:
239
+ """Export results to CSV."""
240
+ data = []
241
+ for result in self._results:
242
+ data.append({
243
+ 'Rank': len(data) + 1,
244
+ 'Pattern': result.pattern_name,
245
+ 'Total Signals': result.total_signals,
246
+ 'Winning Trades': result.winning_trades,
247
+ 'Losing Trades': result.losing_trades,
248
+ 'Win Rate %': f"{result.win_rate:.2f}",
249
+ 'Total PnL': f"{result.total_pnl:.2f}",
250
+ 'Avg PnL': f"{result.avg_pnl:.2f}",
251
+ 'Max Profit': f"{result.max_profit:.2f}",
252
+ 'Max Loss': f"{result.max_loss:.2f}",
253
+ 'Sharpe Ratio': f"{result.sharpe_ratio:.2f}"
254
+ })
255
+
256
+ df = pd.DataFrame(data)
257
+ df.to_csv(filename, index=False)
258
+ print(f"Results exported to {filename}")
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: yfinance-ta-patterns
3
+ Version: 0.1.1
4
+ Summary: CLI and helpers to scan yfinance data for TA-Lib candlestick patterns.
5
+ Author: eminsk
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 eminsk
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/eminsk/yfinance-ta-patterns
29
+ Project-URL: Homepage, https://github.com/eminsk/yfinance-ta-patterns
30
+ Requires-Python: >=3.12
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: ta-lib>=0.6.8
34
+ Requires-Dist: yfinance>=0.2.66
35
+ Requires-Dist: pandas>=2.2.0
36
+ Requires-Dist: pytz>=2024.1
37
+ Requires-Dist: numpy>=1.26
38
+ Dynamic: license-file
39
+
40
+ ## Forex Candlestick Scanner
41
+
42
+ Python package + CLI that downloads Yahoo Finance data via `yfinance` and runs TA-Lib candlestick detectors for your symbol, timeframe, and date filters.
43
+
44
+ ### Quick start
45
+ ```bash
46
+ # create env
47
+ python -m venv .venv
48
+ .\.venv\Scripts\activate # PowerShell; adjust for your shell
49
+
50
+ # install the package (editable for local dev)
51
+ pip install -e .
52
+
53
+ # run CLI (two entrypoints)
54
+ yfinance-ta-patterns --pattern KICKING --symbol EURUSD --timeframe 5m --period 60d
55
+ # or
56
+ yftp --all-patterns --symbol EURUSD --timeframe 5m --period 60d --date 2025-04-01
57
+ ```
58
+ `pyproject.toml` targets Python `>=3.12`.
59
+
60
+ ### CLI usage
61
+ ```
62
+ yfinance-ta-patterns [--pattern NAME | --all-patterns]
63
+ [--symbol EURUSD] [--period 60d]
64
+ [--timeframe 15m] [--date YYYY-MM-DD]
65
+ [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD]
66
+ ```
67
+ - `--pattern`: Single candlestick name (with or without `CDL` prefix).
68
+ - `--all-patterns`: Scan every TA-Lib candlestick detector.
69
+ - `--symbol`: Ticker without suffix; `=X` is appended automatically for Forex (default `EURUSD`).
70
+ - `--period`: History window passed to `yfinance` (e.g., `60d`, `1mo`).
71
+ - `--timeframe`: Use `M1/M5/M15/M30/H1/D1` or raw `yfinance` intervals (`1m`, `5m`, `1h`, `1d`, etc.).
72
+ - `--date`: Filter signals for a single day.
73
+ - `--start-date` / `--end-date`: Inclusive range filter (cannot be combined with `--date`).
74
+
75
+ ### Examples
76
+ - All patterns for a single day:
77
+ ```bash
78
+ yftp --all-patterns --symbol EURUSD --timeframe 5m --period 60d --date 2025-04-01
79
+ ```
80
+ - All patterns across a range:
81
+ ```bash
82
+ yftp --all-patterns --symbol EURUSD --timeframe 5m --period 60d --start-date 2025-04-01 --end-date 2025-04-10
83
+ ```
84
+ - One pattern without date filter:
85
+ ```bash
86
+ yftp --pattern KICKING --symbol EURUSD --timeframe 5m --period 60d
87
+ ```
88
+
89
+ ### Data loader
90
+ `yfinance_ta_patterns/forex_data_loader.py` fetches and normalizes OHLC data. It appends `=X` to symbols when missing and converts timestamps to UTC before shifting to the configured timezone (`Europe/Moscow` by default).
91
+
92
+ ### Pattern analysis
93
+ `yfinance_ta_patterns/pattern_analyzer.py` wraps TA-Lib's `CDL*` functions, returning non-zero signals and applying optional date filters. When `--all-patterns` is used, it iterates over the full catalog and prints hits per pattern.
94
+
95
+ ### Pattern ranking helper (optional)
96
+ `yfinance_ta_patterns/pattern_tester.py` contains a backtesting-style ranking tool. It depends on `utils.pattern_helper.PatternHelper` to enumerate patterns; add that helper before running comparisons or exports.
97
+
98
+ ### Project layout
99
+ - `yfinance_ta_patterns/cli.py`: CLI entry point and argument parsing.
100
+ - `yfinance_ta_patterns/forex_data_loader.py`: Data download and timezone normalization.
101
+ - `yfinance_ta_patterns/pattern_analyzer.py`: Candlestick signal extraction.
102
+ - `yfinance_ta_patterns/pattern_tester.py`: Experimental ranking/backtest utilities.
103
+ - `main.py`: Thin wrapper to launch the CLI.
104
+
105
+ ### Packaging and releases
106
+ - Nightly GitHub Actions workflow builds onefile Nuitka binaries for Windows/macOS/Linux and publishes nightly prereleases.
107
+ - A PyPI publish workflow can be enabled by adding a secret `PYPI_API_TOKEN`; tags like `v0.1.0` will build sdist/wheel and upload.
108
+ - TA-Lib is installed from PyPI on Windows; Linux/macOS CI builds the TA-Lib C library from source for the binaries. For local installs, PyPI wheels (`ta-lib` >=0.6.8) cover CPython 3.9–3.14.
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ tests/test_cli.py
5
+ yfinance_ta_patterns/__init__.py
6
+ yfinance_ta_patterns/cli.py
7
+ yfinance_ta_patterns/forex_data_loader.py
8
+ yfinance_ta_patterns/pattern_analyzer.py
9
+ yfinance_ta_patterns/pattern_tester.py
10
+ yfinance_ta_patterns.egg-info/PKG-INFO
11
+ yfinance_ta_patterns.egg-info/SOURCES.txt
12
+ yfinance_ta_patterns.egg-info/dependency_links.txt
13
+ yfinance_ta_patterns.egg-info/entry_points.txt
14
+ yfinance_ta_patterns.egg-info/requires.txt
15
+ yfinance_ta_patterns.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ yfinance-ta-patterns = yfinance_ta_patterns.cli:main
3
+ yftp = yfinance_ta_patterns.cli:main
@@ -0,0 +1,5 @@
1
+ ta-lib>=0.6.8
2
+ yfinance>=0.2.66
3
+ pandas>=2.2.0
4
+ pytz>=2024.1
5
+ numpy>=1.26
@@ -0,0 +1 @@
1
+ yfinance_ta_patterns