glorifiedjx commited on
Commit
2f45bf1
ยท
verified ยท
1 Parent(s): 2a0440d

RS_M60_final

Browse files
Files changed (1) hide show
  1. confirm_sell_backtest.py +224 -55
confirm_sell_backtest.py CHANGED
@@ -31,16 +31,32 @@ if sys.platform == 'win32':
31
  # =============================================
32
  TICKERS = {
33
  'KOSPI': [
34
- '005930', '000660', '373220', '207940', '005380',
35
- '000270', '068270', '035420', '035720', '005490',
36
- '105560', '055550', '012330', '051910', '006400',
37
- '028260', '066570', '096770', '034730', '015760',
 
 
 
 
 
 
 
38
  ],
39
  'KOSDAQ': [
40
- '247540', '086520', '196170', '028300', '348370',
41
- '058470', '068760', '263750', '035900', '277810',
42
- '041510', '035760', '112040', '293490', '145020',
43
- '039030', '240810', '357780', '403870', '121600',
 
 
 
 
 
 
 
 
 
44
  ],
45
  }
46
 
@@ -54,7 +70,7 @@ DAILY_JUMP = 0.10 # ํ•˜๋ฃจ๋งŒ์— ์ด๊ฒฉ์ด ์ „์ผๆฏ” ์ด๋งŒํผ(%p)+ ๊ธ‰
54
  # --- ๋‹จ๊ณ„ ์ˆ˜ ์†์žก์ด: flag ON ํ›„ '๋ช‡ ๋ฒˆ ๋” ๊นŠ์–ด์ ธ์•ผ' ์ „๋Ÿ‰๋งค๋„๊ฐ€ ๋ฐœ๋™ํ•˜๋‚˜ ---
55
  # 3 = ๊ธฐ์กด(flagโ†’1์ฐจโ†’2์ฐจflagโ†’2์ฐจ๋งค๋„, ๊นŠ์–ด์ง 3๋ฒˆ). ์ค„์ด๋ฉด ๋” ์ผ์ฐ ์šธ๋ฆฌ์ง€๋งŒ ๋” ์ž์ฃผ ์šธ๋ฆผ.
56
  # 2 = flagโ†’1์ฐจโ†’๋งค๋„ (๊นŠ์–ด์ง 2๋ฒˆ) / 1 = flagโ†’๋งค๋„ (๊นŠ์–ด์ง 1๋ฒˆ, ๊ฐ€์žฅ ์ผ์ฐ/๊ฐ€์žฅ ์ž์ฃผ)
57
- CONFIRM_STAGES = 3
58
 
59
  # =============================================
60
  # ๋งค๋„ ์—”์ง„ ์„ ํƒ
@@ -62,10 +78,37 @@ CONFIRM_STAGES = 3
62
  # 'RS_A4' = ์ƒˆ๊ฑฐ: RS์„ ์ด ์‹œ์žฅ๋ณด๋‹ค ์•ฝํ•ด์ง€๋ฉด(๋ฐ๋“œํฌ๋กœ์Šค) '๋ฌด์žฅ' -> A-4(์ถ”์„ธ์ฃฝ์Œ) ํ™•์ธ๋˜๋Š”
63
  # ์ฆ‰์‹œ ๋งค๋„. ๋‹จ๊ณ„ ์•ˆ ๊ฑฐ์นจ -> ๋” ์ผ์ฐ. RS๋Š” ์„ ํ–‰์ง€ํ‘œ๋ผ 240 ๊นจ์ง€๊ธฐ ์ „์— ๊ฒฝ๊ณ .
64
  # =============================================
65
- ENGINE = 'STAGE' # 'STAGE' / 'RS_A4'
66
  RS_MA_PERIOD = 20 # RS์„ (์ข…๋ชฉ/์ง€์ˆ˜)์˜ ์ด๋™ํ‰๊ท  ๊ธฐ๊ฐ„. RS < ์ด ํ‰๊ท ์ด๋ฉด ๋ฐ๋“œํฌ๋กœ์Šค(์•ฝํ™”)
 
 
 
67
  RS_PERSIST = 3 # RS๊ฐ€ ์ด๋งŒํผ(๊ฑฐ๋ž˜์ผ) ์—ฐ์† ์•ฝํ•ด์•ผ '๋ฌด์žฅ' (ํ•˜๋ฃจ ๋…ธ์ด์ฆˆ ๋ฐฉ์ง€)
