Spaces:
Running
Running
back to stage
Browse files- confirm_sell_backtest.py +114 -602
confirm_sell_backtest.py
CHANGED
|
@@ -4,7 +4,8 @@
|
|
| 4 |
|
| 5 |
๊ท์น:
|
| 6 |
- ์ด๋ฒคํธ = ์ข
๋ชฉ ์๊ธฐ๊ณ ์ ๊ธฐ์ค ์ด๊ฒฉ์ด -10% ์์์ -10% ์ดํ๋ก '์๋ก ์ง์
'ํ ๊ตฌ๊ฐ 1๊ฐ (๊น์ด ๋ฌด๊ด)
|
| 7 |
-
- 1์ฐจ ํ ๋ค์ ์ด๋ฒคํธ -> 2์ฐจ,3์ฐจ... ์ฐ์
|
|
|
|
| 8 |
- 2์ฐจ๊ฐ ์ฑ๋ฆฝํ๋ฉด ๊ทธ ๋ '์ ๋๋งค๋' (๊น์ด ๋น๊ต ์์)
|
| 9 |
- ์ ๊ณ ๊ฐ ๊ฐฑ์ ์ ๋ฆฌ์
|
| 10 |
|
|
@@ -18,9 +19,7 @@ import sys, ssl
|
|
| 18 |
import pandas as pd
|
| 19 |
import numpy as np
|
| 20 |
import FinanceDataReader as fdr
|
| 21 |
-
from datetime import datetime, timedelta
|
| 22 |
-
|
| 23 |
-
_KST = timezone(timedelta(hours=9))
|
| 24 |
|
| 25 |
ssl._create_default_https_context = ssl._create_unverified_context
|
| 26 |
if sys.platform == 'win32':
|
|
@@ -31,142 +30,58 @@ if sys.platform == 'win32':
|
|
| 31 |
# =============================================
|
| 32 |
TICKERS = {
|
| 33 |
'KOSPI': [
|
| 34 |
-
# '005930', '000660', '373220', '207940',
|
| 35 |
-
# '005380',
|
| 36 |
# '000270', '068270', '035420', '035720', '005490',
|
| 37 |
-
# '105560', '055550', '012330',
|
| 38 |
-
# '051910',
|
| 39 |
-
# '012450',
|
| 40 |
-
# '006400',
|
| 41 |
# '028260', '066570', '096770', '034730', '015760',
|
| 42 |
-
# '178920',
|
| 43 |
# '204320',
|
| 44 |
-
# '
|
| 45 |
],
|
| 46 |
'KOSDAQ': [
|
| 47 |
# '247540', '086520', '196170', '028300', '348370',
|
| 48 |
-
# '058470', '068760', '263750', '035900',
|
|
|
|
|
|
|
| 49 |
# '041510', '035760', '112040', '293490', '145020',
|
| 50 |
# '039030', '240810', '357780',
|
| 51 |
# '403870',
|
| 52 |
# '121600',
|
| 53 |
# '399720',
|
| 54 |
-
# '
|
| 55 |
-
# '257720',
|
| 56 |
-
# '017510',
|
| 57 |
-
# '389020',
|
| 58 |
-
# '195940',
|
| 59 |
-
'251370',
|
| 60 |
],
|
| 61 |
}
|
| 62 |
|
| 63 |
# =============================================
|
| 64 |
# ์ค์
|
| 65 |
# =============================================
|
| 66 |
-
DIV_THRESHOLD = -0.10 # ์ด๊ฒฉ ๊ธฐ์ค
|
| 67 |
-
MIN_DEEPEN = 0.
|
| 68 |
-
DAILY_JUMP = 0.10 # ํ๋ฃจ๋ง์ ์ด๊ฒฉ์ด ์ ์ผ
|
| 69 |
-
|
| 70 |
-
# --- ๋จ๊ณ ์ ์์ก์ด: flag ON ํ '๋ช ๋ฒ ๋ ๊น์ด์ ธ์ผ' ์ ๋๋งค๋๊ฐ ๋ฐ๋ํ๋ ---
|
| 71 |
-
# 3 = ๊ธฐ์กด(flagโ1์ฐจโ2์ฐจflagโ2์ฐจ๋งค๋, ๊น์ด์ง 3๋ฒ). ์ค์ด๋ฉด ๋ ์ผ์ฐ ์ธ๋ฆฌ์ง๋ง ๋ ์์ฃผ ์ธ๋ฆผ.
|
| 72 |
-
# 2 = flagโ1์ฐจโ๋งค๋ (๊น์ด์ง 2๋ฒ) / 1 = flagโ๋งค๋ (๊น์ด์ง 1๋ฒ, ๊ฐ์ฅ ์ผ์ฐ/๊ฐ์ฅ ์์ฃผ)
|
| 73 |
-
CONFIRM_STAGES = 0
|
| 74 |
-
|
| 75 |
-
# =============================================
|
| 76 |
-
# ๋งค๋ ์์ง ์ ํ
|
| 77 |
-
# 'STAGE' = ๊ธฐ์กด: ์ด๊ฒฉ flagโ1์ฐจโ2์ฐจflagโ2์ฐจ (๋จ๊ณ๋์ ). ํ์๋ ์ ์ง๋ง ํํ(๋๋ฆผ).
|
| 78 |
-
# 'RS_A4' = ์๊ฑฐ: RS์ ์ด ์์ฅ๋ณด๋ค ์ฝํด์ง๋ฉด(๋ฐ๋ํฌ๋ก์ค) '๋ฌด์ฅ' -> A-4(์ถ์ธ์ฃฝ์) ํ์ธ๋๋
|
| 79 |
-
# ์ฆ์ ๋งค๋. ๋จ๊ณ ์ ๊ฑฐ์นจ -> ๋ ์ผ์ฐ. RS๋ ์ ํ์งํ๋ผ 240 ๊นจ์ง๊ธฐ ์ ์ ๊ฒฝ๊ณ .
|
| 80 |
-
# =============================================
|
| 81 |
-
ENGINE = 'RS_A4' # 'STAGE' / 'RS_A4'
|
| 82 |
-
RS_MA_PERIOD = 20 # RS์ (์ข
๋ชฉ/์ง์)์ ์ด๋ํ๊ท ๊ธฐ๊ฐ. RS < ์ด ํ๊ท ์ด๋ฉด ๋ฐ๋ํฌ๋ก์ค(์ฝํ)
|
| 83 |
-
RS_SMOOTH = 5 # RS์ ์์ฒด๋ฅผ ์ด ์ผ์ EMA๋ก ๋ถ๋๋ฝ๊ฒ(ํฑ๋ ์ ๊ฑฐ). 1์ด๋ฉด smoothing ์ํจ.
|
| 84 |
-
RS_BUFFER = 0.02 # ๋ฐ๋ํฌ๋ก์ค ๋ฒํผ: RS๊ฐ MA๋ณด๋ค '์ด๋งํผ(2%) ํ์คํ ์๋'์ฌ์ผ ์ฝํ ์ธ์
|
| 85 |
-
# (RSยทMA ๋ฑ ๋ถ์ด์ 0.0001 ์ฐจ์ด๋ก ํกํก ๊ต์ฐจํ๋ ์ฆ์ ์๋ ๋ฐฉ์ง)
|
| 86 |
-
RS_PERSIST = 3 # RS๊ฐ ์ด๋งํผ(๊ฑฐ๋์ผ) ์ฐ์ ์ฝํด์ผ '๋ฌด์ฅ' (ํ๋ฃจ ๋
ธ์ด์ฆ ๋ฐฉ์ง)
|
| 87 |
-
RS_CONFIRM = 'MA60' # ๋ฌด์ฅ ํ '๋งค๋ ํ์ธ' ์ ํธ:
|
| 88 |
-
# 'A4' = ์ดํ์ ํํ(๋๋ฆผ/์์ , ์ฒ์ฒํ ์ฃฝ๋ ์ถ์ธ์ฉ)
|
| 89 |
-
# 'MA60' = ์ข
๊ฐ๊ฐ 60์ ๊นจ๋ฉด(๋น ๋ฆ! ๊ธ๋ฝ์ ๊ฐํจ) โ ๋ฆ๊ฒ ์ฐ๋ ๊ฑฐ ํด๊ฒฐ
|
| 90 |
-
# 'MA120' = ์ข
๊ฐ๊ฐ 120์ ๊นจ๋ฉด(์ค๊ฐ)
|
| 91 |
-
# 'NONE' = RS ๋ฐ๋ํฌ๋ก์ค๋ง์ผ๋ก(์ ์ผ ๋น ๋ฆ/์ ์ผ ์๋๋ฌ์)
|
| 92 |
-
CONFIRM_NEEDS_FLAT = 'MARGIN' # MA60/MA120 ํ์ธ์ ์ถ๊ฐ ๊ฒ์ดํธ (์ค๋ฅด๋ ์ค์ ๋งค๋ ๊ธ์ง):
|
| 93 |
-
# 'OFF' = ์ข
๊ฐ๋ง (๋น ๋ฅด์ง๋ง ์ค๋ฅผ ๋๋ ์ฐํ)
|
| 94 |
-
# 'SELF' = ํ์ธ์ ๊ธฐ์ธ๊ธฐ ์ฐ์ํฅ์ด๋ฉด ๊ธ์ง (๊ธฐ์ธ๊ธฐ ํํโ๋๋ฆผ)
|
| 95 |
-
# 'MARGIN' = ์ข
๊ฐ๊ฐ ํ์ธ์ ๋ณด๋ค CONFIRM_MARGIN ์๋์ฌ์ผ (๋น ๋ฆ+๋๋ฆผ๋ฌด์)
|
| 96 |
-
# 'MARGIN+240' = MARGIN + 240์ ๊ฐํ๋ฅธ์์น(>FLAT_MAX_240)์ด๋ฉด ๊ธ์ง โ ์ถ์ฒ
|
| 97 |
-
# '240' = 240์ ์ด ํํ/๊บพ์์ด์ด์ผ (๋ ์๊ฒฉ)
|
| 98 |
-
# (RS_CONFIRM='A4'/'NONE'์ผ ๋ ์ด ์ค์ ๋ฌด์)
|
| 99 |
-
CONFIRM_MARGIN = 0.035 # 'MARGIN' ๋ชจ๋์์: ์ข
๊ฐ๊ฐ ํ์ธ์ ๋ณด๋ค ์ด๋งํผ(3%) ์๋์ฌ์ผ '๋ถ๊ดด'๋ก ์ธ์ .
|
| 100 |
-
# ์ ๊น ๋๋ฆผ(์ ์ด์ง ์ค์นจ)์ ๋ฌด์, ์ง์ง ๊ธ๋ฝ๋ง. ๊ธฐ์ธ๊ธฐ ์ ๊ธฐ๋ค๋ ค์ ๋น ๋ฆ.
|
| 101 |
-
SELL_ONCE = True # ์ฒซ ๋งค๋ ํ์ '์ง์ง ์ถ์ธ๋ณต๊ท'๊น์ง ์ฌ๋ฌด์ฅ ๊ธ์ง.
|
| 102 |
-
# ๋จ์ด์ง๋ ์ข
๋ชฉ์ด -30/-40/-50/-80%์ ๊ณ์ ๋ ์ฐ๋ ํฑ๋ ์ ๊ฑฐ.
|
| 103 |
-
RELOCK_LINE = 240 # ์ ๊ธํด์ ๊ธฐ์ค์ : 240=ํ์ค(ํญ๋ฝ์ฃผ ๋ด๋ด ์ ๊น) / 120=๋์จ(๋นจ๋ฆฌ ํ๋ฆผ)
|
| 104 |
-
# ํญ๋ฝ ์ค์ 120์ ์ด ์ฃผ๊ฐ์ ๋ฐ์ง ๋ถ์ด ์ ๊น ๋ฐ๋ฑ์๋ ํ๋ ค๋ฒ๋ฆผ -> 240 ๊ถ์ฅ
|
| 105 |
-
RELOCK_MIN_DAYS = 60 # + ์ฒซ ๋งค๋ ํ ์ต์ ์ด ์ผ์๋ ๋ฌด์กฐ๊ฑด ์ ๊ธ(๋ ๋ค ์ถฉ์กฑํด์ผ ํด์ )
|
| 106 |
-
NO_ARM_BELOW_240 = -0.10 # ์ข
๊ฐ๊ฐ 240์ ๋ณด๋ค ์ด๋งํผ(-10%) ๋ ์๋๋ฉด '๋ฌด์ฅ ๊ธ์ง'.
|
| 107 |
-
# ์ด๋ฏธ ์ถ์ธ ๋ฌด๋์ง(240 ํ์ฐธ ์๋) ์ข
๋ชฉ์ ์ตํ๋ณด๋ฃจ ๋์ ์๋.
|
| 108 |
-
# None ์ด๋ฉด ์ด ํํฐ ๋(240 ์๋์ฌ๋ ๋ฌด์ฅ ํ์ฉ).
|
| 109 |
-
NO_ARM_DIV_BELOW = -0.30 # ์ด๊ฒฉ(๊ณ ์ ๋๋น ๋ํญ)์ด ์ด๋ฏธ ์ด๋งํผ(-25%) ๋๊ฒ ๊น์ผ๋ฉด '๋ฌด์ฅ ๊ธ์ง'.
|
| 110 |
-
# 240์ ์ ํญ๋ฝ์ฃผ ๋ฐ๋ผ ๋ฐ๋ฅ๊น์ง ๋ด๋ ค์์ ๋ชป ๊ฑฐ๋ฆ -> '๊ณ ์ ๋๋น'๋ก ๋ง์.
|
| 111 |
-
# ์ด๋ฏธ -50/-80% ๋น ์ง ์์ฒด์ 'ํ์๋ผ' ์ ํจ. None=ํํฐ ๋.
|
| 112 |
PEAK_WINDOW = 252 # ๊ณ ์ ๊ธฐ์ค = ์ต๊ทผ N๊ฑฐ๋์ผ(252=1๋
) ๋ด ์ต๊ณ ์ (์ค๋๋ ๊ณ ์ ์ ์ ๋ฌถ์)
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
# =============================================
|
| 116 |
-
# ์ถ์ธ ํํฐ: A-4(๊ธฐ์ธ๊ธฐ ํํ/ํ๋ฝ)๋ง ์ฌ์ฉ
|
| 117 |
-
# [๊ณตํต ์ ์ AND] 60์ ์ด 240์ ๋ํ ํ RUN_MIN_DAYS ๊ฒฝ๊ณผ
|
| 118 |
-
# [๊ทธ ์ A-4] 60/120/240์ ๊ธฐ์ธ๊ธฐ๊ฐ ๊ฐ ๋ฐด๋ ์์ด๋ฉด ๋งค๋ ํ์ฉ. 240์ ์์ผ๋ฉด ๋งค๋ ๋.
|
| 119 |
-
# =============================================
|
| 120 |
-
USE_TREND = True # ์ถ์ธํํฐ ์ ์ฒด on/off (๊บผ์ง๋ฉด ํํฐ ์์)
|
| 121 |
-
|
| 122 |
-
# ๊ณตํต ์ ์ : 60์ ์ด 240์ ๋ํ ํ ์ด๋งํผ(๊ฑฐ๋์ผ) ์ง๋์ผ ๋งค๋ ํ์ฉ
|
| 123 |
-
RUN_MIN_DAYS = 40 # 60์ ์ด 240์ ๋ํ ํ ๊ฒฝ๊ณผ์ผ (๋ง ๋ํ ์งํ=240์ ๋๋ฌด ๊ทผ์ฒ๋ผ ์๋ฏธ์์)
|
| 124 |
-
|
| 125 |
-
# 240์ ์ถ์ : 240์ผ์น ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด(์์ฅ์ด๊ธฐ ๋ฑ) ์๋ ๋งํผ ์ต์ฅ ํ๊ท ์ผ๋ก ๋์ฒด
|
| 126 |
-
ESTIMATE_MA240 = True # True๋ฉด 240์ผ ๋ฏธ๋ง์ด์ด๋ ์ถ์ ํด์ ํ์ (๋งค๋ ๋ผ์ธ์ [240์ถ์ ] ํ์)
|
| 127 |
-
MA240_MIN = 120 # ์ต์ ์ด๋งํผ(๊ฑฐ๋์ผ) ์์ด์ผ ์ถ์ . ์ด ๋ฏธ๋ง์ด๋ฉด ์ถ์ ์ํจ(๋งค๋ ๋)
|
| 128 |
-
|
| 129 |
-
# A-4) ๊ธฐ์ธ๊ธฐ๊ฐ '๋ฎ์ผ๋ฉด'(ํํ/ํ๋ฝ) ๋งค๋ ํ์ฉ โ 60/120/240์ ๊ฐ๊ฐ ๋ฐด๋(ํํ<๊ธฐ์ธ๊ธฐโค์ํ)
|
| 130 |
-
# ์์น ๋๋ ฅ์ด ์ฃฝ์๋ค๊ณ ๋ณด๊ณ ๋์ง. AND/OR๋ก ๊ฒฐํฉ.
|
| 131 |
-
# ํํ์ด -1 ์ดํ(์ -9)๋ฉด 'ํํ ์์'. 240์ ํํ 0% (๋ง์ด๋์ค๋๋ฉด ์ด๋ฏธ ๋ฆ์).
|
| 132 |
-
USE_TREND_FLAT = True # A-4 ์ฌ์ฉ?
|
| 133 |
-
FLAT_SLOPE_DAYS = 10 # ๊ธฐ์ธ๊ธฐ ์ธก์ ๊ฐ๊ฒฉ(๊ฑฐ๋์ผ)
|
| 134 |
-
FLAT_SMOOTH = 5 # ๊ธฐ์ธ๊ธฐ ์ค๋ฌด๋ฉ ์ผ์
|
| 135 |
-
FLAT_MIN_60 = -9.0 # 60์ ๊ธฐ์ธ๊ธฐ ํํ (-9=ํํ์์)
|
| 136 |
-
FLAT_MAX_60 = 0.00 # 60์ ๊ธฐ์ธ๊ธฐ ์ํ (โค0%=๊บพ์ฌ ๋ด๋ ค๊ฐ๊ฑฐ๋ ์์ ํํ)
|
| 137 |
-
FLAT_MIN_120 = -9.0 # 120์ ๊ธฐ์ธ๊ธฐ ํํ (-9=ํํ์์)
|
| 138 |
-
FLAT_MAX_120 = 0.05 # 120์ ๊ธฐ์ธ๊ธฐ ์ํ
|
| 139 |
-
FLAT_MIN_240 = 0.00 # 240์ ๊ธฐ์ธ๊ธฐ ํํ (0%=๋ง์ด๋์ค๋ฉด ๋๋ฌด ๋ฆ์ -> ์ ์ธ)
|
| 140 |
-
FLAT_MAX_240 = 0.04 # 240์ ๊ธฐ์ธ๊ธฐ ์ํ (์ด๋ณด๋ค ๊ฐํ๋ฅด๊ฒ ์ค๋ฅด๋ฉด=์์น์ถ์ธ ํ๋ณตํ ๋๋ฆผ -> ๋งค๋X)
|
| 141 |
-
FLAT_COMBINE = 'AND' # 'AND'(์ธ ์ ๋ค ๋ง์กฑ) / 'OR'(ํ๋๋ผ๋ ๋ง์กฑ)
|
| 142 |
-
|
| 143 |
-
SHOW_STEPS = True # True๋ฉด ๋งค๋๋ง๋ค [๋จ๊ณ ์์ธ](flag/1์ฐจ/2์ฐจ ์ธ์ ์ด๋ป๊ฒ) ์ถ๋ ฅ
|
| 144 |
-
|
| 145 |
LOOK_AHEAD = 200 # ๋งค๋ ํ ๊ด์ฐฐ๊ธฐ๊ฐ(๊ฑฐ๋์ผ)
|
| 146 |
PROTECT_DROP = -0.10 # ๋งค๋๊ฐ ๋๋น ์ด๋งํผ ๋ ๋น ์ง๋ฉด '์ ํ ๊ฒ'
|
| 147 |
|
| 148 |
-
TREND_MIN_DAYS = 60 # '240์ฌ'
|
| 149 |
-
TREND_MIN_GAP = 0.05 # '240์ฌ': ์ข
๊ฐ๊ฐ 240์ +5% ์ด์
|
| 150 |
|
| 151 |
-
#
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
ANALYZE_FROM = '2020-01-01' # ์ฑ์ (๋งค๋์ง๊ณ) ์์์ผ โ ์ด๊ฑด '์ธ์ ๋ถํฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณผ์ง'์ผ ๋ฟ
|
| 156 |
-
ANALYZE_TO = None # None=์ค๋
|
| 157 |
|
| 158 |
# โ
๋ฐ์ดํฐ ๋ก๋ฉ์ ANALYZE_FROM ๊ณผ ๋ฌด๊ดํ๊ฒ 'ํญ์ ์ถฉ๋ถํ ๊ณผ๊ฑฐ๋ถํฐ' ๋ถ๋ฌ์จ๋ค.
|
| 159 |
# (๊ณ ์ /์ด๊ฒฉ์ ์ ์ฒด ๊ฐ๊ฒฉ์ด๋ ฅ์ผ๋ก ๊ณ์ฐ๋ผ์ผ ํ๋ฏ๋ก. ANALYZE_FROM ๋ง ๋ฆ์ถ๋ฉด ๊ณ ์ ์
|
| 160 |
# ์๋ชป ์ก์ ๋งค๋์์ ์ด ๋ฌ๋ผ์ง๋ ๋ฒ๊ทธ๊ฐ ์๊น -> ๊ทธ๋์ DATA_START ๋ ๊ณ ์ .)
|
| 161 |
DATA_START = '2018-01-01' # ๊ณ ์ . ๋ ๊ณผ๊ฑฐ๋ฅผ ๋ณด๋ ค๋ฉด ์ด ๋ ์ง๋ง ์๋น๊ธฐ๋ฉด ๋จ.
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
"""๋ฐ์ดํฐ ๋๋ ์ง. ANALYZE_TO ๊ฐ None ์ด๋ฉด '์ง๊ธ KST ์ค๋'์ ๋งค๋ฒ ์๋ก ๊ณ์ฐ
|
| 166 |
-
(์ฑ์ด ๋ฉฐ์น ์งธ ์ผ์ ธ ์์ด๋ ํญ์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๋ก)."""
|
| 167 |
-
if ANALYZE_TO:
|
| 168 |
-
return ANALYZE_TO
|
| 169 |
-
return datetime.now(_KST).strftime('%Y-%m-%d')
|
| 170 |
|
| 171 |
|
| 172 |
# =============================================
|
|
@@ -188,113 +103,21 @@ def _trailing_peaks(stock, index, win):
|
|
| 188 |
return pk, ipk
|
| 189 |
|
| 190 |
|
| 191 |
-
def
|
| 192 |
-
"""
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
if ma60[k - 1] <= ma240[k - 1]: # ์ง์ ๋ 60<=240 -> ์ฌ๊ธฐ๊ฐ ๋ํ ์งํ
|
| 201 |
-
break
|
| 202 |
-
k -= 1
|
| 203 |
-
return t - k
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
def _smoothed_slope(line, days, smooth):
|
| 207 |
-
"""์ ์ days๊ฐ๊ฒฉ ๊ธฐ์ธ๊ธฐ((line[t]/line[t-days])-1)๋ฅผ ๊ตฌํ๊ณ smooth์ผ ์ด๋ํ๊ท ."""
|
| 208 |
-
n = len(line)
|
| 209 |
-
raw = np.full(n, np.nan)
|
| 210 |
-
for t in range(days, n):
|
| 211 |
-
a = line[t]; b = line[t - days]
|
| 212 |
-
if not np.isnan(a) and not np.isnan(b) and b > 0:
|
| 213 |
-
raw[t] = a / b - 1
|
| 214 |
-
if smooth <= 1:
|
| 215 |
-
return raw
|
| 216 |
-
out = np.full(n, np.nan)
|
| 217 |
-
for t in range(n):
|
| 218 |
-
lo = max(0, t - smooth + 1)
|
| 219 |
-
seg = raw[lo:t + 1]
|
| 220 |
-
seg = seg[~np.isnan(seg)]
|
| 221 |
-
if len(seg) > 0:
|
| 222 |
-
out[t] = seg.mean()
|
| 223 |
-
return out
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
def trend_ok(t, stock, ma240, ma60=None, ma120=None, flat_slopes=None):
|
| 227 |
-
"""์ถ์ธ ํํฐ(USE_TREND).
|
| 228 |
-
[๊ณตํต ์ ์ AND] 60์ ์ด 240์ ๋ํ ํ RUN_MIN_DAYS ๊ฒฝ๊ณผ
|
| 229 |
-
[๊ทธ ์ A-4] 60/120/240์ ๊ธฐ์ธ๊ธฐ๊ฐ ๊ฐ ๋ฐด๋ ์์ด๋ฉด ๋งค๋ ํ์ฉ.
|
| 230 |
-
240์ ์์ผ๋ฉด ๋งค๋ ๋. ์ฌ์ ์ ์ค์ ๊ฐ/๊ธฐ์ธ๊ธฐ ํฌํจ(๋๋ฒ๊น
)."""
|
| 231 |
-
if not USE_TREND:
|
| 232 |
-
return True, ''
|
| 233 |
-
m = ma240[t]
|
| 234 |
-
m60v = ma60[t] if (ma60 is not None) else float('nan')
|
| 235 |
-
vals = f'[ma60={m60v:,.0f} ma240={m:,.0f} ์ข
๊ฐ={stock[t]:,.0f}]' if not np.isnan(m) else ''
|
| 236 |
-
if np.isnan(m) or m <= 0:
|
| 237 |
-
return False, '240์์'
|
| 238 |
-
since = _days_since_cross(t, ma60, ma240)
|
| 239 |
-
if since < 0:
|
| 240 |
-
return False, f'60<240(์ฐ์ํฅ์๋){vals}'
|
| 241 |
-
if since < RUN_MIN_DAYS:
|
| 242 |
-
return False, f'๋ํํ{since}์ผ(<{RUN_MIN_DAYS}){vals}'
|
| 243 |
-
a4 = False; a4msg = ''
|
| 244 |
-
if USE_TREND_FLAT and flat_slopes is not None:
|
| 245 |
-
checks = [] # (์ , ๊ธฐ์ธ๊ธฐ๊ฐ, ํํ, ์ํ, ํต๊ณผ?)
|
| 246 |
-
for ln, lo, hi in ((60, FLAT_MIN_60, FLAT_MAX_60),
|
| 247 |
-
(120, FLAT_MIN_120, FLAT_MAX_120),
|
| 248 |
-
(240, FLAT_MIN_240, FLAT_MAX_240)):
|
| 249 |
-
arr = flat_slopes.get(ln)
|
| 250 |
-
if arr is not None and not np.isnan(arr[t]):
|
| 251 |
-
v = float(arr[t])
|
| 252 |
-
checks.append((ln, v, lo, hi, (lo < v <= hi)))
|
| 253 |
-
if checks:
|
| 254 |
-
a4 = all(c[4] for c in checks) if FLAT_COMBINE == 'AND' else any(c[4] for c in checks)
|
| 255 |
-
def _fm(ln, v, lo, hi, ok):
|
| 256 |
-
mk = 'O' if ok else 'X'
|
| 257 |
-
if lo <= -1:
|
| 258 |
-
return f'{ln}:{v*100:.1f}โค{hi*100:.1f}%{mk}'
|
| 259 |
-
return f'{ln}:{v*100:.1f}โ({lo*100:.0f},{hi*100:.1f}]{mk}'
|
| 260 |
-
a4msg = ' '.join(_fm(*c) for c in checks)
|
| 261 |
-
if a4:
|
| 262 |
-
return True, f'A4(ํํยท{a4msg})'
|
| 263 |
-
ex = (' ' + a4msg) if a4msg else ''
|
| 264 |
-
return False, f'์ถ์ธ๋ฏธ๋ฌ{vals}{ex}(๋ํํ{since}์ผ)'
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
def find_confirmed_sells(stock, index, dates, start_pos, ma60=None, ma240=None,
|
| 268 |
-
low=None, bbl=None, ma240_est=None, ma120=None):
|
| 269 |
-
"""ํ์ ๋งค๋ ์ฐพ๊ธฐ โ ๋จ๊ณ๋์ (CONFIRM_STAGES๋จ๊ณ)+๊ธ๋ณ์ ํ + ์ถ์ธํํฐ(A-4)."""
|
| 270 |
n = len(stock)
|
| 271 |
twpk, twip = _trailing_peaks(stock, index, PEAK_WINDOW)
|
| 272 |
-
|
| 273 |
-
flat_slopes = None
|
| 274 |
-
if USE_TREND_FLAT:
|
| 275 |
-
flat_slopes = {}
|
| 276 |
-
for ln, arr in ((60, ma60), (120, ma120), (240, ma240)):
|
| 277 |
-
if arr is not None:
|
| 278 |
-
flat_slopes[ln] = _smoothed_slope(arr, FLAT_SLOPE_DAYS, FLAT_SMOOTH)
|
| 279 |
-
|
| 280 |
-
SELL_STAGE = max(1, int(CONFIRM_STAGES)) + 1 # ์ด ๋จ๊ณ์ ๋๋ฌํ๋ฉด ์ ๋๋งค๋
|
| 281 |
-
|
| 282 |
-
def _sname(stg):
|
| 283 |
-
"""๋จ๊ณ ์ด๋ฆ. ๊ธฐ๋ณธ(CONFIRM_STAGES=3)์ด๋ฉด ์น์ํ 1์ฐจ/2์ฐจ ์ด๋ฆ, ์๋๋ฉด N๋จ๊ณ."""
|
| 284 |
-
if CONFIRM_STAGES == 3:
|
| 285 |
-
return {1: 'flag ON', 2: '1์ฐจ', 3: '2์ฐจ flag ON'}.get(stg, f'{stg}๋จ๊ณ')
|
| 286 |
-
return 'flag ON' if stg == 1 else f'{stg}๋จ๊ณ'
|
| 287 |
-
|
| 288 |
-
def _gate(t):
|
| 289 |
-
ok, why = (True, '')
|
| 290 |
-
if ma240 is not None:
|
| 291 |
-
ok, why = trend_ok(t, stock, ma240, ma60, ma120, flat_slopes)
|
| 292 |
-
return ok, why
|
| 293 |
-
|
| 294 |
stage = 0; ref_div = None; first_i = None; sold = False
|
| 295 |
prev_div = None
|
| 296 |
sells = []
|
| 297 |
-
jumps = []
|
| 298 |
_SNAME = {0: '์์', 1: 'flagON', 2: '1์ฐจ', 3: '2์ฐจflagON', 4: '2์ฐจ๋งค๋'}
|
| 299 |
|
| 300 |
def _log_jump(t, frm, to, div, pdv, st):
|
|
@@ -304,325 +127,60 @@ def find_confirmed_sells(stock, index, dates, start_pos, ma60=None, ma240=None,
|
|
| 304 |
'from': _SNAME[frm], 'to': _SNAME[to],
|
| 305 |
'skip': (frm == 0 and to == 2) or (frm == 2 and to == 4)})
|
| 306 |
|
| 307 |
-
def _sell(t, st, div, via=''):
|
| 308 |
-
if t < start_pos:
|
| 309 |
-
return False, '๊ด์ฐฐ๊ธฐ๊ฐ๋ฐ'
|
| 310 |
-
ok, why = _gate(t)
|
| 311 |
-
if ok:
|
| 312 |
-
est = bool(ma240_est[t]) if ma240_est is not None else False
|
| 313 |
-
sells.append({'i': t, 'price': st, 'div': div, 'date': dates[t],
|
| 314 |
-
'trend': why, 'via': via, 'est': est})
|
| 315 |
-
return True, why
|
| 316 |
-
return False, why # ์ถ์ธํํฐ์ ๋งํ (why=์ฐจ๋จ์ฌ์ )
|
| 317 |
-
|
| 318 |
-
steps = [] # ๋จ๊ณ ์ ์ด ์์ธ ๋ก๊ทธ
|
| 319 |
-
def _step(t, name, div, note=''):
|
| 320 |
-
if t >= start_pos:
|
| 321 |
-
steps.append({'date': dates[t], 'name': name, 'div': div, 'note': note})
|
| 322 |
-
|
| 323 |
for t in range(n):
|
| 324 |
st = float(stock[t]); it = float(index[t])
|
| 325 |
peak = float(twpk[t]); iap = float(twip[t])
|
| 326 |
-
|
| 327 |
-
i_term = ((it / iap - 1) if iap else 0.0) # ์ง์: ๊ณ ์ ์ผ ๋๋น
|
| 328 |
-
div = s_term - i_term
|
| 329 |
-
_bd = f'[์ข
๋ชฉ {s_term*100:+.0f}% โ ์ง์ {i_term*100:+.0f}%]' # ์ด๊ฒฉ ๋ถํด(๋๋ฒ๊น
)
|
| 330 |
|
|
|
|
| 331 |
if div > DIV_THRESHOLD:
|
| 332 |
-
if stage != 0 and t >= start_pos:
|
| 333 |
-
_step(t, f'๋ฆฌ์
(์ด๊ฒฉ {DIV_THRESHOLD*100:.0f}%์ ํ๋ณต)', div)
|
| 334 |
stage = 0; ref_div = None; first_i = None; sold = False
|
| 335 |
prev_div = div
|
| 336 |
continue
|
| 337 |
-
|
|
|
|
| 338 |
prev_div = div
|
| 339 |
continue
|
| 340 |
|
|
|
|
| 341 |
crash = (prev_div is not None) and ((div - prev_div) <= -DAILY_JUMP)
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
def _try_sell(note, via):
|
| 345 |
-
"""๋งค๋ ์๋. ๋ฐ๋ํ๋ฉด True(+๋ฐ๋๋ก๊ทธ), ์ฐจ๋จ๋๋ฉด False(+์ฐจ๋จ๋ก๊ทธ). ๋๊ธฐ ์ํจ."""
|
| 346 |
-
fired, why = _sell(t, st, div, via)
|
| 347 |
-
if fired:
|
| 348 |
-
_step(t, '์ ๋๋งค๋ โ
๋ฐ๋', div, note + ' ' + _bd)
|
| 349 |
-
return True
|
| 350 |
-
_step(t, f'๋งค๋์กฐ๊ฑด ์ถฉ์กฑํ์ผ๋ ์ฐจ๋จ({why}) โ ์ด๋ฒ ์ ํธ ๋ฌด์', div, note)
|
| 351 |
-
return False
|
| 352 |
|
| 353 |
-
|
| 354 |
-
# ๋ฌด์ฅ(stage>=1) ํ ๊น์ด์ง ๋๋ง๋ค stage+1 (๊ธ๋ณ์ด๋ฉด +2๋ก ํ ๋จ๊ณ ๊ฑด๋๋).
|
| 355 |
-
# stage ๊ฐ SELL_STAGE ์ ๋๋ฌํ๋ฉด ์ ๋๋งค๋.
|
| 356 |
-
if stage == 0:
|
| 357 |
if crash:
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
ref_div = div; first_i = t
|
| 361 |
-
if nx >= SELL_STAGE:
|
| 362 |
-
if _try_sell(cnote + ' (๋จ๊ณ ๊ฑด๋๋)', '๊ธ๋ณ์ ํ'):
|
| 363 |
-
sold = True
|
| 364 |
-
else:
|
| 365 |
-
stage = SELL_STAGE - 1 # ์ฐจ๋จ -> ๋งค๋์ง์ ๋จ๊ณ๋ก park
|
| 366 |
-
else:
|
| 367 |
-
stage = nx
|
| 368 |
-
_step(t, f'{_sname(nx)}(flag ๊ฑด๋๋)', div, cnote)
|
| 369 |
else:
|
| 370 |
-
stage = 1; ref_div = div
|
| 371 |
-
|
| 372 |
-
if _try_sell('flag์ฆ์', 'flag'):
|
| 373 |
-
sold = True
|
| 374 |
-
else:
|
| 375 |
-
stage = SELL_STAGE - 1
|
| 376 |
-
else:
|
| 377 |
-
_step(t, _sname(1), div, f'๊ธฐ์ค {DIV_THRESHOLD*100:.0f}%')
|
| 378 |
-
else:
|
| 379 |
-
adv = 0
|
| 380 |
if crash:
|
| 381 |
-
|
|
|
|
| 382 |
elif div <= ref_div - MIN_DEEPEN:
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
if crash:
|
| 389 |
-
_log_jump(t, stage, SELL_STAGE, div, prev_div, st)
|
| 390 |
-
if _try_sell(note, '๊ธ๋ณ์ ํ' if crash else '๋จ๊ณ๋์ '):
|
| 391 |
-
sold = True
|
| 392 |
-
else:
|
| 393 |
-
stage = SELL_STAGE - 1 # ์ฐจ๋จ -> ๋งค๋์ง์ ๋จ๊ณ park, ๋ ๊น์ด์ง๋ฉด ์ฌ์๋
|
| 394 |
-
else:
|
| 395 |
-
stage = nx
|
| 396 |
-
_step(t, _sname(nx), div, note)
|
| 397 |
-
prev_div = div
|
| 398 |
-
|
| 399 |
-
# ๊ด์ฐฐ ์ข
๋ฃ ์์ , ์์ง ๋งค๋ ์ ๋ฌ๊ณ ๋จ๊ณ ๋๊ธฐ ์ค์ด๋ฉด ํ์ฌ ์ํ ๊ธฐ๋ก
|
| 400 |
-
if not sold and 1 <= stage < SELL_STAGE and ref_div is not None:
|
| 401 |
-
last = n - 1
|
| 402 |
-
if CONFIRM_STAGES == 3:
|
| 403 |
-
nm = {1: '1์ฐจ flag', 2: '1์ฐจ (๋ค์: 2์ฐจ flag)',
|
| 404 |
-
3: '2์ฐจ flag (์ ๋๋งค๋ ์๋ฐ!)'}.get(stage, f'{stage}๋จ๊ณ')
|
| 405 |
-
else:
|
| 406 |
-
tail = ' (๋ค์: ์ ๋๋งค๋!)' if stage == SELL_STAGE - 1 else ''
|
| 407 |
-
nm = f'{stage}๋จ๊ณ / ๋งค๋๊น์ง {SELL_STAGE}๋จ๊ณ{tail}'
|
| 408 |
-
cur = float(stock[last] / twpk[last] - 1) - ((float(index[last] / twip[last] - 1)) if twip[last] else 0.0)
|
| 409 |
-
trigger = ref_div - MIN_DEEPEN # ๋ค์ ๋จ๊ณ ๋ฐ๋ ์ด๊ฒฉ
|
| 410 |
-
need = cur - trigger # >0 ์ด๋ฉด ์ด๋งํผ ๋ ๊น์ด์ ธ์ผ ๋ค์ ๋จ๊ณ
|
| 411 |
-
# ํ์ฌ ์ด๊ฒฉ์ด '๋ฌด์ฅ๋ ์ต์ ์ (ref_div)'๋ณด๋ค ๋๋ ท์ด ์๋ก ํ๋ณต๋์ผ๋ฉด = ๋ฐ๋ฑ ์ค (์๋ฐ ์๋)
|
| 412 |
-
if cur > ref_div + MIN_DEEPEN:
|
| 413 |
-
nm = nm.replace(' (์ ๋๋งค๋ ์๋ฐ!)', '').replace(' (๋ค์: ์ ๋๋งค๋!)', '')
|
| 414 |
-
note = (f'๋ฐ๋ฑ ์ค โ ํ์ฌ ์ด๊ฒฉ {cur*100:.1f}%, ์ต์ {ref_div*100:.1f}%๊น์ง ๊ฐ๋ค ํ๋ณต. '
|
| 415 |
-
f'๋งค๋ํธ๋ฆฌ๊ฑฐ {trigger*100:.1f}% (๋ฆฌ์
์ ์ด๊ฒฉ {DIV_THRESHOLD*100:.0f}% ํ๋ณต ์)')
|
| 416 |
-
_step(last, f'[ํ์ฌ] {nm} ยท ๋ฐ๋ฑ ์ค', cur, note)
|
| 417 |
-
elif need > 0:
|
| 418 |
-
_step(last, f'[ํ์ฌ] {nm}', cur, f'๋ค์ ๋จ๊ณ๊น์ง {need*100:.1f}%p ๋ ํ๋ฝ ํ์')
|
| 419 |
-
else:
|
| 420 |
-
_step(last, f'[ํ์ฌ] {nm}', cur, '์กฐ๊ฑด ์ถฉ์กฑ์ํ(๋ค์๋ด ํ์ธ)')
|
| 421 |
-
return sells, jumps, steps
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
def find_rs_a4_sells(stock, index, dates, start_pos, ma60=None, ma240=None,
|
| 425 |
-
low=None, bbl=None, ma240_est=None, ma120=None):
|
| 426 |
-
"""RS_A4 ์์ง: RS์ (์ข
๋ชฉ/์ง์)์ด ์์ฅ๋ณด๋ค ์ฝํด์ง๋ฉด(RS<RS_MA, RS_PERSIST์ผ ์ง์) '๋ฌด์ฅ',
|
| 427 |
-
๊ทธ ์ํ์์ A-4 ์ถ์ธํํฐ๊ฐ ํ์ธ๋๋ ์ฆ์ ์ ๋๋งค๋. ๋จ๊ณ ์ ๊ฑฐ์นจ -> ์ผ์ฐ ์ด๋ค.
|
| 428 |
-
๋ฐํ์ STAGE ์์ง๊ณผ ๋์ผ ํ์ (sells, jumps, steps)."""
|
| 429 |
-
n = len(stock)
|
| 430 |
-
twpk, twip = _trailing_peaks(stock, index, PEAK_WINDOW)
|
| 431 |
-
|
| 432 |
-
flat_slopes = None
|
| 433 |
-
if USE_TREND_FLAT:
|
| 434 |
-
flat_slopes = {}
|
| 435 |
-
for ln, arr in ((60, ma60), (120, ma120), (240, ma240)):
|
| 436 |
-
if arr is not None:
|
| 437 |
-
flat_slopes[ln] = _smoothed_slope(arr, FLAT_SLOPE_DAYS, FLAT_SMOOTH)
|
| 438 |
-
|
| 439 |
-
# RS์ (์ข
๋ชฉ/์ง์) -> ํฑ๋ ์ค์ด๋ ค EMA๋ก ๋ถ๋๋ฝ๊ฒ -> ๊ทธ ์ด๋ํ๊ท ๊ณผ ๋น๊ต(๋ฒํผ๋ฐด๋)
|
| 440 |
-
rs_raw = pd.Series([stock[t] / index[t] if index[t] else np.nan for t in range(n)])
|
| 441 |
-
rs_s = rs_raw.ewm(span=max(1, RS_SMOOTH), adjust=False).mean() if RS_SMOOTH > 1 else rs_raw
|
| 442 |
-
rs = rs_s.values
|
| 443 |
-
rs_ma = rs_s.rolling(RS_MA_PERIOD).mean().values
|
| 444 |
-
# ๋ฐ๋ํฌ๋ก์ค: RS๊ฐ MA๋ณด๋ค RS_BUFFER ๋งํผ 'ํ์คํ' ์๋์ฌ์ผ ์ฝํ ์ธ์ (์ค์นจ ๋ฌด์)
|
| 445 |
-
rs_weak = np.array([(not np.isnan(rs_ma[t])) and rs[t] < rs_ma[t] * (1 - RS_BUFFER)
|
| 446 |
-
for t in range(n)])
|
| 447 |
-
|
| 448 |
-
sells = []; jumps = []; steps = []
|
| 449 |
-
def _step(t, name, div, note=''):
|
| 450 |
-
if t >= start_pos:
|
| 451 |
-
steps.append({'date': dates[t], 'name': name, 'div': div, 'note': note})
|
| 452 |
-
|
| 453 |
-
def _gate(t):
|
| 454 |
-
if ma240 is None:
|
| 455 |
-
return True, ''
|
| 456 |
-
return trend_ok(t, stock, ma240, ma60, ma120, flat_slopes)
|
| 457 |
-
|
| 458 |
-
def _flat_gate(line_no):
|
| 459 |
-
"""CONFIRM_NEEDS_FLAT์ ๋ฐ๋ผ ์ถ๊ฐ ์กฐ๊ฑด. (ํต๊ณผ์ฌ๋ถ, ์ฌ์ ๊ผฌ๋ฆฌํ) ๋ฐํ."""
|
| 460 |
-
def slope_at(ln, t):
|
| 461 |
-
arr = flat_slopes.get(ln) if flat_slopes else None
|
| 462 |
-
return arr[t] if (arr is not None and not np.isnan(arr[t])) else None
|
| 463 |
-
line_arr = {60: ma60, 120: ma120}.get(line_no)
|
| 464 |
-
if CONFIRM_NEEDS_FLAT == 'SELF':
|
| 465 |
-
def chk(t):
|
| 466 |
-
s = slope_at(line_no, t)
|
| 467 |
-
if s is None:
|
| 468 |
-
return True, ''
|
| 469 |
-
if s > 0: # ํ์ธ์ ์ด ์ฐ์ํฅ -> ๋งค๋ ๊ธ์ง
|
| 470 |
-
return False, f'{line_no}์ ์ฐ์ํฅ({s*100:+.1f}%)โ๋ณด๋ฅ'
|
| 471 |
-
return True, f'{line_no}์ ๊ธฐ์ธ๊ธฐ{s*100:+.1f}%'
|
| 472 |
-
return chk
|
| 473 |
-
if CONFIRM_NEEDS_FLAT in ('MARGIN', 'MARGIN+240'):
|
| 474 |
-
# ๊ธฐ์ธ๊ธฐ(ํํ) ๋์ ๋ง์ง: ์ข
๊ฐ๊ฐ ํ์ธ์ ๋ณด๋ค CONFIRM_MARGIN ํ์คํ ์๋์ฌ์ผ ๋ฐ๋.
|
| 475 |
-
# ์ ๊น ๋๋ฆผ(์ ์ด์ง ์ค์นจ)=๋ฌด์, ์ง์ง ๋ถ๊ดด(์ ํจ์ฌ ์๋)=์ฆ์(์ ๊บพ์ ์ ๊ธฐ๋ค๋ฆผ).
|
| 476 |
-
# '+240'์ด๋ฉด ์ถ๊ฐ๋ก 240์ ์ด ๋๋ฌด ๊ฐํ๋ฅธ ์ฐ์ํฅ(>FLAT_MAX_240)์ด๋ฉด ๋งค๋ ๊ธ์ง(์์น ํ๋ณตํ ๋๋ฆผ).
|
| 477 |
-
def chk(t):
|
| 478 |
-
if line_arr is None or np.isnan(line_arr[t]):
|
| 479 |
-
return True, ''
|
| 480 |
-
margin = (1 - stock[t] / line_arr[t]) # ์ ๋๋น ์ผ๋ง๋ ์๋(%)
|
| 481 |
-
if margin < CONFIRM_MARGIN:
|
| 482 |
-
return False, f'{line_no}์ ์ด์ง(-{margin*100:.1f}%<{CONFIRM_MARGIN*100:.0f}%)โ๋ณด๋ฅ'
|
| 483 |
-
if CONFIRM_NEEDS_FLAT == 'MARGIN+240':
|
| 484 |
-
s240 = slope_at(240, t)
|
| 485 |
-
if s240 is not None and s240 > FLAT_MAX_240:
|
| 486 |
-
return False, f'240 ๊ฐํ๋ฅธ์์น({s240*100:+.1f}%>{FLAT_MAX_240*100:.0f}%)=๋๋ฆผโ๋ณด๋ฅ'
|
| 487 |
-
return True, f'{line_no}์ -{margin*100:.1f}%(๋ถ๊ดด)'
|
| 488 |
-
return chk
|
| 489 |
-
if CONFIRM_NEEDS_FLAT == '240':
|
| 490 |
-
def chk(t):
|
| 491 |
-
s = slope_at(240, t)
|
| 492 |
-
if s is None:
|
| 493 |
-
return True, ''
|
| 494 |
-
if not (FLAT_MIN_240 < s <= FLAT_MAX_240):
|
| 495 |
-
return False, f'240๊ธฐ์ธ๊ธฐ {s*100:+.1f}%โ({FLAT_MIN_240*100:.0f},{FLAT_MAX_240*100:.0f}]โ๋ณด๋ฅ'
|
| 496 |
-
return True, f'240๊ธฐ์ธ๊ธฐ {s*100:+.1f}%'
|
| 497 |
-
return chk
|
| 498 |
-
return lambda t: (True, '') # 'OFF'
|
| 499 |
-
|
| 500 |
-
_g60 = _flat_gate(60); _g120 = _flat_gate(120)
|
| 501 |
-
|
| 502 |
-
def _confirm(t):
|
| 503 |
-
"""๋ฌด์ฅ ํ '๋งค๋ ํ์ธ' ์ ํธ. ๋ชจ๋๋ณ:
|
| 504 |
-
A4=์ดํ์ ํํ(๋๋ฆผ/์์ ) / MA60=์ข
๊ฐ<60์ (๋น ๋ฆ) / MA120=์ข
๊ฐ<120์ (์ค๊ฐ) / NONE=RS๋ง.
|
| 505 |
-
MA60/120์ CONFIRM_NEEDS_FLAT ๊ธฐ์ธ๊ธฐ ๊ฒ์ดํธ๋ ํต๊ณผํด์ผ ํจ(์ค๋ฅผ ๋ ๋งค๋ ๊ธ์ง)."""
|
| 506 |
-
if RS_CONFIRM == 'NONE':
|
| 507 |
-
return True, 'RS๋จ๋
'
|
| 508 |
-
if RS_CONFIRM == 'MA60':
|
| 509 |
-
if ma60 is None or np.isnan(ma60[t]):
|
| 510 |
-
return False, '60์ ์์'
|
| 511 |
-
if stock[t] >= ma60[t]:
|
| 512 |
-
return False, f'60์ ์(์ข
๊ฐ {stock[t]:,.0f}>{ma60[t]:,.0f})'
|
| 513 |
-
gok, gwhy = _g60(t)
|
| 514 |
-
if not gok:
|
| 515 |
-
return False, gwhy
|
| 516 |
-
return True, f'์ข
๊ฐ<60์ ({ma60[t]:,.0f}) {gwhy}'.strip()
|
| 517 |
-
if RS_CONFIRM == 'MA120':
|
| 518 |
-
if ma120 is None or np.isnan(ma120[t]):
|
| 519 |
-
return False, '120์ ์์'
|
| 520 |
-
if stock[t] >= ma120[t]:
|
| 521 |
-
return False, f'120์ ์(์ข
๊ฐ {stock[t]:,.0f}>{ma120[t]:,.0f})'
|
| 522 |
-
gok, gwhy = _g120(t)
|
| 523 |
-
if not gok:
|
| 524 |
-
return False, gwhy
|
| 525 |
-
return True, f'์ข
๊ฐ<120์ ({ma120[t]:,.0f}) {gwhy}'.strip()
|
| 526 |
-
return _gate(t) # 'A4'
|
| 527 |
-
|
| 528 |
-
armed = False; armed_date = None; sold = False; weak_run = 0
|
| 529 |
-
locked = False; lock_i = -1
|
| 530 |
-
for t in range(n):
|
| 531 |
-
div = (stock[t] / twpk[t] - 1) - ((index[t] / twip[t] - 1) if twip[t] else 0.0)
|
| 532 |
-
|
| 533 |
-
# SELL_ONCE ์ ๊ธ: ๋งค๋ ํ (1)RELOCK_LINE ํ๋ณต AND (2)์ต์๊ธฐ๊ฐ ๊ฒฝ๊ณผ ๋ ๋ค์ฌ์ผ ํ๋ฆผ
|
| 534 |
-
if locked:
|
| 535 |
-
line = {60: ma60, 120: ma120, 240: ma240}.get(RELOCK_LINE, ma240)
|
| 536 |
-
if line is None:
|
| 537 |
-
line = ma120 if ma120 is not None else ma60
|
| 538 |
-
above = (line is not None and not np.isnan(line[t]) and stock[t] > line[t])
|
| 539 |
-
enough_days = (t - lock_i) >= RELOCK_MIN_DAYS
|
| 540 |
-
if above and enough_days:
|
| 541 |
-
locked = False; armed = False; weak_run = 0; sold = False
|
| 542 |
if t >= start_pos:
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
if armed and t >= start_pos:
|
| 552 |
-
_step(t, '๋๊ธฐ ํด์ (RS ํ๋ณต)', div)
|
| 553 |
-
armed = False; weak_run = 0
|
| 554 |
-
if not SELL_ONCE:
|
| 555 |
-
sold = False
|
| 556 |
-
continue
|
| 557 |
-
|
| 558 |
-
# RS_PERSIST์ผ ์ฐ์ ์ฝํ๋ฉด ๋ฌด์ฅ (์ด๋ฏธ ํ ์ํ๋ฉด ๋ฌด์ฅ ์ ํจ)
|
| 559 |
-
if not armed and not sold and weak_run >= RS_PERSIST:
|
| 560 |
-
# (1) ์ข
๊ฐ๊ฐ 240์ ๋ณด๋ค ๊น์ด ์๋ = ์ถ์ธ ๋ฌด๋์ง -> ๋ฌด์ฅ ๊ธ์ง
|
| 561 |
-
deep_below = False
|
| 562 |
-
if NO_ARM_BELOW_240 is not None and ma240 is not None and not np.isnan(ma240[t]):
|
| 563 |
-
deep_below = (stock[t] / ma240[t] - 1) <= NO_ARM_BELOW_240
|
| 564 |
-
# (2) ์ด๊ฒฉ(๊ณ ์ ๋๋น ๋ํญ)์ด ์ด๋ฏธ ๋๋ฌด ๊น์ = ์ตํ๋ณด๋ฃจ ์์ ์ง๋จ -> ๋ฌด์ฅ ๊ธ์ง
|
| 565 |
-
deep_div = (NO_ARM_DIV_BELOW is not None and div <= NO_ARM_DIV_BELOW)
|
| 566 |
-
if deep_below or deep_div:
|
| 567 |
-
if t >= start_pos:
|
| 568 |
-
why = (f'์ด๊ฒฉ {div*100:.0f}%โค{NO_ARM_DIV_BELOW*100:.0f}%(๊ณ ์ ๋๋น ํ์ฐธ ๋น ์ง)'
|
| 569 |
-
if deep_div else
|
| 570 |
-
f'์ข
๊ฐ 240์ {(stock[t]/ma240[t]-1)*100:.0f}%(์ถ์ธ์ดํ)')
|
| 571 |
-
_step(t, '๋๊ธฐ ๋ณด๋ฅ(์ตํ๋ณด๋ฃจ ๋์ ์๋)', div, why)
|
| 572 |
-
else:
|
| 573 |
-
armed = True; armed_date = dates[t]
|
| 574 |
-
rsd = (rs[t] / rs_ma[t] - 1) * 100
|
| 575 |
-
_step(t, '๋งค๋๋๊ธฐ ์ง์
(RS ์ฝํ)', div, f'RS {rsd:.1f}% < MA{RS_MA_PERIOD}')
|
| 576 |
-
|
| 577 |
-
if armed and not sold:
|
| 578 |
-
ok, why = _confirm(t)
|
| 579 |
-
# ๊ฐํ ์์น์ถ์ธ(240์ฌ) ์ค์ด๋ฉด ๋งค๋ ๋ณด๋ฅ = ์์น ํ๋ณตํ ๋๋ฆผ false alarm ๋ฐฉ์ง
|
| 580 |
-
if ok and NO_SELL_STRONG_UPTREND and ma240 is not None and in_240_run(stock, ma240, t):
|
| 581 |
-
ok = False
|
| 582 |
if t >= start_pos:
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
|
|
|
| 586 |
if t >= start_pos:
|
| 587 |
-
|
| 588 |
-
sells.append({'i': t, 'price': float(stock[t]), 'div': div,
|
| 589 |
-
'date': dates[t], 'trend': why,
|
| 590 |
-
'via': f'RS์ฝํ({armed_date})', 'est': est})
|
| 591 |
-
_step(t, '์ ๋๋งค๋ โ
๋ฐ๋', div, f'RS์ฝํ ํ ํ์ธ[{RS_CONFIRM}]: {why}')
|
| 592 |
sold = True
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
else:
|
| 596 |
-
pass # ๋ฌด์ฅ๋์ง๋ง ํ์ธ์ ํธ ์์ง(๋๋ ์์น์ถ์ธ ๋ณด๋ฅ) -> ๋๊ธฐ
|
| 597 |
-
|
| 598 |
-
# ํ์ฌ ์ํ
|
| 599 |
-
last = n - 1
|
| 600 |
-
if not sold:
|
| 601 |
-
div = (stock[last] / twpk[last] - 1) - ((index[last] / twip[last] - 1) if twip[last] else 0.0)
|
| 602 |
-
if armed:
|
| 603 |
-
ok, why = _confirm(last)
|
| 604 |
-
# ๋งค๋ ํ์ธ์ ๊น์ง ๋จ์ ๊ฑฐ๋ฆฌ(๊ฐ๊ฒฉ ๊ธฐ์ค) = ์๋ฐ ๊ฒฝ๊ณ
|
| 605 |
-
line_arr = {'MA60': ma60, 'MA120': ma120}.get(RS_CONFIRM)
|
| 606 |
-
dist = None; lname = ''
|
| 607 |
-
if line_arr is not None and not np.isnan(line_arr[last]):
|
| 608 |
-
use_margin = CONFIRM_NEEDS_FLAT in ('MARGIN', 'MARGIN+240')
|
| 609 |
-
trigger = line_arr[last] * (1 - CONFIRM_MARGIN) if use_margin else line_arr[last]
|
| 610 |
-
dist = stock[last] / trigger - 1 # >0 ์ด๋ฉด ์ด๋งํผ ๋ ๋น ์ ธ์ผ ๋งค๋
|
| 611 |
-
lname = '60์ ' if RS_CONFIRM == 'MA60' else '120์ '
|
| 612 |
-
if dist is not None and dist > 0:
|
| 613 |
-
if dist <= 0.03:
|
| 614 |
-
nm = f'โ ๏ธ ๋งค๋ ๋๊ธฐ ({lname}๊น์ง -{dist*100:.1f}%) ๋งค๋ ์๋ฐ!'
|
| 615 |
-
else:
|
| 616 |
-
nm = f'๋งค๋ ๋๊ธฐ ({lname}๊น์ง -{dist*100:.1f}%)'
|
| 617 |
-
note = ''
|
| 618 |
-
elif dist is not None: # ๋ถ๊ดด์ ๋๋ฌํ์ผ๋ ๋ค๋ฅธ ์กฐ๊ฑด(์์น์ถ์ธ ๋ฑ)์ ๋งํ
|
| 619 |
-
nm = '๋งค๋ ๋๊ธฐ'; note = f'{lname} ๋๋ฌํ์ผ๋ ๋ณด๋ฅ ({why})'
|
| 620 |
-
else:
|
| 621 |
-
nm = '๋งค๋ ๋๊ธฐ'; note = f'ํ์ธ[{RS_CONFIRM}] ๋๊ธฐ์ค ({why})'
|
| 622 |
-
_step(last, f'[ํ์ฌ] {nm}', div, note)
|
| 623 |
-
else:
|
| 624 |
-
_step(last, '[ํ์ฌ] ๊ด๋ง', div, '')
|
| 625 |
-
return sells, jumps, steps
|
| 626 |
|
| 627 |
|
| 628 |
def in_240_run(stock, ma240, i):
|
|
@@ -637,16 +195,17 @@ def in_240_run(stock, ma240, i):
|
|
| 637 |
return True
|
| 638 |
|
| 639 |
|
| 640 |
-
def classify_240(stock, ma240, i, price):
|
| 641 |
-
"""('EARLY'|'STILL_UP'|'ALREADY_DOWN'|'UNKNOWN', ์ดํ๊น์ง ์ผ์)
|
|
|
|
| 642 |
m = ma240[i]
|
| 643 |
if np.isnan(m):
|
| 644 |
return ('UNKNOWN', None)
|
| 645 |
-
if
|
| 646 |
return ('ALREADY_DOWN', None)
|
| 647 |
end = min(i + 1 + LOOK_AHEAD, len(stock))
|
| 648 |
for k in range(i + 1, end):
|
| 649 |
-
if not np.isnan(ma240[k]) and
|
| 650 |
return ('EARLY', k - i)
|
| 651 |
return ('STILL_UP', None)
|
| 652 |
|
|
@@ -661,7 +220,7 @@ def protected(stock, i, price):
|
|
| 661 |
|
| 662 |
def load_one(ticker, market):
|
| 663 |
try:
|
| 664 |
-
sdf = fdr.DataReader(ticker, DATA_START,
|
| 665 |
except Exception:
|
| 666 |
return None
|
| 667 |
if not isinstance(sdf, pd.DataFrame) or 'Close' not in sdf.columns or len(sdf) == 0:
|
|
@@ -670,7 +229,7 @@ def load_one(ticker, market):
|
|
| 670 |
idf = None
|
| 671 |
for c in (idx_code, '^' + idx_code):
|
| 672 |
try:
|
| 673 |
-
t = fdr.DataReader(c, DATA_START,
|
| 674 |
if isinstance(t, pd.DataFrame) and 'Close' in t.columns and len(t) > 0:
|
| 675 |
idf = t; break
|
| 676 |
except Exception:
|
|
@@ -684,20 +243,7 @@ def load_one(ticker, market):
|
|
| 684 |
merged = merged.dropna()
|
| 685 |
if len(merged) == 0:
|
| 686 |
return None
|
| 687 |
-
|
| 688 |
-
ma240_full = merged['stock'].rolling(240).mean()
|
| 689 |
-
if ESTIMATE_MA240:
|
| 690 |
-
# min_periods=MA240_MIN: ๋ฐ์ดํฐ๊ฐ MA240_MIN์ผ ์ด์์ด๋ฉด ์๋ ๋งํผ ํ๊ท ์ผ๋ก ์ฑ์
|
| 691 |
-
ma240_est = merged['stock'].rolling(240, min_periods=MA240_MIN).mean()
|
| 692 |
-
merged['ma240'] = ma240_est
|
| 693 |
-
merged['ma240_est'] = ma240_full.isna() & ma240_est.notna() # ์ถ์ ์ผ๋ก ์ฑ์ด ๊ตฌ๊ฐ True
|
| 694 |
-
else:
|
| 695 |
-
merged['ma240'] = ma240_full
|
| 696 |
-
merged['ma240_est'] = False
|
| 697 |
-
merged['ma60'] = merged['stock'].rolling(60).mean()
|
| 698 |
-
merged['ma120'] = merged['stock'].rolling(120).mean()
|
| 699 |
-
merged['bbl'] = (merged['stock'].rolling(20).mean()
|
| 700 |
-
- 2 * merged['stock'].rolling(20).std()) # BB ํ๋จ(์๋ช
์ ์ต์ปค์ฉ)
|
| 701 |
dates = [str(d)[:10] for d in merged.index]
|
| 702 |
start_pos = next((k for k, d in enumerate(dates) if d >= ANALYZE_FROM), 0)
|
| 703 |
return {
|
|
@@ -705,10 +251,6 @@ def load_one(ticker, market):
|
|
| 705 |
'low': merged['low'].values.astype(float),
|
| 706 |
'index': merged['index'].values.astype(float),
|
| 707 |
'ma240': merged['ma240'].values.astype(float),
|
| 708 |
-
'ma240_est': merged['ma240_est'].values.astype(bool),
|
| 709 |
-
'ma60': merged['ma60'].values.astype(float),
|
| 710 |
-
'ma120': merged['ma120'].values.astype(float),
|
| 711 |
-
'bbl': merged['bbl'].values.astype(float),
|
| 712 |
'dates': dates, 'start_pos': start_pos,
|
| 713 |
}
|
| 714 |
|
|
@@ -719,28 +261,9 @@ def pct(a, b):
|
|
| 719 |
|
| 720 |
def main():
|
| 721 |
print("=" * 88)
|
| 722 |
-
print(f"์ด๊ฒฉ ์ฌํ์ธ ๋ฐฑํ
์คํธ [lower-low] | {ANALYZE_FROM} ~ {
|
| 723 |
f"{sum(len(v) for v in TICKERS.values())}๊ฐ")
|
| 724 |
-
|
| 725 |
-
cb = FLAT_COMBINE
|
| 726 |
-
def _b(lo, hi):
|
| 727 |
-
return f"โค{hi*100:.0f}%" if lo <= -1 else f"({lo*100:.0f},{hi*100:.0f}]%"
|
| 728 |
-
tf_s = (f"์ถ์ธํํฐ=ON[๋ํํ{RUN_MIN_DAYS}์ผ AND A-4ํํ "
|
| 729 |
-
f"60{_b(FLAT_MIN_60,FLAT_MAX_60)} {cb} "
|
| 730 |
-
f"120{_b(FLAT_MIN_120,FLAT_MAX_120)} {cb} "
|
| 731 |
-
f"240{_b(FLAT_MIN_240,FLAT_MAX_240)}]")
|
| 732 |
-
else:
|
| 733 |
-
tf_s = "์ถ์ธํํฐ=OFF"
|
| 734 |
-
if ENGINE == 'RS_A4':
|
| 735 |
-
rs_confirm = {'A4': 'A-4ํํ', 'MA60': '์ข
๊ฐ<60์ ', 'MA120': '์ข
๊ฐ<120์ ',
|
| 736 |
-
'NONE': 'RS๋จ๋
'}.get(RS_CONFIRM, RS_CONFIRM)
|
| 737 |
-
print(f"์์ง=RS_A4 | RS(EMA{RS_SMOOTH})<MA{RS_MA_PERIOD}ร(1-{RS_BUFFER*100:.0f}%) "
|
| 738 |
-
f"{RS_PERSIST}์ผ์ฐ์ RS์ฝํ -> ํ์ธ[{rs_confirm}] ์ฆ์๋งค๋ | {tf_s}")
|
| 739 |
-
else:
|
| 740 |
-
div_s = f"์ด๊ฒฉ๊ธฐ์ค={DIV_THRESHOLD*100:.0f}%(๊ณ ์ )"
|
| 741 |
-
jmp_s = f"ํ๋ฃจ๊ธ๋ณ({DAILY_JUMP*100:.0f}%p)์ ํ"
|
| 742 |
-
print(f"์์ง=STAGE | ๊ณ ์ =์ต๊ทผ{PEAK_WINDOW}์ผ๋ด์ต๊ณ | {div_s} | ๋จ๊ณ๋์ ({MIN_DEEPEN*100:.1f}%p+, "
|
| 743 |
-
f"๋งค๋๊น์ง {CONFIRM_STAGES+1}๋จ๊ณ) | {jmp_s} | {tf_s}")
|
| 744 |
print(f"์ฑ์ : ๊ด์ฐฐ {LOOK_AHEAD}์ผ | E์จ=240์ดํ(์กฐ๊ธฐ๊ฒฝ๋ณด) | P์จ=๋งค๋๊ฐ๋๋น {PROTECT_DROP*100:.0f}% ์ถ๊ฐํ๋ฝ ํํผ")
|
| 745 |
print("=" * 88)
|
| 746 |
|
|
@@ -760,27 +283,29 @@ def main():
|
|
| 760 |
all_jumps = [] # (tk, market, jump) ์ ์ฒด ๊ธ๋ณ ์ ํ
|
| 761 |
for (tk, market), r in cache.items():
|
| 762 |
stock, index, ma240 = r['stock'], r['index'], r['ma240']
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
_sd, _sm = FLAT_SLOPE_DAYS, FLAT_SMOOTH
|
| 766 |
-
sl60 = _smoothed_slope(ma60, _sd, _sm) if ma60 is not None else None
|
| 767 |
-
sl120 = _smoothed_slope(ma120, _sd, _sm) if ma120 is not None else None
|
| 768 |
-
sl240 = _smoothed_slope(ma240, _sd, _sm) if ma240 is not None else None
|
| 769 |
-
_engine = find_rs_a4_sells if ENGINE == 'RS_A4' else find_confirmed_sells
|
| 770 |
-
sells, jumps, steps = _engine(stock, index, r['dates'], r['start_pos'],
|
| 771 |
-
ma60=ma60, ma240=ma240,
|
| 772 |
-
low=r.get('low'), bbl=r.get('bbl'),
|
| 773 |
-
ma240_est=r.get('ma240_est'), ma120=ma120)
|
| 774 |
for j in jumps:
|
| 775 |
all_jumps.append((tk, market, j))
|
| 776 |
lines = []
|
| 777 |
for s in sells:
|
| 778 |
i = s['i']; price = s['price']
|
| 779 |
-
cat, days = classify_240(stock, ma240, i, price)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
isP = bool(protected(stock, i, price))
|
| 781 |
is_run = in_240_run(stock, ma240, i)
|
| 782 |
if cat == 'ALREADY_DOWN':
|
| 783 |
down_n += 1
|
|
|
|
| 784 |
elif cat == 'EARLY':
|
| 785 |
b = run if is_run else edge
|
| 786 |
b['n'] += 1; b['E'] += 1
|
|
@@ -788,29 +313,17 @@ def main():
|
|
| 788 |
if is_run:
|
| 789 |
drop = stock[min(i + 1 + LOOK_AHEAD, len(stock)) - 1] / price - 1
|
| 790 |
run['drops'].append(drop)
|
|
|
|
| 791 |
elif cat == 'STILL_UP':
|
| 792 |
b = run if is_run else edge
|
| 793 |
b['n'] += 1
|
| 794 |
if isP: b['P'] += 1
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
# ๊ธฐ์ธ๊ธฐ ๋๋ฒ๊น
: ๋งค๋ ์์ ์ 60/120/240์ ๊ธฐ์ธ๊ธฐ(์ค๋ฌด๋ฉ) ํญ์ ํ์
|
| 800 |
-
def _slv(arr):
|
| 801 |
-
if arr is None or i >= len(arr) or np.isnan(arr[i]):
|
| 802 |
-
return '-'
|
| 803 |
-
return f'{arr[i]*100:.1f}%'
|
| 804 |
-
slp = f'๊ธฐ์ธ๊ธฐ[60={_slv(sl60)} 120={_slv(sl120)} 240={_slv(sl240)}]'
|
| 805 |
-
parts = [p for p in (via, tr, est, avoid, slp) if p]
|
| 806 |
lines.append(f" ๋งค๋ {s['date']} @ {price:,.0f} (์ด๏ฟฝ๏ฟฝ {s['div']*100:.0f}%) "
|
| 807 |
-
f"[{
|
| 808 |
-
# ๋จ๊ณ ์์ธ ๋ก๊ทธ
|
| 809 |
-
if SHOW_STEPS and steps:
|
| 810 |
-
lines.append(f" [๋จ๊ณ ์์ธ]")
|
| 811 |
-
for stp in steps:
|
| 812 |
-
nt = f" ({stp['note']})" if stp['note'] else ''
|
| 813 |
-
lines.append(f" {stp['date']} {stp['name']:<24} ์ด๊ฒฉ {stp['div']*100:+.1f}%{nt}")
|
| 814 |
detail.append((tk, market, lines))
|
| 815 |
|
| 816 |
tot = run['n'] + edge['n'] + down_n
|
|
@@ -833,23 +346,22 @@ def main():
|
|
| 833 |
else:
|
| 834 |
print(f" {tk} {market} - ๋งค๋ ์์")
|
| 835 |
|
| 836 |
-
# ---- ํ๋ฃจ ๊ธ๋ณ ์ ํ ๋ฐ๋ ๋ด์ญ
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
f"{j['from']} -> {j['to']}{mark}")
|
| 853 |
|
| 854 |
|
| 855 |
if __name__ == '__main__':
|
|
|
|
| 4 |
|
| 5 |
๊ท์น:
|
| 6 |
- ์ด๋ฒคํธ = ์ข
๋ชฉ ์๊ธฐ๊ณ ์ ๊ธฐ์ค ์ด๊ฒฉ์ด -10% ์์์ -10% ์ดํ๋ก '์๋ก ์ง์
'ํ ๊ตฌ๊ฐ 1๊ฐ (๊น์ด ๋ฌด๊ด)
|
| 7 |
+
- 1์ฐจ ํ '์ง์ ์ด๋ฒคํธ๋ก๋ถํฐ 22๊ฑฐ๋์ผ ์ด๋ด'์ ๋ค์ ์ด๋ฒคํธ -> 2์ฐจ,3์ฐจ... ์ฐ์
|
| 8 |
+
(22์ผ ๋๊ธฐ๋ฉด ๋๊ธฐ๊ณ ๊ทธ๊ฒ ์ 1์ฐจ)
|
| 9 |
- 2์ฐจ๊ฐ ์ฑ๋ฆฝํ๋ฉด ๊ทธ ๋ '์ ๋๋งค๋' (๊น์ด ๋น๊ต ์์)
|
| 10 |
- ์ ๊ณ ๊ฐ ๊ฐฑ์ ์ ๋ฆฌ์
|
| 11 |
|
|
|
|
| 19 |
import pandas as pd
|
| 20 |
import numpy as np
|
| 21 |
import FinanceDataReader as fdr
|
| 22 |
+
from datetime import datetime, timedelta
|
|
|
|
|
|
|
| 23 |
|
| 24 |
ssl._create_default_https_context = ssl._create_unverified_context
|
| 25 |
if sys.platform == 'win32':
|
|
|
|
| 30 |
# =============================================
|
| 31 |
TICKERS = {
|
| 32 |
'KOSPI': [
|
| 33 |
+
# '005930', '000660', '373220', '207940', '005380',
|
|
|
|
| 34 |
# '000270', '068270', '035420', '035720', '005490',
|
| 35 |
+
# '105560', '055550', '012330', '051910', '006400',
|
|
|
|
|
|
|
|
|
|
| 36 |
# '028260', '066570', '096770', '034730', '015760',
|
|
|
|
| 37 |
# '204320',
|
| 38 |
+
# '012450',
|
| 39 |
],
|
| 40 |
'KOSDAQ': [
|
| 41 |
# '247540', '086520', '196170', '028300', '348370',
|
| 42 |
+
# '058470', '068760', '263750', '035900',
|
| 43 |
+
# '277810',
|
| 44 |
+
'234690',
|
| 45 |
# '041510', '035760', '112040', '293490', '145020',
|
| 46 |
# '039030', '240810', '357780',
|
| 47 |
# '403870',
|
| 48 |
# '121600',
|
| 49 |
# '399720',
|
| 50 |
+
# '234340',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
],
|
| 52 |
}
|
| 53 |
|
| 54 |
# =============================================
|
| 55 |
# ์ค์
|
| 56 |
# =============================================
|
| 57 |
+
DIV_THRESHOLD = -0.10 # ์ด๊ฒฉ ๊ธฐ์ค
|
| 58 |
+
MIN_DEEPEN = 0.025 # ๋ค์ ๋จ๊ณ๋ก ๊ฐ๋ ค๋ฉด ์ง์ ๋จ๊ณ๋ณด๋ค ์ต์ ์ด๋งํผ(%p) ๋ ๊น์ด์ผ (๋
ธ์ด์ฆ ๋ฐฉ์ง)
|
| 59 |
+
DAILY_JUMP = 0.10 # ํ๋ฃจ๋ง์ ์ด๊ฒฉ์ด ์ ์ผ ๋๋น ์ด๋งํผ(%p)+ ๊ธ๋ฝํ๋ฉด '๋๊ธฐ' ๊ฑด๋๋ฐ๊ณ ๋ฐ๋ก ํ์ ์ ํ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
PEAK_WINDOW = 252 # ๊ณ ์ ๊ธฐ์ค = ์ต๊ทผ N๊ฑฐ๋์ผ(252=1๋
) ๋ด ์ต๊ณ ์ (์ค๋๋ ๊ณ ์ ์ ์ ๋ฌถ์)
|
| 61 |
+
CONFIRM_WINDOW = 9999 # 1์ฐจ ์ด๋ฒคํธ๋ก๋ถํฐ 2์ฐจ(๋งค๋)๊น์ง N์ผ ์ด๋ด
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
LOOK_AHEAD = 200 # ๋งค๋ ํ ๊ด์ฐฐ๊ธฐ๊ฐ(๊ฑฐ๋์ผ)
|
| 63 |
PROTECT_DROP = -0.10 # ๋งค๋๊ฐ ๋๋น ์ด๋งํผ ๋ ๋น ์ง๋ฉด '์ ํ ๊ฒ'
|
| 64 |
|
| 65 |
+
TREND_MIN_DAYS = 60 # '240์ฌ': ์ต๊ทผ N๊ฑฐ๋์ผ ์ฐ์ 240์ ์
|
| 66 |
+
TREND_MIN_GAP = 0.05 # '240์ฌ': ๋งค๋์ผ ์ข
๊ฐ๊ฐ 240์ +5% ์ด์
|
| 67 |
|
| 68 |
+
# --- ๋งค๋ ์๋ ์ต์ ํํฐ (์์ฒญ์ฌํญ) ---
|
| 69 |
+
SKIP_IF_BELOW_240 = True # ๋งค๋ ์์ ์ข
๊ฐ๊ฐ 240์ ์๋๋ฉด ์๋ ๋ (์ด๋ฏธ ๋ฆ์)
|
| 70 |
+
SKIP_IF_NO_240 = True # 240์ ์ด ์์ผ๋ฉด(๋ฐ์ดํฐ ๋ถ์กฑ) ์๋ ๋
|
| 71 |
+
MAX_EARLY_DAYS = 129 # ๋งค๋ ํ ์ด ์ผ์๊น์ง๋ง ์ ํจ. ์ด๋ณด๋ค ๋ฆ๊ฒ(130์ผ+) 240์ดํํ๋ฉด
|
| 72 |
+
# = ๋๋ฌด ์ผ์ฐ ํ ๊ฒ -> ์๋ ๋. (None์ด๋ฉด ์ด ํํฐ ๋)
|
| 73 |
+
SKIP_IF_STILL_UP = True # ๋งค๋ ํ ๊ด์ฐฐ๊ธฐ๊ฐ ๋ด๋ด 240 ์ ๊นจ๊ณ ์ถ์ธ์ ์ง=ํ๋งค๋(false alarm) -> ์๋ ๋
|
| 74 |
|
| 75 |
ANALYZE_FROM = '2020-01-01' # ์ฑ์ (๋งค๋์ง๊ณ) ์์์ผ โ ์ด๊ฑด '์ธ์ ๋ถํฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณผ์ง'์ผ ๋ฟ
|
| 76 |
+
ANALYZE_TO = None # None=์ค๋
|
| 77 |
|
| 78 |
# โ
๋ฐ์ดํฐ ๋ก๋ฉ์ ANALYZE_FROM ๊ณผ ๋ฌด๊ดํ๊ฒ 'ํญ์ ์ถฉ๋ถํ ๊ณผ๊ฑฐ๋ถํฐ' ๋ถ๋ฌ์จ๋ค.
|
| 79 |
# (๊ณ ์ /์ด๊ฒฉ์ ์ ์ฒด ๊ฐ๊ฒฉ์ด๋ ฅ์ผ๋ก ๊ณ์ฐ๋ผ์ผ ํ๋ฏ๋ก. ANALYZE_FROM ๋ง ๋ฆ์ถ๋ฉด ๊ณ ์ ์
|
| 80 |
# ์๋ชป ์ก์ ๋งค๋์์ ์ด ๋ฌ๋ผ์ง๋ ๋ฒ๊ทธ๊ฐ ์๊น -> ๊ทธ๋์ DATA_START ๋ ๊ณ ์ .)
|
| 81 |
DATA_START = '2018-01-01' # ๊ณ ์ . ๋ ๊ณผ๊ฑฐ๋ฅผ ๋ณด๋ ค๋ฉด ์ด ๋ ์ง๋ง ์๋น๊ธฐ๋ฉด ๋จ.
|
| 82 |
|
| 83 |
+
_to_dt = datetime.strptime(ANALYZE_TO, '%Y-%m-%d') if ANALYZE_TO else datetime.today()
|
| 84 |
+
ANALYZE_TO = _to_dt.strftime('%Y-%m-%d')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
|
| 87 |
# =============================================
|
|
|
|
| 103 |
return pk, ipk
|
| 104 |
|
| 105 |
|
| 106 |
+
def find_confirmed_sells(stock, index, dates, start_pos):
|
| 107 |
+
"""ํ์ ๋งค๋(2์ฐจ) ์ฐพ๊ธฐ โ ๋จ๊ณ๋ณ ๋์ ํ๋ฝ + ํ๋ฃจ ์ด๊ฒฉ ๊ธ๋ณ ์ ํ.
|
| 108 |
+
๊ณ ์ = '์ต๊ทผ PEAK_WINDOW(252) ๊ฑฐ๋์ผ ๋ด ์ต๊ณ ์ ' (ํธ๋ ์ผ๋ง).
|
| 109 |
+
๋จ๊ณ: flagON(๋๊ธฐ) -> 1์ฐจ(ํ์ ) -> 2์ฐจflagON(๋๊ธฐ) -> 2์ฐจ๋งค๋(ํ์ )
|
| 110 |
+
- ํ์: ์ง์ ๋จ๊ณ๋ณด๋ค ์ด๊ฒฉ MIN_DEEPEN(2.5%p)+ ๋ ๊น์ผ๋ฉด ๋ค์ ๋จ๊ณ
|
| 111 |
+
- ํ๋ฃจ ๊ธ๋ณ: ์ ์ผ ์ด๊ฒฉ ๋๋น ์ค๋ ์ด๊ฒฉ์ด DAILY_JUMP(10%p)+ ๊ธ๋ฝํ๋ฉด
|
| 112 |
+
'๋๊ธฐ(flagON)' ๊ฑด๋๋ฐ๊ณ ๋ฐ๋ก ํ์ ์ผ๋ก ์ ํ
|
| 113 |
+
- 1์ฐจ -> 2์ฐจ(๋งค๋)๋ 22์ผ ์ด๋ด
|
| 114 |
+
- ์ด๊ฒฉ -10% ์๋ก ํ๋ณตํ๋ฉด ์ํผ์๋ ๋ฆฌ์
(๋งค๋ํ๋ ๊ฒ๋ ํ๋ฆผ)."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
n = len(stock)
|
| 116 |
twpk, twip = _trailing_peaks(stock, index, PEAK_WINDOW)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
stage = 0; ref_div = None; first_i = None; sold = False
|
| 118 |
prev_div = None
|
| 119 |
sells = []
|
| 120 |
+
jumps = [] # ํ๋ฃจ๊ธ๋ณ ์ ํ ๋ฐ๋ ๋ด์ญ
|
| 121 |
_SNAME = {0: '์์', 1: 'flagON', 2: '1์ฐจ', 3: '2์ฐจflagON', 4: '2์ฐจ๋งค๋'}
|
| 122 |
|
| 123 |
def _log_jump(t, frm, to, div, pdv, st):
|
|
|
|
| 127 |
'from': _SNAME[frm], 'to': _SNAME[to],
|
| 128 |
'skip': (frm == 0 and to == 2) or (frm == 2 and to == 4)})
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
for t in range(n):
|
| 131 |
st = float(stock[t]); it = float(index[t])
|
| 132 |
peak = float(twpk[t]); iap = float(twip[t])
|
| 133 |
+
div = (st / peak - 1) - ((it / iap - 1) if iap else 0.0)
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
# ์ด๊ฒฉ -10% ์๋ก(=์ต๊ทผ ๊ณ ์ ๊ทผ์ฒ๋ก ํ๋ณต/์ ๊ณ ๊ฐ) -> ์ํผ์๋ ๋ฆฌ์
|
| 136 |
if div > DIV_THRESHOLD:
|
|
|
|
|
|
|
| 137 |
stage = 0; ref_div = None; first_i = None; sold = False
|
| 138 |
prev_div = div
|
| 139 |
continue
|
| 140 |
+
|
| 141 |
+
if sold: # ๋งค๋ ํ, ์์ง ํ๋ณต ๋ชปํจ -> ์ ์ง ์ ์ง
|
| 142 |
prev_div = div
|
| 143 |
continue
|
| 144 |
|
| 145 |
+
# ํ๋ฃจ ๊ธ๋ณ: ์ ์ผ ์ด๊ฒฉ ๋๋น DAILY_JUMP ์ด์ ๊ธ๋ฝ
|
| 146 |
crash = (prev_div is not None) and ((div - prev_div) <= -DAILY_JUMP)
|
| 147 |
+
within = (first_i is None) or ((t - first_i) <= CONFIRM_WINDOW)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
+
if stage == 0: # ์๋ฌด๊ฒ๋ ์์
|
|
|
|
|
|
|
|
|
|
| 150 |
if crash:
|
| 151 |
+
_log_jump(t, 0, 2, div, prev_div, st)
|
| 152 |
+
stage = 2; ref_div = div; first_i = t # flagON ๊ฑด๋๋ฐ๊ณ -> 1์ฐจ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
else:
|
| 154 |
+
stage = 1; ref_div = div # flag ON(๋๊ธฐ)
|
| 155 |
+
elif stage == 1: # flag ON(๋๊ธฐ)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
if crash:
|
| 157 |
+
_log_jump(t, 1, 2, div, prev_div, st)
|
| 158 |
+
stage = 2; ref_div = div; first_i = t # -> 1์ฐจ(ํ์ )
|
| 159 |
elif div <= ref_div - MIN_DEEPEN:
|
| 160 |
+
stage = 2; ref_div = div; first_i = t # -> 1์ฐจ(ํ์ )
|
| 161 |
+
elif stage == 2: # 1์ฐจ(ํ์ )
|
| 162 |
+
if crash and within:
|
| 163 |
+
_log_jump(t, 2, 4, div, prev_div, st)
|
| 164 |
+
stage = 4; ref_div = div # 2์ฐจflagON ๊ฑด๋๋ฐ๊ณ -> ๋งค๋
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
if t >= start_pos:
|
| 166 |
+
sells.append({'i': t, 'price': st, 'div': div, 'date': dates[t]})
|
| 167 |
+
sold = True
|
| 168 |
+
elif div <= ref_div - MIN_DEEPEN and within:
|
| 169 |
+
stage = 3; ref_div = div # -> 2์ฐจ flag ON(๋๊ธฐ)
|
| 170 |
+
elif stage == 3: # 2์ฐจ flag ON(๋๊ธฐ)
|
| 171 |
+
if crash and within:
|
| 172 |
+
_log_jump(t, 3, 4, div, prev_div, st)
|
| 173 |
+
stage = 4; ref_div = div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
if t >= start_pos:
|
| 175 |
+
sells.append({'i': t, 'price': st, 'div': div, 'date': dates[t]})
|
| 176 |
+
sold = True
|
| 177 |
+
elif div <= ref_div - MIN_DEEPEN and within:
|
| 178 |
+
stage = 4; ref_div = div # -> 2์ฐจ ๋งค๋(ํ์ )
|
| 179 |
if t >= start_pos:
|
| 180 |
+
sells.append({'i': t, 'price': st, 'div': div, 'date': dates[t]})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
sold = True
|
| 182 |
+
prev_div = div
|
| 183 |
+
return sells, jumps
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
|
| 186 |
def in_240_run(stock, ma240, i):
|
|
|
|
| 195 |
return True
|
| 196 |
|
| 197 |
|
| 198 |
+
def classify_240(stock, low, ma240, i, price):
|
| 199 |
+
"""('EARLY'|'STILL_UP'|'ALREADY_DOWN'|'UNKNOWN', ์ดํ๊น์ง ์ผ์)
|
| 200 |
+
ALREADY_DOWN ์ '์ ๊ฐ(์ฅ์ค)๊ฐ 240์ ์ ํฐ์น/์ดํ'ํ๋ฉด ํด๋น (์ข
๊ฐ ์๋)."""
|
| 201 |
m = ma240[i]
|
| 202 |
if np.isnan(m):
|
| 203 |
return ('UNKNOWN', None)
|
| 204 |
+
if low[i] <= m: # ์ฅ์ค ์ ๊ฐ๊ฐ 240์ ํฐ์น/์ดํ -> ์ด๋ฏธ ์ํ
|
| 205 |
return ('ALREADY_DOWN', None)
|
| 206 |
end = min(i + 1 + LOOK_AHEAD, len(stock))
|
| 207 |
for k in range(i + 1, end):
|
| 208 |
+
if not np.isnan(ma240[k]) and low[k] <= ma240[k]:
|
| 209 |
return ('EARLY', k - i)
|
| 210 |
return ('STILL_UP', None)
|
| 211 |
|
|
|
|
| 220 |
|
| 221 |
def load_one(ticker, market):
|
| 222 |
try:
|
| 223 |
+
sdf = fdr.DataReader(ticker, DATA_START, ANALYZE_TO)
|
| 224 |
except Exception:
|
| 225 |
return None
|
| 226 |
if not isinstance(sdf, pd.DataFrame) or 'Close' not in sdf.columns or len(sdf) == 0:
|
|
|
|
| 229 |
idf = None
|
| 230 |
for c in (idx_code, '^' + idx_code):
|
| 231 |
try:
|
| 232 |
+
t = fdr.DataReader(c, DATA_START, ANALYZE_TO)
|
| 233 |
if isinstance(t, pd.DataFrame) and 'Close' in t.columns and len(t) > 0:
|
| 234 |
idf = t; break
|
| 235 |
except Exception:
|
|
|
|
| 243 |
merged = merged.dropna()
|
| 244 |
if len(merged) == 0:
|
| 245 |
return None
|
| 246 |
+
merged['ma240'] = merged['stock'].rolling(240).mean()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
dates = [str(d)[:10] for d in merged.index]
|
| 248 |
start_pos = next((k for k, d in enumerate(dates) if d >= ANALYZE_FROM), 0)
|
| 249 |
return {
|
|
|
|
| 251 |
'low': merged['low'].values.astype(float),
|
| 252 |
'index': merged['index'].values.astype(float),
|
| 253 |
'ma240': merged['ma240'].values.astype(float),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
'dates': dates, 'start_pos': start_pos,
|
| 255 |
}
|
| 256 |
|
|
|
|
| 261 |
|
| 262 |
def main():
|
| 263 |
print("=" * 88)
|
| 264 |
+
print(f"์ด๊ฒฉ ์ฌํ์ธ ๋ฐฑํ
์คํธ [lower-low] | {ANALYZE_FROM} ~ {ANALYZE_TO} | ์ข
๋ชฉ "
|
| 265 |
f"{sum(len(v) for v in TICKERS.values())}๊ฐ")
|
| 266 |
+
print(f"๊ท์น: ๊ณ ์ =์ต๊ทผ{PEAK_WINDOW}์ผ๋ด์ต๊ณ | ๋จ๊ณ๋์ ({MIN_DEEPEN*100:.1f}%p+) | ํ๋ฃจ๊ธ๋ณ({DAILY_JUMP*100:.0f}%p)์ ํ | 1์ฐจ~2์ฐจ 22์ผ๋ด | ํ๋ณต์์ฌ์์")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
print(f"์ฑ์ : ๊ด์ฐฐ {LOOK_AHEAD}์ผ | E์จ=240์ดํ(์กฐ๊ธฐ๊ฒฝ๋ณด) | P์จ=๋งค๋๊ฐ๋๋น {PROTECT_DROP*100:.0f}% ์ถ๊ฐํ๋ฝ ํํผ")
|
| 268 |
print("=" * 88)
|
| 269 |
|
|
|
|
| 283 |
all_jumps = [] # (tk, market, jump) ์ ์ฒด ๊ธ๋ณ ์ ํ
|
| 284 |
for (tk, market), r in cache.items():
|
| 285 |
stock, index, ma240 = r['stock'], r['index'], r['ma240']
|
| 286 |
+
low = r['low']
|
| 287 |
+
sells, jumps = find_confirmed_sells(stock, index, r['dates'], r['start_pos'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
for j in jumps:
|
| 289 |
all_jumps.append((tk, market, j))
|
| 290 |
lines = []
|
| 291 |
for s in sells:
|
| 292 |
i = s['i']; price = s['price']
|
| 293 |
+
cat, days = classify_240(stock, low, ma240, i, price)
|
| 294 |
+
# --- ์๋ ์ต์ ํํฐ ---
|
| 295 |
+
if SKIP_IF_NO_240 and cat == 'UNKNOWN':
|
| 296 |
+
continue # 240์ ์์ -> ์๋ ๋
|
| 297 |
+
if SKIP_IF_BELOW_240 and cat == 'ALREADY_DOWN':
|
| 298 |
+
continue # ๋งค๋ ์์ ์ด๋ฏธ 240 ์๋ -> ์๋ ๋
|
| 299 |
+
if MAX_EARLY_DAYS is not None and cat == 'EARLY' and days is not None \
|
| 300 |
+
and days > MAX_EARLY_DAYS:
|
| 301 |
+
continue # 130์ผ+ ๋ค์์ผ 240์ดํ = ๋๋ฌด ์ผ์ฐ ํ ๊ฒ -> ๋
|
| 302 |
+
if SKIP_IF_STILL_UP and cat == 'STILL_UP':
|
| 303 |
+
continue # ๋งค๋ ํ ์ถ์ธ์ ์ง(240 ์๊นจ์ง) = ํ๋งค๋ -> ๋
|
| 304 |
isP = bool(protected(stock, i, price))
|
| 305 |
is_run = in_240_run(stock, ma240, i)
|
| 306 |
if cat == 'ALREADY_DOWN':
|
| 307 |
down_n += 1
|
| 308 |
+
where = '์ด๋ฏธ240์๋'
|
| 309 |
elif cat == 'EARLY':
|
| 310 |
b = run if is_run else edge
|
| 311 |
b['n'] += 1; b['E'] += 1
|
|
|
|
| 313 |
if is_run:
|
| 314 |
drop = stock[min(i + 1 + LOOK_AHEAD, len(stock)) - 1] / price - 1
|
| 315 |
run['drops'].append(drop)
|
| 316 |
+
where = f"{'240์ฌ' if is_run else '๊ฒฝ๊ณ'}, {days}์ผ๋ค 240์ดํ"
|
| 317 |
elif cat == 'STILL_UP':
|
| 318 |
b = run if is_run else edge
|
| 319 |
b['n'] += 1
|
| 320 |
if isP: b['P'] += 1
|
| 321 |
+
where = f"{'240์ฌ' if is_run else '๊ฒฝ๊ณ'}, ์ถ์ธ์ ์ง"
|
| 322 |
+
else:
|
| 323 |
+
where = 'ํ์ ๋ถ๊ฐ'
|
| 324 |
+
avoid = ', ํ๋ฝํํผO' if isP else ''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
lines.append(f" ๋งค๋ {s['date']} @ {price:,.0f} (์ด๏ฟฝ๏ฟฝ {s['div']*100:.0f}%) "
|
| 326 |
+
f"[{where}{avoid}]")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
detail.append((tk, market, lines))
|
| 328 |
|
| 329 |
tot = run['n'] + edge['n'] + down_n
|
|
|
|
| 346 |
else:
|
| 347 |
print(f" {tk} {market} - ๋งค๋ ์์")
|
| 348 |
|
| 349 |
+
# ---- ํ๋ฃจ ๊ธ๋ณ ์ ํ(flagON/2์ฐจflagON ๋๊ธฐ ๊ฑด๋๋) ๋ฐ๋ ๋ด์ญ ----
|
| 350 |
+
print("\n" + "=" * 88)
|
| 351 |
+
print(f"[ํ๋ฃจ ๊ธ๋ณ ์ ํ ๋ฐ๋ ๋ด์ญ] (์ ์ผ ์ด๊ฒฉ ๋๋น {DAILY_JUMP*100:.0f}%p+ ๊ธ๋ฝ -> ๋๊ธฐ๋จ๊ณ ๊ฑด๋๋)")
|
| 352 |
+
print("=" * 88)
|
| 353 |
+
if not all_jumps:
|
| 354 |
+
print(" ๋ฐ๋ ์์")
|
| 355 |
+
else:
|
| 356 |
+
n_skip = sum(1 for _, _, j in all_jumps if j['skip'])
|
| 357 |
+
print(f" ์ด {len(all_jumps)}ํ ๋ฐ๋ (์ด ์ค '๋๊ธฐ ๊ฑด๋๋(SKIP)' {n_skip}ํ)\n")
|
| 358 |
+
print(f" {'์ข
๋ชฉ':<8}{'์์ฅ':<7}{'๋ ์ง':<12}{'ํ๋ฃจ๋ํญ':>8}{'์ด๊ฒฉ':>8} ๋จ๊ณ์ ํ")
|
| 359 |
+
print(" " + "-" * 70)
|
| 360 |
+
for tk, market, j in sorted(all_jumps, key=lambda z: z[2]['date']):
|
| 361 |
+
mark = ' โ
๊ฑด๋๋' if j['skip'] else ''
|
| 362 |
+
print(f" {tk:<8}{market:<7}{j['date']:<12}"
|
| 363 |
+
f"{j['drop']*100:>7.1f}%{j['div']*100:>7.1f}% "
|
| 364 |
+
f"{j['from']} -> {j['to']}{mark}")
|
|
|
|
| 365 |
|
| 366 |
|
| 367 |
if __name__ == '__main__':
|