41 |
42 | Setup
43 |
44 |
45 |
46 | ```bash
47 | cd
48 | pip install -r requirements.txt
49 | pip install -e . # or python setup.py install
50 | ```
51 |
52 |
53 |
54 |
55 |
56 | ### Features
57 |
58 | #### Visualize
59 |
60 | + Draw Harmonic Patterns in the graph using mplfinance + ipympl
61 |
62 |
63 | #### Predict
64 |
65 | + Predict harmonic patterns according to current kline
66 |
67 | #### Else:
68 |
69 | + go to examples/*.ipynb
70 | + [example](examples/HarmoCurrent.ipynb)
71 |
--------------------------------------------------------------------------------
/examples/HarmoCurrent.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "### init"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 13,
13 | "metadata": {
14 | "ExecuteTime": {
15 | "end_time": "2023-01-07T23:12:25.659999Z",
16 | "start_time": "2023-01-07T23:12:25.545687Z"
17 | },
18 | "init_cell": true
19 | },
20 | "outputs": [
21 | {
22 | "name": "stdout",
23 | "output_type": "stream",
24 | "text": [
25 | "The autoreload extension is already loaded. To reload it, use:\n",
26 | " %reload_ext autoreload\n"
27 | ]
28 | }
29 | ],
30 | "source": [
31 | "%load_ext autoreload\n",
32 | "import matplotlib\n",
33 | "%reload_ext autoreload\n",
34 | "%autoreload 2\n",
35 | "\n",
36 | "#%matplotlib notebook\n",
37 | "#%matplotlib widget\n",
38 | "\n",
39 | "import warnings\n",
40 | "warnings.simplefilter(\"ignore\")"
41 | ]
42 | },
43 | {
44 | "cell_type": "code",
45 | "execution_count": 14,
46 | "metadata": {
47 | "ExecuteTime": {
48 | "end_time": "2023-01-07T23:12:25.748099Z",
49 | "start_time": "2023-01-07T23:12:25.663793Z"
50 | },
51 | "init_cell": true
52 | },
53 | "outputs": [],
54 | "source": [
55 | "import logging\n",
56 | "import signal, threading, os, time\n",
57 | "import logging\n",
58 | "\n",
59 | "from IPython.core.debugger import set_trace\n",
60 | "from IPython.terminal.embed import embed\n",
61 | "\n",
62 | "import os, sys\n",
63 | "\n",
64 | "\n",
65 | "import ccxt\n",
66 | "import pandas as pd\n",
67 | "\n",
68 | "logger = logging.getLogger('notebook')\n",
69 | "logger.setLevel(logging.INFO)"
70 | ]
71 | },
72 | {
73 | "cell_type": "code",
74 | "execution_count": 15,
75 | "metadata": {
76 | "ExecuteTime": {
77 | "end_time": "2023-01-07T23:12:25.842974Z",
78 | "start_time": "2023-01-07T23:12:25.751353Z"
79 | },
80 | "init_cell": true
81 | },
82 | "outputs": [],
83 | "source": [
84 | "import mplfinance as mpf\n",
85 | "#matplotlib.use('Agg')\n",
86 | "import matplotlib.pyplot as plt\n",
87 | "plt.rcParams['figure.figsize'] = [8, 12]\n"
88 | ]
89 | },
90 | {
91 | "cell_type": "code",
92 | "execution_count": 16,
93 | "metadata": {
94 | "ExecuteTime": {
95 | "end_time": "2023-01-07T23:12:25.939296Z",
96 | "start_time": "2023-01-07T23:12:25.846489Z"
97 | },
98 | "code_folding": [
99 | 9
100 | ],
101 | "init_cell": true
102 | },
103 | "outputs": [],
104 | "source": [
105 | "\n",
106 | "try:\n",
107 | " from settings import HTTP_PROXY\n",
108 | "except ImportError:\n",
109 | " HTTP_PROXY = None\n",
110 | " \n",
111 | "# (!!!)You should change this according to your network environment\n",
112 | "if HTTP_PROXY is None:\n",
113 | " HTTP_PROXY = 'http://127.0.0.1:1087' \n",
114 | "\n",
115 | "def kline_to_df(arr) -> pd.DataFrame:\n",
116 | " kline = pd.DataFrame(\n",
117 | " arr,\n",
118 | " columns=['ts', 'open', 'high', 'low', 'close', 'volume' ])\n",
119 | " kline.index = pd.to_datetime(kline.ts, unit='ms')\n",
120 | " kline.drop('ts', axis=1, inplace=True)\n",
121 | " return kline\n",
122 | " "
123 | ]
124 | },
125 | {
126 | "cell_type": "code",
127 | "execution_count": 17,
128 | "metadata": {
129 | "ExecuteTime": {
130 | "end_time": "2023-01-07T23:12:26.022225Z",
131 | "start_time": "2023-01-07T23:12:25.942116Z"
132 | },
133 | "code_folding": [],
134 | "init_cell": true,
135 | "scrolled": false
136 | },
137 | "outputs": [],
138 | "source": [
139 | "\n",
140 | "PROXIES = {\n",
141 | " 'http': HTTP_PROXY,\n",
142 | " 'https': HTTP_PROXY,\n",
143 | "}\n",
144 | "\n",
145 | "ccxt_options = {'proxies': PROXIES}\n",
146 | "\n",
147 | "ok = 'okex'\n",
148 | "bn = 'binance'\n",
149 | "\n",
150 | "client_list = [bn, ok]"
151 | ]
152 | },
153 | {
154 | "cell_type": "markdown",
155 | "metadata": {},
156 | "source": [
157 | "## Step by Step"
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": 19,
163 | "metadata": {
164 | "ExecuteTime": {
165 | "end_time": "2023-01-07T23:12:35.936358Z",
166 | "start_time": "2023-01-07T23:12:35.160500Z"
167 | }
168 | },
169 | "outputs": [],
170 | "source": [
171 | "# prepare client\n",
172 | "binance = getattr(ccxt, 'binance')(ccxt_options)\n",
173 | "markerts = binance.load_markets()\n",
174 | "symbol = 'BTC/USDT'\n",
175 | "period = '4h'"
176 | ]
177 | },
178 | {
179 | "cell_type": "code",
180 | "execution_count": 20,
181 | "metadata": {
182 | "ExecuteTime": {
183 | "end_time": "2023-01-07T23:12:36.660188Z",
184 | "start_time": "2023-01-07T23:12:36.278189Z"
185 | }
186 | },
187 | "outputs": [],
188 | "source": [
189 | "# fetch raw kline data via ccxt unified api\n",
190 | "kline_data = binance.fetch_ohlcv(symbol, period, limit=1000)\n",
191 | "\n",
192 | "kline = kline_to_df(kline_data)"
193 | ]
194 | },
195 | {
196 | "cell_type": "code",
197 | "execution_count": 21,
198 | "metadata": {
199 | "ExecuteTime": {
200 | "end_time": "2023-01-07T23:12:37.083300Z",
201 | "start_time": "2023-01-07T23:12:37.002885Z"
202 | }
203 | },
204 | "outputs": [
205 | {
206 | "name": "stdout",
207 | "output_type": "stream",
208 | "text": [
209 | "raw ccxt kline data:\n",
210 | " [[1658736000000, 21962.33, 22110.98, 21868.36, 21941.25, 22048.18138], [1658750400000, 21942.18, 22003.12, 21683.4, 21895.79, 32159.10297], [1658764800000, 21893.94, 22021.47, 21552.53, 21890.17, 27200.4675], [1658779200000, 21892.66, 22259.98, 21250.0, 21310.9, 43229.18467]]\n"
211 | ]
212 | }
213 | ],
214 | "source": [
215 | "print('raw ccxt kline data:\\n', kline_data[:4])"
216 | ]
217 | },
218 | {
219 | "cell_type": "code",
220 | "execution_count": 22,
221 | "metadata": {
222 | "ExecuteTime": {
223 | "end_time": "2023-01-07T23:12:37.503897Z",
224 | "start_time": "2023-01-07T23:12:37.432331Z"
225 | },
226 | "scrolled": true
227 | },
228 | "outputs": [
229 | {
230 | "name": "stdout",
231 | "output_type": "stream",
232 | "text": [
233 | "convert to dataframe:\n",
234 | " open high low close volume\n",
235 | "ts \n",
236 | "2022-07-25 08:00:00 21962.33 22110.98 21868.36 21941.25 22048.18138\n",
237 | "2022-07-25 12:00:00 21942.18 22003.12 21683.40 21895.79 32159.10297\n",
238 | "2022-07-25 16:00:00 21893.94 22021.47 21552.53 21890.17 27200.46750\n",
239 | "2022-07-25 20:00:00 21892.66 22259.98 21250.00 21310.90 43229.18467\n"
240 | ]
241 | }
242 | ],
243 | "source": [
244 | "print('convert to dataframe:\\n', kline[:4])"
245 | ]
246 | },
247 | {
248 | "cell_type": "code",
249 | "execution_count": 23,
250 | "metadata": {
251 | "ExecuteTime": {
252 | "end_time": "2023-01-07T23:12:39.898580Z",
253 | "start_time": "2023-01-07T23:12:37.843840Z"
254 | }
255 | },
256 | "outputs": [],
257 | "source": [
258 | "patterns, predict_patterns = detector.search_patterns(kline, only_last=False, last_n=4, plot=False, predict=True)"
259 | ]
260 | },
261 | {
262 | "cell_type": "code",
263 | "execution_count": 24,
264 | "metadata": {
265 | "ExecuteTime": {
266 | "end_time": "2023-01-07T23:12:40.288715Z",
267 | "start_time": "2023-01-07T23:12:40.216987Z"
268 | }
269 | },
270 | "outputs": [
271 | {
272 | "name": "stdout",
273 | "output_type": "stream",
274 | "text": [
275 | "BTC/USDT 4h \n",
276 | "patterns found: [Timestamp('2022-08-17 04:00:00'), Timestamp('2022-08-20 20:00:00'), Timestamp('2022-08-21 20:00:00'), Timestamp('2022-08-23 04:00:00'), Timestamp('2022-08-24 16:00:00')], [['H', 24446.71, 137], ['L', 20761.9, 159], ['H', 21800.0, 165], ['L', 20890.14, 173], ['H', 21900.0, 182]], \n",
277 | " bearish abcd, {'AB': 0.8764666217127463, 'BC': 1.1099070186622118, 'AB=CD': 0.9727964550621349}\n",
278 | "BTC/USDT 4h \n",
279 | "patterns found: [Timestamp('2022-08-15 00:00:00'), Timestamp('2022-08-20 20:00:00'), Timestamp('2022-08-21 20:00:00'), Timestamp('2022-08-23 04:00:00'), Timestamp('2022-08-24 16:00:00')], [['H', 25211.32, 124], ['L', 20761.9, 159], ['H', 21800.0, 165], ['L', 20890.14, 173], ['H', 21900.0, 182]], \n",
280 | " bearish abcd, {'AB': 0.8764666217127463, 'BC': 1.1099070186622118, 'AB=CD': 0.9727964550621349}\n",
281 | "BTC/USDT 4h \n",
282 | "patterns found: [Timestamp('2022-10-04 20:00:00'), Timestamp('2022-10-13 12:00:00'), Timestamp('2022-10-14 00:00:00'), Timestamp('2022-10-21 12:00:00'), Timestamp('2022-10-29 08:00:00')], [['H', 20475.0, 429], ['L', 18190.0, 481], ['H', 19951.87, 484], ['L', 18650.0, 529], ['H', 21085.0, 576]], \n",
283 | " bearish butterfly, {'XAB': 0.7710590809628004, 'XAD': 1.2669584245076586, 'ABC': 0.7389137677581205, 'BCD': 1.8703864441150053, 'AB=CD': 1.3820542945847316}\n"
284 | ]
285 | }
286 | ],
287 | "source": [
288 | "for pat in patterns:\n",
289 | " msg = f'{symbol} {period} \\npatterns found: {pat[1]}, {pat[0]}, \\n {pat[2]}, {pat[3]}'\n",
290 | " print(msg)\n",
291 | "\n",
292 | "\n",
293 | "for pat in predict_patterns:\n",
294 | " msg = '\\n'.join([f'{p} {v}' for p,v in list(zip([str(dt) for dt in pat[1]], [p for p in pat[0]]))])\n",
295 | " msg = f'{symbol} {period} {msg} {pat[2]} {pat[3]}'\n",
296 | " print(msg)"
297 | ]
298 | },
299 | {
300 | "cell_type": "markdown",
301 | "metadata": {
302 | "heading_collapsed": true
303 | },
304 | "source": [
305 | "### Parameters"
306 | ]
307 | },
308 | {
309 | "cell_type": "code",
310 | "execution_count": 18,
311 | "metadata": {
312 | "ExecuteTime": {
313 | "end_time": "2023-01-07T23:12:26.120221Z",
314 | "start_time": "2023-01-07T23:12:26.025441Z"
315 | },
316 | "code_folding": [],
317 | "hidden": true,
318 | "init_cell": true,
319 | "run_control": {
320 | "marked": true
321 | }
322 | },
323 | "outputs": [],
324 | "source": [
325 | "from harmonic_functions import HarmonicDetector\n",
326 | "#from HarmonicPatterns.harmonic import send_alert, search_function\n",
327 | "from functools import partial\n",
328 | "import time\n",
329 | "from multiprocessing import Pool\n",
330 | "\n",
331 | "PERIODS = ['1h', '4h', '1d']\n",
332 | "\n",
333 | "################\n",
334 | "# HarmonicDetector params\n",
335 | "# err_allowed: The error rate that detector would allow, usually 0.05 < err_rate < 0.1\n",
336 | "# strict: for example 0.618 * (1 - err_rate) < pattern < 0.618 * (1 + err_rate), the base should be a fibbonacci number, like 0.382, 0.618, 0.786, 1.618....\n",
337 | "# predict_err_rate: # similiar to err_allowed but used in predict\n",
338 | "\n",
339 | "\n",
340 | "################\n",
341 | "# search params\n",
342 | "# only_last: find patterns in history or not\n",
343 | "# symbols: symbols list to find\n",
344 | "# periods: periods that used in ccxt\n",
345 | "# last_n: if only_last is set, how near should the bar be\n",
346 | "# alert: send alerts when patterns found\n",
347 | "# plot: plot patterns when patterns found\n",
348 | "def search_function(detector, exchange_id, symbols, periods=PERIODS, ccxt_args={}, savefig=False, predict=True, only_last=False, alert=False, plot=True):\n",
349 | " client = getattr(ccxt, exchange_id)(ccxt_args)\n",
350 | " client.load_markets()\n",
351 | " RETRY_TIMES=3\n",
352 | " for symbol in symbols:\n",
353 | " for period in periods:\n",
354 | " print(f'------------------calculating {symbol} {period}------------------')\n",
355 | " retry = RETRY_TIMES\n",
356 | " while retry>0:\n",
357 | " try:\n",
358 | " df = kline_to_df(client.fetch_ohlcv(symbol, period, limit=1000))\n",
359 | "\n",
360 | " patterns, predict_patterns = detector.search_patterns(df, only_last=only_last, last_n=4, plot=plot, predict=predict)\n",
361 | " break\n",
362 | " except Exception as e:\n",
363 | " print(e)\n",
364 | " retry -= 1\n",
365 | " if retry==0: raise\n",
366 | " continue\n",
367 | " for pat in patterns:\n",
368 | " msg = f'{symbol} {period} \\npatterns found: {pat[1]}, {pat[0]}, \\n {pat[2]}, {pat[3]}'\n",
369 | " print(msg)\n",
370 | " if alert and pat[0][-1][2] == len(df)-1:\n",
371 | " send_alert(f'Pattern_Found_{symbol}_{period}', msg)\n",
372 | "\n",
373 | " for pat in predict_patterns:\n",
374 | " msg = '\\n'.join([f'{p} {v}' for p,v in list(zip([str(dt) for dt in pat[1]], [p for p in pat[0]]))])\n",
375 | " msg = f'{symbol} {period} {msg} {pat[2]} {pat[3]}'\n",
376 | " print(msg)\n",
377 | " if alert:\n",
378 | " send_alert(f'Pattern_Predict_{symbol}_{period}', msg)\n",
379 | "\n",
380 | "\n",
381 | "detector = HarmonicDetector(error_allowed=0.07, strict=True, predict_err_rate=0.07)\n",
382 | "search = partial(search_function, detector, ccxt_args=ccxt_options, only_last=False, plot=True)\n",
383 | "\n",
384 | "#PERIODS = ['5m','15m', '30m']\n"
385 | ]
386 | },
387 | {
388 | "cell_type": "markdown",
389 | "metadata": {},
390 | "source": [
391 | "### DETECT BTC"
392 | ]
393 | },
394 | {
395 | "cell_type": "code",
396 | "execution_count": null,
397 | "metadata": {
398 | "ExecuteTime": {
399 | "end_time": "2023-01-07T22:59:52.333500Z",
400 | "start_time": "2023-01-07T22:59:42.268411Z"
401 | },
402 | "scrolled": true
403 | },
404 | "outputs": [],
405 | "source": [
406 | "s = ['BTC/USDT', 'ETH/USDT']\n",
407 | "search('binance', s, periods = ['1h', '4h'])"
408 | ]
409 | },
410 | {
411 | "cell_type": "markdown",
412 | "metadata": {},
413 | "source": [
414 | "### BTC USDT"
415 | ]
416 | },
417 | {
418 | "cell_type": "code",
419 | "execution_count": null,
420 | "metadata": {
421 | "ExecuteTime": {
422 | "end_time": "2023-01-07T22:47:19.358619Z",
423 | "start_time": "2023-01-07T22:47:17.448137Z"
424 | },
425 | "scrolled": true
426 | },
427 | "outputs": [],
428 | "source": [
429 | "s = ['BTC/USDT']\n",
430 | "\n",
431 | "search(ok, s, periods=['1h', '4h'], predict=True, only_last=False, alert=False, plot=True)\n"
432 | ]
433 | }
434 | ],
435 | "metadata": {
436 | "kernelspec": {
437 | "display_name": "Python 3",
438 | "language": "python",
439 | "name": "python3"
440 | },
441 | "language_info": {
442 | "codemirror_mode": {
443 | "name": "ipython",
444 | "version": 3
445 | },
446 | "file_extension": ".py",
447 | "mimetype": "text/x-python",
448 | "name": "python",
449 | "nbconvert_exporter": "python",
450 | "pygments_lexer": "ipython3",
451 | "version": "3.7.4"
452 | },
453 | "varInspector": {
454 | "cols": {
455 | "lenName": 16,
456 | "lenType": 16,
457 | "lenVar": 40
458 | },
459 | "kernels_config": {
460 | "python": {
461 | "delete_cmd_postfix": "",
462 | "delete_cmd_prefix": "del ",
463 | "library": "var_list.py",
464 | "varRefreshCmd": "print(var_dic_list())"
465 | },
466 | "r": {
467 | "delete_cmd_postfix": ") ",
468 | "delete_cmd_prefix": "rm(",
469 | "library": "var_list.r",
470 | "varRefreshCmd": "cat(var_dic_list()) "
471 | }
472 | },
473 | "types_to_exclude": [
474 | "module",
475 | "function",
476 | "builtin_function_or_method",
477 | "instance",
478 | "_Feature"
479 | ],
480 | "window_display": false
481 | }
482 | },
483 | "nbformat": 4,
484 | "nbformat_minor": 4
485 | }
486 |
--------------------------------------------------------------------------------
/examples/run_detect.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import warnings
3 | warnings.simplefilter("ignore")
4 |
5 | from datetime import datetime
6 |
7 | import signal, threading, os, time
8 | import logging
9 |
10 | from IPython.core.debugger import set_trace
11 | from IPython.terminal.embed import embed
12 |
13 | import time
14 | from multiprocessing import Pool, TimeoutError
15 | from functools import partial
16 |
17 | import os, sys
18 | import asyncio
19 |
20 | import nest_asyncio
21 | nest_asyncio.apply()
22 |
23 | import inspect
24 | #import ccxt.async_support as ccxt
25 | import ccxt
26 |
27 | import pandas as pd
28 |
29 | # from djow_core.base.logger import get_logger
30 | from logging import Logger
31 |
32 | HTTP_PROXY = None
33 |
34 | import httpx
35 |
36 | logger = Logger('Harmonic')
37 |
38 | from .harmonic_functions import HarmonicDetector
39 |
40 |
41 | from .settings import NOTIFY_URL
42 | from .settings import MAIN_SYMBOLS, ALT_SYMBOLS, PERIODS, ERROR_RATE
43 | from .settings import PROCESS_COUNT
44 |
45 |
46 | import redis
47 | redis_client = redis.Redis()
48 |
49 |
50 | import sys
51 | def get_frame_fname(level=0):
52 | return sys._getframe(level+1).f_code.co_name
53 |
54 | def send_alert(title: str, body: str):
55 | """
56 | 用Redis缓存一下,主要是为了不重复发送
57 | """
58 | body = body.replace('\n', '\n\n')
59 | template='''
60 | \n\n
61 | {body}
62 | \n\n
63 | '''
64 | if not redis_client.exists(body):
65 | r = httpx.post(NOTIFY_URL, data={'text': title, 'desp': template.format(body=body)})
66 | redis_client.setex(body, 60 * 60 * 30, 1)
67 | return r.status_code
68 | else:
69 | return None
70 |
71 |
72 | def kline_to_df(arr) -> pd.DataFrame:
73 | kline = pd.DataFrame(
74 | arr,
75 | columns=['ts', 'open', 'high', 'low', 'close', 'volume' ])
76 | kline.index = pd.to_datetime(kline.ts, unit='ms')
77 | kline.drop('ts', axis=1, inplace=True)
78 | return kline
79 |
80 |
81 | def search_function(detector, exchange_id, symbols, periods=PERIODS, ccxt_args={}, savefig=False, predict=True, only_last=False, alert=False, plot=False):
82 | client = getattr(ccxt, exchange_id)(ccxt_args)
83 | client.load_markets()
84 | RETRY_TIMES=3
85 | for symbol in symbols:
86 | for period in periods:
87 | logger.info(f'------------------calculating {symbol} {period}------------------')
88 | retry = RETRY_TIMES
89 | while retry>0:
90 | try:
91 | df = kline_to_df(client.fetch_ohlcv(symbol, period, limit=1000))
92 |
93 | patterns, predict_patterns = detector.search_patterns(df, only_last=only_last, last_n=4, plot=plot, predict=predict)
94 | break
95 | except Exception as e:
96 | logger.error(e)
97 | retry -= 1
98 | if retry==0: raise
99 | continue
100 | for pat in patterns:
101 | msg = f'{symbol} {period} \npatterns found: {pat[1]}, {pat[0]}, \n {pat[2]}, {pat[3]}'
102 | logger.info(msg)
103 | if alert and pat[0][-1][2] == len(df)-1:
104 | send_alert(f'Pattern_Found_{symbol}_{period}', msg)
105 |
106 | for pat in predict_patterns:
107 | msg = '\n'.join([f'{p} {v}' for p,v in list(zip([str(dt) for dt in pat[1]], [p for p in pat[0]]))])
108 | msg = f'{symbol} {period} {msg} {pat[2]} {pat[3]}'
109 | logger.info(msg)
110 | if alert:
111 | send_alert(f'Pattern_Predict_{symbol}_{period}', msg)
112 |
113 |
114 | def main():
115 | #signal.signal(signal.SIGINT, partial(debug_handler, engine))
116 |
117 | PROXIES = {
118 | 'http': HTTP_PROXY,
119 | 'https': HTTP_PROXY,
120 | }
121 |
122 | ccxt_options = {'proxies': PROXIES}
123 |
124 | ok = 'okex'
125 | bn = 'binance'
126 | hb = 'huobipro'
127 |
128 | notify_msgs = []
129 | while True:
130 | epoch_start_time = datetime.now()
131 | predict_results = []
132 | #call_repl(engine)
133 |
134 | detector = HarmonicDetector(error_allowed=ERROR_RATE, strict=True)
135 | client = hb
136 |
137 | symbols = [*MAIN_SYMBOLS, *ALT_SYMBOLS]
138 |
139 | search = partial(search_function, detector, ccxt_args=ccxt_options)
140 |
141 | try:
142 | with Pool(PROCESS_COUNT) as p:
143 | # 检测主流币和山寨是否出现谐波模式
144 | r = p.map_async(partial(search, client, periods=PERIODS, predict=PREDICT, only_last=True, alert=True, plot=False), [[si] for si in symbols])
145 | # 检测平台币
146 | r1 = p.map_async(partial(search, hb, periods=PERIODS, predict=PREDICT, only_last=True, alert=True, plot=False), [['HT/USDT']])
147 | r2 = p.map_async(partial(search, ok, periods=PERIODS, predict=PREDICT, only_last=True, alert=True, plot=False), [['OKB/USDT']])
148 | r3 = p.map_async(partial(search, bn, periods=PERIODS, predict=PREDICT, only_last=True, alert=True, plot=False), [['BNB/USDT']])
149 | r.get(timeout=360)
150 | r1.get(timeout=120)
151 | r2.get(timeout=120)
152 | r3.get(timeout=120)
153 | except TimeoutError as e:
154 | logger.error(e)
155 | continue
156 | except Exception as e:
157 | logger.error(e)
158 | continue
159 | finally:
160 | pass
161 |
162 |
163 | epoch_end_time = datetime.now()
164 | run_time = (epoch_end_time - epoch_start_time).total_seconds()
165 | print(f'------------|Total seconds: {run_time}s|---------------')
166 | if len(predict_results)>0:
167 | send_alert('Patterns predict', '\n\n'.join(predict_results))
168 | #time.sleep(10)
169 |
170 |
171 | if __name__ == '__main__':
172 | # 从异步改成同步,用multiprocessing来达到并发效果
173 | #loop = asyncio.get_event_loop()
174 | #loop.run_until_complete(main())
175 | main()
176 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 |
2 | numpy>=1.14.2
3 | #scipy>=1.3.1
4 | pandas>=0.22.0
5 | ipython
6 |
7 | ccxt
8 | jupyter
9 |
10 | matplotlib
11 |
12 | #nest-asyncio
13 | mplfinance
14 |
15 | ipympl
16 |
17 | TA-Lib
18 |
19 | fire
20 |
--------------------------------------------------------------------------------
/res/plot_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djoffrey/HarmonicPatterns/9ebdcab2cb598dc8636957e92a27cef93e1aa4ca/res/plot_0.png
--------------------------------------------------------------------------------
/res/predict_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djoffrey/HarmonicPatterns/9ebdcab2cb598dc8636957e92a27cef93e1aa4ca/res/predict_0.png
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os, glob
2 |
3 | from setuptools import setup, find_packages
4 |
5 | HERE = os.path.dirname(os.path.abspath(__file__))
6 |
7 |
8 | # with open('requirements.txt') as f:
9 | # requirements = f.read().splitlines()
10 | # requirements = [r for r in requirements if not r.startswith('-e')]
11 |
12 |
13 | # packages = ['djow'] + glob.glob('djow_*')
14 |
15 | setup(
16 | name='HarmonicPatterns',
17 | version='0.0.1',
18 | # install_requires=requirements,
19 | # packages=packages,
20 | url='https://github.com/djoffrey/HarmonicPatterns',
21 | author='djoffrey',
22 | author_email='joffrey.oh@gmail.com',
23 | description='A Python Library for Harmonic Trading'
24 | )
25 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djoffrey/HarmonicPatterns/9ebdcab2cb598dc8636957e92a27cef93e1aa4ca/src/__init__.py
--------------------------------------------------------------------------------
/src/harmonic_functions.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Author Djoffrey
4 | some harmonic pattern scanner functions
5 | (TODO: calculate using (high+low)/2 or etc, not just close price)
6 | (TODO: should return each peak distance)
7 | """
8 |
9 | import pandas as pd
10 | import numpy as np
11 | import talib.abstract as ta
12 | # from scipy.signal import argrelextrema
13 | import mplfinance as mpf
14 |
15 | MAIN_FIBB_RATIOS = [0.618, 1.618]
16 | SECOND_FIBB_RATIOS = [0.786, 0.886, 1.13, 1.27]
17 | ALT_FIBB_RATIOS = [0.382, 0.5, 0.707, 1.41, 2.0, 2.24, 2.618, 3.14, 3.618]
18 | AB_CD = [1.27, 1.618, 2.236]
19 |
20 | FIBB_RATIOS = [*MAIN_FIBB_RATIOS, *SECOND_FIBB_RATIOS, *ALT_FIBB_RATIOS]
21 |
22 | from IPython.core.debugger import set_trace
23 | import os
24 |
25 |
26 | def kline_to_df(arr) -> pd.DataFrame:
27 | kline = pd.DataFrame(
28 | arr,
29 | columns=['ts', 'open', 'high', 'low', 'close', 'volume' ])
30 | kline.index = pd.to_datetime(kline.ts, unit='ms')
31 | kline.drop('ts', axis=1, inplace=True)
32 | return kline
33 |
34 |
35 | class HarmonicDetector(object):
36 | def __init__(self, error_allowed:float=0.05, strict:bool=True, predict_err_rate:float=None):
37 | self.err = error_allowed
38 | self.predict_err_rate = self.err if predict_err_rate is None else predict_err_rate
39 |
40 | self.strict = strict
41 |
42 | def is_eq(self, n: float, m: float, err:float=None, l_closed:bool=False, r_closed:bool=True) -> bool:
43 | _err = self.err if err is None else err
44 | left = m if l_closed else m * (1 - _err)
45 | right = m if r_closed else m * (1 + _err)
46 | return (n >= left) and (n <= right)
47 |
48 | def is_in(self, n: float, l: float, r: float, err:float=None, l_closed:bool=True, r_closed:bool=True) -> bool:
49 | _err = self.err if err is None else err
50 | left = l if l_closed else l * (1 - _err)
51 | right = r if r_closed else r * (1 + _err)
52 | if self.strict:
53 | fibb_rates = [
54 | self.is_eq(n, f_rate) for f_rate in FIBB_RATIOS
55 | if (f_rate >= left) and (f_rate <= right)
56 | ]
57 | return np.any(fibb_rates)
58 | else:
59 | return (n >= left) and (n <= right)
60 |
61 | def get_zigzag(self, df: pd.DataFrame, period: int):
62 |
63 | # translated from https://www.tradingview.com/script/mRbjBGdL-Double-Zig-Zag-with-HHLL/
64 |
65 | zigzag_pattern = []
66 | direction = 0
67 | changed = False
68 | for idx in range(1, len(df)):
69 | highest_high = ta.MAX(df.high[:idx], timeperiod=period)[-1]
70 | lowest_low = ta.MIN(df.low[:idx], timeperiod=period)[-1]
71 |
72 |
73 | new_high = df.high[idx] >= highest_high
74 | new_low = df.low[idx] <= lowest_low
75 |
76 | if new_high and not new_low:
77 | if direction != 1:
78 | direction = 1
79 | changed = True
80 | elif direction == 1:
81 | changed = False
82 | elif not new_high and new_low:
83 | if direction != -1:
84 | direction = -1
85 | changed = True
86 | elif direction == -1:
87 | changed = False
88 |
89 | if new_high or new_low:
90 | if changed or len(zigzag_pattern)==0:
91 | if direction == 1:
92 | pat = ['H', df.high[idx], idx]
93 | zigzag_pattern.append(pat)
94 | elif direction == -1:
95 | pat = ['L', df.low[idx], idx]
96 | zigzag_pattern.append(pat)
97 | else:
98 | if direction == 1 and df.high[idx] > zigzag_pattern[-1][1]:
99 | pat = ['H', df.high[idx], idx]
100 | zigzag_pattern[-1] = pat
101 | elif direction == -1 and df.low[idx] < zigzag_pattern[-1][1]:
102 | pat = ['L', df.low[idx], idx]
103 | zigzag_pattern[-1] = pat
104 | else:
105 | pass
106 | return zigzag_pattern
107 |
108 | def detect_abcd(self, current_pat: list, predict:bool=False, predict_mode:str='direct'):
109 | """
110 | AB=CD is a common pattern in harmonic trading, it is many pattern's inner structure
111 | AB=CD has two main alternatives 1.27AB=CD and 1.618AB=CD
112 | """
113 | if not predict:
114 | X, A, B, C, D = [pat[1] for pat in current_pat]
115 | last_direction = current_pat[-1][0]
116 | XAB = abs(B-A) / abs(X-A)
117 | XAD = abs(A-D) / abs(X-A)
118 | ABC = abs(B-C) / abs(A-B)
119 | BCD = abs(C-D) / abs(B-C)
120 |
121 | ABCD = BCD / (1/ABC)
122 |
123 | ret_dict = {
124 | 'AB': ABC,
125 | 'BC': BCD,
126 | 'AB=CD': ABCD
127 | }
128 |
129 | # Detect
130 | pattern_found = np.all(np.array([
131 | self.is_in(ABC, 0.382, 0.886), # AB
132 | self.is_in(BCD, 1.13, 1.168), # CD
133 | self.is_eq(ABCD, 1) # strictly 1/AB = CD
134 | ]))
135 | direction = 1 if D