68
- RS_CONFIRM = 'A4' # 'A4'=A-4 ์ถ”์„ธํ•„ํ„ฐ ํ™•์ธ๋ผ์•ผ ๋งค๋„ / 'NONE'=RS ๋ฐ๋“œํฌ๋กœ์Šค๋งŒ์œผ๋กœ ๋งค๋„
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  PEAK_WINDOW = 252 # ๊ณ ์  ๊ธฐ์ค€ = ์ตœ๊ทผ N๊ฑฐ๋ž˜์ผ(252=1๋…„) ๋‚ด ์ตœ๊ณ ์  (์˜ค๋ž˜๋œ ๊ณ ์ ์— ์•ˆ ๋ฌถ์ž„)
70
  # CONFIRM_WINDOW ์ œ๊ฑฐ๋จ: 1์ฐจ->2์ฐจ ์‚ฌ์ด ์ผ์ˆ˜ ์ œํ•œ ์—†์Œ (๋ฉฐ์น ์ด ๊ฑธ๋ฆฌ๋“  ์—ฐ์‡„ ํ—ˆ์šฉ)
71
 
@@ -77,7 +120,7 @@ PEAK_WINDOW = 252 # ๊ณ ์  ๊ธฐ์ค€ = ์ตœ๊ทผ N๊ฑฐ๋ž˜์ผ(252=1๋…„) ๋‚ด ์ตœ
77
  USE_TREND = True # ์ถ”์„ธํ•„ํ„ฐ ์ „์ฒด on/off (๊บผ์ง€๋ฉด ํ•„ํ„ฐ ์—†์Œ)
78
 
79
  # ๊ณตํ†ต ์ „์ œ: 60์„ ์ด 240์„  ๋ŒํŒŒ ํ›„ ์ด๋งŒํผ(๊ฑฐ๋ž˜์ผ) ์ง€๋‚˜์•ผ ๋งค๋„ ํ—ˆ์šฉ
80
- RUN_MIN_DAYS = 100 # 60์„ ์ด 240์„  ๋ŒํŒŒ ํ›„ ๊ฒฝ๊ณผ์ผ (๋ง‰ ๋ŒํŒŒ ์งํ›„=240์„  ๋„ˆ๋ฌด ๊ทผ์ฒ˜๋ผ ์˜๋ฏธ์—†์Œ)
81
 
82
  # 240์„  ์ถ”์ •: 240์ผ์น˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด(์ƒ์žฅ์ดˆ๊ธฐ ๋“ฑ) ์žˆ๋Š” ๋งŒํผ ์ตœ์žฅ ํ‰๊ท ์œผ๋กœ ๋Œ€์ฒด
83
  ESTIMATE_MA240 = True # True๋ฉด 240์ผ ๋ฏธ๋งŒ์ด์–ด๋„ ์ถ”์ •ํ•ด์„œ ํŒ์ • (๋งค๋„ ๋ผ์ธ์— [240์ถ”์ •] ํ‘œ์‹œ)
@@ -94,16 +137,20 @@ FLAT_MAX_60 = 0.00 # 60์„  ๊ธฐ์šธ๊ธฐ ์ƒํ•œ (โ‰ค0%=๊บพ์—ฌ ๋‚ด๋ ค๊ฐ€๊ฑฐ
94
  FLAT_MIN_120 = -9.0 # 120์„  ๊ธฐ์šธ๊ธฐ ํ•˜ํ•œ (-9=ํ•˜ํ•œ์—†์Œ)
95
  FLAT_MAX_120 = 0.05 # 120์„  ๊ธฐ์šธ๊ธฐ ์ƒํ•œ
96
  FLAT_MIN_240 = 0.00 # 240์„  ๊ธฐ์šธ๊ธฐ ํ•˜ํ•œ (0%=๋งˆ์ด๋„ˆ์Šค๋ฉด ๋„ˆ๋ฌด ๋Šฆ์Œ -> ์ œ์™ธ)
97
- FLAT_MAX_240 = 0.04 # 240์„  ๊ธฐ์šธ๊ธฐ ์ƒํ•œ
98
  FLAT_COMBINE = 'AND' # 'AND'(์„ธ ์„  ๋‹ค ๋งŒ์กฑ) / 'OR'(ํ•˜๋‚˜๋ผ๋„ ๋งŒ์กฑ)
99
 
100
- SHOW_STEPS = True # True๋ฉด ๋งค๋„๋งˆ๋‹ค [๋‹จ๊ณ„ ์ƒ์„ธ](flag/1์ฐจ/2์ฐจ ์–ธ์ œ ์–ด๋–ป๊ฒŒ) ์ถœ๋ ฅ
101
 
102
  LOOK_AHEAD = 200 # ๋งค๋„ ํ›„ ๊ด€์ฐฐ๊ธฐ๊ฐ„(๊ฑฐ๋ž˜์ผ)
103
  PROTECT_DROP = -0.10 # ๋งค๋„๊ฐ€ ๋Œ€๋น„ ์ด๋งŒํผ ๋” ๋น ์ง€๋ฉด '์ž˜ ํŒ ๊ฒƒ'
104
 
105
- TREND_MIN_DAYS = 60 # '240์˜ฌ': ์ตœ๊ทผ N๊ฑฐ๋ž˜์ผ ์—ฐ์† 240์„  ์œ„
106
- TREND_MIN_GAP = 0.05 # '240์˜ฌ': ๋งค๋„์ผ ์ข…๊ฐ€๊ฐ€ 240์„  +5% ์ด์ƒ
 
 
 
 
107
 
108
  ANALYZE_FROM = '2020-01-01' # ์ฑ„์ (๋งค๋„์ง‘๊ณ„) ์‹œ์ž‘์ผ โ€” ์ด๊ฑด '์–ธ์ œ๋ถ€ํ„ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณผ์ง€'์ผ ๋ฟ
109
  ANALYZE_TO = None # None=์˜ค๋Š˜(๋งค๋ฒˆ ํ˜ธ์ถœ์‹œ์  KST ๊ธฐ์ค€). ํŠน์ •์ผ ๊ณ ์ •ํ•˜๋ ค๋ฉด 'YYYY-MM-DD'.
@@ -359,10 +406,18 @@ def find_confirmed_sells(stock, index, dates, start_pos, ma60=None, ma240=None,
359
  tail = ' (๋‹ค์Œ: ์ „๋Ÿ‰๋งค๋„!)' if stage == SELL_STAGE - 1 else ''
360
  nm = f'{stage}๋‹จ๊ณ„ / ๋งค๋„๊นŒ์ง€ {SELL_STAGE}๋‹จ๊ณ„{tail}'
361
  cur = float(stock[last] / twpk[last] - 1) - ((float(index[last] / twip[last] - 1)) if twip[last] else 0.0)
362
- need = cur - (ref_div - MIN_DEEPEN) # >0 ์ด๋ฉด ์ด๋งŒํผ ๋” ๊นŠ์–ด์ ธ์•ผ ๋‹ค์Œ ๋‹จ๊ณ„
363
- note = (f'๋‹ค์Œ ๋‹จ๊ณ„๊นŒ์ง€ {need*100:.1f}%p ๋” ํ•˜๋ฝ ํ•„์š”' if need > 0
364
- else '์กฐ๊ฑด ์ถฉ์กฑ์ƒํƒœ(๋‹ค์Œ๋ด‰ ํ™•์ธ)')
365
- _step(last, f'[ํ˜„์žฌ] {nm}', cur, note)
 
 
 
 
 
 
 
 
366
  return sells, jumps, steps
367
 
368
 
@@ -381,10 +436,14 @@ def find_rs_a4_sells(stock, index, dates, start_pos, ma60=None, ma240=None,
381
  if arr is not None:
382
  flat_slopes[ln] = _smoothed_slope(arr, FLAT_SLOPE_DAYS, FLAT_SMOOTH)
383
 
384
- # RS์„ ๊ณผ ๊ทธ ์ด๋™ํ‰๊ท 
385
- rs = np.array([stock[t] / index[t] if index[t] else np.nan for t in range(n)])
386
- rs_ma = pd.Series(rs).rolling(RS_MA_PERIOD).mean().values
387
- rs_weak = np.array([(not np.isnan(rs_ma[t])) and rs[t] < rs_ma[t] for t in range(n)])
 
 
 
 
388
 
389
  sells = []; jumps = []; steps = []
390
  def _step(t, name, div, note=''):
@@ -396,46 +455,153 @@ def find_rs_a4_sells(stock, index, dates, start_pos, ma60=None, ma240=None,
396
  return True, ''
397
  return trend_ok(t, stock, ma240, ma60, ma120, flat_slopes)
398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  armed = False; armed_date = None; sold = False; weak_run = 0
 
400
  for t in range(n):
401
  div = (stock[t] / twpk[t] - 1) - ((index[t] / twip[t] - 1) if twip[t] else 0.0)
402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  if rs_weak[t]:
404
  weak_run += 1
405
  else:
406
- # RS๊ฐ€ ๋‹ค์‹œ ์‹œ์žฅ๋ณด๋‹ค ๊ฐ•ํ•ด์ง -> ๋ฌด์žฅ ํ•ด์ œ / ๋งค๋„์ƒํƒœ ๋ฆฌ์…‹
407
  if armed and t >= start_pos:
408
  _step(t, '๋ฌด์žฅ ํ•ด์ œ(RS ํšŒ๋ณต)', div)
409
- armed = False; weak_run = 0; sold = False
 
 
410
  continue
411
 
412
- # RS_PERSIST์ผ ์—ฐ์† ์•ฝํ•˜๋ฉด ๋ฌด์žฅ
413
- if not armed and weak_run >= RS_PERSIST:
414
- armed = True; armed_date = dates[t]
415
- rsd = (rs[t] / rs_ma[t] - 1) * 100
416
- _step(t, '๋ฌด์žฅ(RS ๋ฐ๋“œํฌ๋กœ์Šค)', div, f'RS {rsd:.1f}% < MA{RS_MA_PERIOD}')
 
 
 
 
 
 
 
 
 
 
 
 
 
417
 
418
  if armed and not sold:
419
- ok, why = (_gate(t) if RS_CONFIRM == 'A4' else (True, 'RS๋‹จ๋…'))
 
 
 
 
 
 
420
  if ok:
421
  if t >= start_pos:
422
  est = bool(ma240_est[t]) if ma240_est is not None else False
423
  sells.append({'i': t, 'price': float(stock[t]), 'div': div,
424
  'date': dates[t], 'trend': why,
425
  'via': f'RS๋ฌด์žฅ({armed_date})', 'est': est})
426
- _step(t, '์ „๋Ÿ‰๋งค๋„ โ˜…๋ฐœ๋™', div,
427
- f'RS๋ฌด์žฅ ํ›„ A-4ํ™•์ธ' if RS_CONFIRM == 'A4' else 'RS ๋ฐ๋“œํฌ๋กœ์Šค')
428
  sold = True
 
 
429
  else:
430
- pass # ๋ฌด์žฅ๋์ง€๋งŒ A-4 ์•„์ง -> ๋Œ€๊ธฐ (๋‹ค์Œ๋‚  ๋˜ ํ™•์ธ)
431
 
432
  # ํ˜„์žฌ ์ƒํƒœ
433
  last = n - 1
434
  if not sold:
435
  div = (stock[last] / twpk[last] - 1) - ((index[last] / twip[last] - 1) if twip[last] else 0.0)
436
  if armed:
437
- ok, why = (_gate(last) if RS_CONFIRM == 'A4' else (True, ''))
438
- note = ('A-4 ํ™•์ธ ๋Œ€๊ธฐ์ค‘ (' + why + ')') if RS_CONFIRM == 'A4' else 'RS๋‹จ๋…'
439
  _step(last, '[ํ˜„์žฌ] ๋ฌด์žฅ๋จ(๋งค๋„ ๋Œ€๊ธฐ)', div, note)
440
  else:
441
  _step(last, '[ํ˜„์žฌ] ๊ด€๋ง (RS ์•„์ง ์‹œ์žฅ๋ณด๋‹ค ์•ˆ ์•ฝํ•จ)', div, '')
@@ -549,8 +715,10 @@ def main():
549
  else:
550
  tf_s = "์ถ”์„ธํ•„ํ„ฐ=OFF"
551
  if ENGINE == 'RS_A4':
552
- rs_confirm = 'A-4ํ™•์ธ' if RS_CONFIRM == 'A4' else 'RS๋‹จ๋…'
553
- print(f"์—”์ง„=RS_A4 | RS์„ <MA{RS_MA_PERIOD} {RS_PERSIST}์ผ์—ฐ์† ๋ฌด์žฅ -> {rs_confirm} ์ฆ‰์‹œ๋งค๋„ | {tf_s}")
 
 
554
  else:
555
  div_s = f"์ด๊ฒฉ๊ธฐ์ค€={DIV_THRESHOLD*100:.0f}%(๊ณ ์ •)"
556
  jmp_s = f"ํ•˜๋ฃจ๊ธ‰๋ณ€({DAILY_JUMP*100:.0f}%p)์ ํ”„"
@@ -648,22 +816,23 @@ def main():
648
  else:
649
  print(f" {tk} {market} - ๋งค๋„ ์—†์Œ")
650
 
651
- # ---- ํ•˜๋ฃจ ๊ธ‰๋ณ€ ์ ํ”„(flagON/2์ฐจflagON ๋Œ€๊ธฐ ๊ฑด๋„ˆ๋œ€) ๋ฐœ๋™ ๋‚ด์—ญ ----
652
- print("\n" + "=" * 88)
653
- print(f"[ํ•˜๋ฃจ ๊ธ‰๋ณ€ ์ ํ”„ ๋ฐœ๋™ ๋‚ด์—ญ] (์ „์ผ ์ด๊ฒฉ ๋Œ€๋น„ {DAILY_JUMP*100:.0f}%p+ ๊ธ‰๋ฝ -> ๋Œ€๊ธฐ๋‹จ๊ณ„ ๊ฑด๋„ˆ๋œ€)")
654
- print("=" * 88)
655
- if not all_jumps:
656
- print(" ๋ฐœ๋™ ์—†์Œ")
657
- else:
658
- n_skip = sum(1 for _, _, j in all_jumps if j['skip'])
659
- print(f" ์ด {len(all_jumps)}ํšŒ ๋ฐœ๋™ (์ด ์ค‘ '๋Œ€๊ธฐ ๊ฑด๋„ˆ๋œ€(SKIP)' {n_skip}ํšŒ)\n")
660
- print(f" {'์ข…๋ชฉ':<8}{'์‹œ์žฅ':<7}{'๋‚ ์งœ':<12}{'ํ•˜๋ฃจ๋‚™ํญ':>8}{'์ด๊ฒฉ':>8} ๋‹จ๊ณ„์ „ํ™˜")
661
- print(" " + "-" * 70)
662
- for tk, market, j in sorted(all_jumps, key=lambda z: z[2]['date']):
663
- mark = ' โ˜…๊ฑด๋„ˆ๋œ€' if j['skip'] else ''
664
- print(f" {tk:<8}{market:<7}{j['date']:<12}"
665
- f"{j['drop']*100:>7.1f}%{j['div']*100:>7.1f}% "
666
- f"{j['from']} -> {j['to']}{mark}")
 
667
 
668
 
669
  if __name__ == '__main__':
 
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
+ # '005250',
45
  ],
46
  'KOSDAQ': [
47
+ # '247540', '086520', '196170', '028300', '348370',
48
+ # '058470', '068760', '263750', '035900', '277810',
49
+ # '041510', '035760', '112040', '293490', '145020',
50
+ # '039030', '240810', '357780',
51
+ # '403870',
52
+ # '121600',
53
+ # '399720',
54
+ # '277810',
55
+ # '257720',
56
+ # '017510',
57
+ # '389020',
58
+ # '195940',
59
+ '251370',
60
  ],
61
  }
62
 
 
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
  # ๋งค๋„ ์—”์ง„ ์„ ํƒ
 
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
  # CONFIRM_WINDOW ์ œ๊ฑฐ๋จ: 1์ฐจ->2์ฐจ ์‚ฌ์ด ์ผ์ˆ˜ ์ œํ•œ ์—†์Œ (๋ฉฐ์น ์ด ๊ฑธ๋ฆฌ๋“  ์—ฐ์‡„ ํ—ˆ์šฉ)
114
 
 
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์ถ”์ •] ํ‘œ์‹œ)
 
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 = False # True๋ฉด ๋งค๋„๋งˆ๋‹ค [๋‹จ๊ณ„ ์ƒ์„ธ](flag/1์ฐจ/2์ฐจ ์–ธ์ œ ์–ด๋–ป๊ฒŒ) ์ถœ๋ ฅ
144
 
145
  LOOK_AHEAD = 200 # ๋งค๋„ ํ›„ ๊ด€์ฐฐ๊ธฐ๊ฐ„(๊ฑฐ๋ž˜์ผ)
146
  PROTECT_DROP = -0.10 # ๋งค๋„๊ฐ€ ๋Œ€๋น„ ์ด๋งŒํผ ๋” ๋น ์ง€๋ฉด '์ž˜ ํŒ ๊ฒƒ'
147
 
148
+ TREND_MIN_DAYS = 60 # '240์˜ฌ'(๊ฐ•ํ•œ ์ƒ์Šน์ถ”์„ธ): ์ตœ๊ทผ N๊ฑฐ๋ž˜์ผ ์—ฐ์† 240์„  ์œ„
149
+ TREND_MIN_GAP = 0.05 # '240์˜ฌ': ์ข…๊ฐ€๊ฐ€ 240์„  +5% ์ด์ƒ
150
+
151
+ # ๊ฐ•ํ•œ ์ƒ์Šน์ถ”์„ธ(240์˜ฌ) ์ค‘์ด๋ฉด ๋งค๋„ ๊ธˆ์ง€ โ€” ๊ธ‰๋“ฑ์ฃผ๊ฐ€ ์ƒ์Šน ํ•œ๋ณตํŒ์—์„œ 60์„  ์ž ๊น ๊นจ๋Š”
152
+ # '๋ˆŒ๋ฆผ'์„ ๋ถ•๊ดด๋กœ ์˜ค์ธํ•˜๋Š” false alarm ๋ฐฉ์ง€. (240 ๊ธฐ์šธ๊ธฐ๋ณด๋‹ค ํŠผํŠผ: 60์ผ์—ฐ์† 240์œ„+5%๊ฐญ)
153
+ NO_SELL_STRONG_UPTREND = False # True๋ฉด in_240_run(๊ฐ•ํ•œ์ƒ์Šน) ์‹œ ๋งค๋„ ๋ณด๋ฅ˜
154
 
155
  ANALYZE_FROM = '2020-01-01' # ์ฑ„์ (๋งค๋„์ง‘๊ณ„) ์‹œ์ž‘์ผ โ€” ์ด๊ฑด '์–ธ์ œ๋ถ€ํ„ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณผ์ง€'์ผ ๋ฟ
156
  ANALYZE_TO = None # None=์˜ค๋Š˜(๋งค๋ฒˆ ํ˜ธ์ถœ์‹œ์  KST ๊ธฐ์ค€). ํŠน์ •์ผ ๊ณ ์ •ํ•˜๋ ค๋ฉด 'YYYY-MM-DD'.
 
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
 
 
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=''):
 
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
+ _step(t, f'์žฌ๋ฌด์žฅ ๊ฐ€๋Šฅ({RELOCK_LINE}์„  ํšŒ๋ณต+{t-lock_i}์ผ๊ฒฝ๊ณผ=์ƒˆ ์‚ฌ์ดํด)', div)
544
+ else:
545
+ continue # ์ž ๊ฒจ์žˆ์œผ๋ฉด ์นจ๋ฌต
546
+
547
  if rs_weak[t]:
548
  weak_run += 1
549
  else:
550
+ # RS๊ฐ€ ๋‹ค์‹œ ์‹œ์žฅ๋ณด๋‹ค ๊ฐ•ํ•ด์ง -> ๋ฌด์žฅ ํ•ด์ œ
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
+ _step(t, '๋งค๋„ ๋ณด๋ฅ˜(๊ฐ•ํ•œ ์ƒ์Šน์ถ”์„ธ=240์˜ฌ, ๋ˆŒ๋ฆผ)', div,
584
+ f'์ข…๊ฐ€ {stock[t]:,.0f} > 240์„ +{TREND_MIN_GAP*100:.0f}%, {TREND_MIN_DAYS}์ผ์—ฐ์† 240์œ„')
585
  if ok:
586
  if t >= start_pos:
587
  est = bool(ma240_est[t]) if ma240_est is not None else False
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
+ if SELL_ONCE:
594
+ locked = True; lock_i = t # ํ•œ ๋ฒˆ ํŒ”๋ฉด ์ž ๊ธˆ(ํ•ด์ œ์„ +๊ธฐ๊ฐ„ ๋‘˜ ๋‹ค ์ถฉ์กฑ๊นŒ์ง€)
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
+ note = f'ํ™•์ธ[{RS_CONFIRM}] ๋Œ€๊ธฐ์ค‘ ({why})'
605
  _step(last, '[ํ˜„์žฌ] ๋ฌด์žฅ๋จ(๋งค๋„ ๋Œ€๊ธฐ)', div, note)
606
  else:
607
  _step(last, '[ํ˜„์žฌ] ๊ด€๋ง (RS ์•„์ง ์‹œ์žฅ๋ณด๋‹ค ์•ˆ ์•ฝํ•จ)', div, '')
 
715
  else:
716
  tf_s = "์ถ”์„ธํ•„ํ„ฐ=OFF"
717
  if ENGINE == 'RS_A4':
718
+ rs_confirm = {'A4': 'A-4ํ‰ํ‰', 'MA60': '์ข…๊ฐ€<60์„ ', 'MA120': '์ข…๊ฐ€<120์„ ',
719
+ 'NONE': 'RS๋‹จ๋…'}.get(RS_CONFIRM, RS_CONFIRM)
720
+ print(f"์—”์ง„=RS_A4 | RS(EMA{RS_SMOOTH})<MA{RS_MA_PERIOD}ร—(1-{RS_BUFFER*100:.0f}%) "
721
+ f"{RS_PERSIST}์ผ์—ฐ์† ๋ฌด์žฅ -> ํ™•์ธ[{rs_confirm}] ์ฆ‰์‹œ๋งค๋„ | {tf_s}")
722
  else:
723
  div_s = f"์ด๊ฒฉ๊ธฐ์ค€={DIV_THRESHOLD*100:.0f}%(๊ณ ์ •)"
724
  jmp_s = f"ํ•˜๋ฃจ๊ธ‰๋ณ€({DAILY_JUMP*100:.0f}%p)์ ํ”„"
 
816
  else:
817
  print(f" {tk} {market} - ๋งค๋„ ์—†์Œ")
818
 
819
+ # ---- ํ•˜๋ฃจ ๊ธ‰๋ณ€ ์ ํ”„ ๋ฐœ๋™ ๋‚ด์—ญ (STAGE ์—”์ง„ ์ „์šฉ. RS_A4๋Š” ๋‹จ๊ณ„๊ฐœ๋… ์—†์–ด์„œ ์ƒ๋žต) ----
820
+ if ENGINE != 'RS_A4':
821
+ print("\n" + "=" * 88)
822
+ print(f"[ํ•˜๋ฃจ ๊ธ‰๋ณ€ ์ ํ”„ ๋ฐœ๋™ ๋‚ด์—ญ] (์ „์ผ ์ด๊ฒฉ ๋Œ€๋น„ {DAILY_JUMP*100:.0f}%p+ ๊ธ‰๋ฝ -> ๋Œ€๊ธฐ๋‹จ๊ณ„ ๊ฑด๋„ˆ๋œ€)")
823
+ print("=" * 88)
824
+ if not all_jumps:
825
+ print(" ๋ฐœ๋™ ์—†์Œ")
826
+ else:
827
+ n_skip = sum(1 for _, _, j in all_jumps if j['skip'])
828
+ print(f" ์ด {len(all_jumps)}ํšŒ ๋ฐœ๋™ (์ด ์ค‘ '๋Œ€๊ธฐ ๊ฑด๋„ˆ๋œ€(SKIP)' {n_skip}ํšŒ)\n")
829
+ print(f" {'์ข…๋ชฉ':<8}{'์‹œ์žฅ':<7}{'๋‚ ์งœ':<12}{'ํ•˜๋ฃจ๋‚™ํญ':>8}{'์ด๊ฒฉ':>8} ๋‹จ๊ณ„์ „ํ™˜")
830
+ print(" " + "-" * 70)
831
+ for tk, market, j in sorted(all_jumps, key=lambda z: z[2]['date']):
832
+ mark = ' โ˜…๊ฑด๋„ˆ๋œ€' if j['skip'] else ''
833
+ print(f" {tk:<8}{market:<7}{j['date']:<12}"
834
+ f"{j['drop']*100:>7.1f}%{j['div']*100:>7.1f}% "
835
+ f"{j['from']} -> {j['to']}{mark}")
836
 
837
 
838
  if __name__ == '__main__':