nextrade-engine 0.7.0__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {nextrade_engine-0.7.0/nextrade/nextrade_engine.egg-info → nextrade_engine-0.8.0}/PKG-INFO +2 -1
  2. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/core/regime.py +2 -2
  3. nextrade_engine-0.8.0/nextrade/nextrade/indicators/adaptive.py +99 -0
  4. nextrade_engine-0.8.0/nextrade/nextrade/indicators/confluence.py +91 -0
  5. nextrade_engine-0.8.0/nextrade/nextrade/indicators/pattern.py +82 -0
  6. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0/nextrade/nextrade_engine.egg-info}/PKG-INFO +2 -1
  7. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade_engine.egg-info/requires.txt +1 -0
  8. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/pyproject.toml +3 -2
  9. nextrade_engine-0.7.0/nextrade/nextrade/indicators/adaptive.py +0 -73
  10. nextrade_engine-0.7.0/nextrade/nextrade/indicators/confluence.py +0 -61
  11. nextrade_engine-0.7.0/nextrade/nextrade/indicators/pattern.py +0 -76
  12. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/LICENSE +0 -0
  13. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/README.md +0 -0
  14. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/__init__.py +0 -0
  15. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/backtest/__init__.py +0 -0
  16. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/backtest/engine.py +0 -0
  17. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/core/__init__.py +0 -0
  18. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/core/brain.py +0 -0
  19. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/data/__init__.py +0 -0
  20. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/data/fetcher.py +0 -0
  21. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/indicators/__init__.py +0 -0
  22. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/lang.py +0 -0
  23. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/ml/__init__.py +0 -0
  24. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/ml/features.py +0 -0
  25. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/ml/model.py +0 -0
  26. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/utils/__init__.py +0 -0
  27. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade/utils/terminal_ui.py +0 -0
  28. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade_engine.egg-info/SOURCES.txt +0 -0
  29. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade_engine.egg-info/dependency_links.txt +0 -0
  30. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/nextrade/nextrade_engine.egg-info/top_level.txt +0 -0
  31. {nextrade_engine-0.7.0 → nextrade_engine-0.8.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextrade-engine
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: AI Trading Engine — Ringan, Multi-Market, Bisa di HP
5
5
  Author-email: NexTrade <wafiqrazy035@gmail.com>
6
6
  License-Expression: MIT
@@ -14,6 +14,7 @@ License-File: LICENSE
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: yfinance
16
16
  Requires-Dist: scikit-learn
17
+ Requires-Dist: nexarray
17
18
  Dynamic: license-file
18
19
 
19
20
  # NexTrade 🧠
@@ -29,7 +29,7 @@ def detect_regime(close: np.ndarray, period: int = 20) -> dict:
29
29
  trend_strength = abs(slope) / (np.std(window) + 1e-9)
30
30
 
31
31
  # 2. Volatility — ATR proxy pakai std returns
32
- returns = np.diff(window) / (window[:-1] + 1e-9)
32
+ window_arr = np.array(window); returns = np.diff(window_arr) / (window_arr[:-1] + 1e-9)
33
33
  volatility = np.std(returns) * 100 # dalam persen
34
34
 
35
35
  # 3. Range ratio — seberapa besar range vs body rata-rata
@@ -60,4 +60,4 @@ def detect_regime_series(close: np.ndarray, period: int = 20) -> np.ndarray:
60
60
  for i in range(period, len(close)):
61
61
  r = detect_regime(close[max(0, i-period):i+1], period)
62
62
  regimes[i] = r["regime"]
63
- return regimes
63
+ return regimes
@@ -0,0 +1,99 @@
1
+ """
2
+ NexTrade v0.8.0 — Adaptive Indicators
3
+ Menggunakan NexArray bukan NumPy!
4
+ """
5
+ import sys
6
+ sys.path.insert(0, '/home/claude/nexarray')
7
+
8
+ try:
9
+ import nexarray as nx
10
+ HAS_NX = True
11
+ except ImportError:
12
+ HAS_NX = False
13
+
14
+ from nextrade.core.regime import detect_regime, MarketRegime
15
+
16
+ def _to_list(data) -> list:
17
+ if HAS_NX and hasattr(data, '_data'):
18
+ return list(data._data)
19
+ try:
20
+ return list(data)
21
+ except:
22
+ return data
23
+
24
+ def adaptive_period(close, base: int = 14,
25
+ min_p: int = 5, max_p: int = 30) -> int:
26
+ close_list = _to_list(close)
27
+ r = detect_regime(close_list)
28
+ vol = r["volatility"]
29
+ if vol > 2.0: return min_p
30
+ elif vol < 0.5: return max_p
31
+ ratio = (vol - 0.5) / (2.0 - 0.5)
32
+ return int(max_p - ratio * (max_p - min_p))
33
+
34
+ def adaptive_rsi(close) -> float:
35
+ close_list = _to_list(close)
36
+ period = adaptive_period(close_list)
37
+ if len(close_list) < period + 1:
38
+ return 50.0
39
+
40
+ if HAS_NX:
41
+ # Pakai NexArray!
42
+ arr = nx.array(close_list[-period-1:])
43
+ delta = nx.diff(arr)
44
+ gain = nx.where(delta > 0, delta, nx.array([0.0]*delta.size))
45
+ loss = nx.where(delta < 0, delta * -1, nx.array([0.0]*delta.size))
46
+ avg_g = gain.mean() + 1e-9
47
+ avg_l = loss.mean() + 1e-9
48
+ rs = avg_g / avg_l
49
+ return round(float(100 - 100 / (1 + rs)), 2)
50
+ else:
51
+ seg = close_list[-period-1:]
52
+ delta = [seg[i]-seg[i-1] for i in range(1, len(seg))]
53
+ gain = [max(d, 0) for d in delta]
54
+ loss = [max(-d, 0) for d in delta]
55
+ avg_g = sum(gain)/len(gain) + 1e-9
56
+ avg_l = sum(loss)/len(loss) + 1e-9
57
+ return round(100 - 100/(1 + avg_g/avg_l), 2)
58
+
59
+ def adaptive_ema(close) -> float:
60
+ close_list = _to_list(close)
61
+ period = adaptive_period(close_list)
62
+ if len(close_list) < period:
63
+ return float(close_list[-1])
64
+ k = 2 / (period + 1)
65
+ ema = float(close_list[-period])
66
+ for price in close_list[-period+1:]:
67
+ ema = price * k + ema * (1 - k)
68
+ return round(ema, 6)
69
+
70
+ def momentum_score(close) -> float:
71
+ close_list = _to_list(close)
72
+
73
+ if HAS_NX:
74
+ arr = nx.array(close_list)
75
+ rsi = adaptive_rsi(close_list)
76
+ ema = adaptive_ema(close_list)
77
+ price = close_list[-1]
78
+ pct = (price - ema) / (ema + 1e-9) * 100
79
+ ema_sc = min(max(50 + pct * 10, 0), 100)
80
+ period = max(5, len(close_list) // 10)
81
+ if len(close_list) > period:
82
+ roc = (price - close_list[-period]) / (close_list[-period]+1e-9)*100
83
+ roc_sc = min(max(50 + roc * 5, 0), 100)
84
+ else:
85
+ roc_sc = 50.0
86
+ score = rsi*0.4 + ema_sc*0.3 + roc_sc*0.3
87
+ return round(float(score), 2)
88
+ else:
89
+ rsi = adaptive_rsi(close_list)
90
+ ema = adaptive_ema(close_list)
91
+ price = close_list[-1]
92
+ pct = (price - ema) / (ema + 1e-9) * 100
93
+ ema_sc = min(max(50 + pct*10, 0), 100)
94
+ period = max(5, len(close_list)//10)
95
+ roc_sc = 50.0
96
+ if len(close_list) > period:
97
+ roc = (price-close_list[-period])/(close_list[-period]+1e-9)*100
98
+ roc_sc = min(max(50+roc*5, 0), 100)
99
+ return round(rsi*0.4 + ema_sc*0.3 + roc_sc*0.3, 2)
@@ -0,0 +1,91 @@
1
+ """
2
+ NexTrade v0.8.0 — Confluence Score
3
+ Gabungkan semua indikator — NexArray powered!
4
+ """
5
+ import sys
6
+ sys.path.insert(0, '/home/claude/nexarray')
7
+
8
+ try:
9
+ import nexarray as nx
10
+ HAS_NX = True
11
+ except ImportError:
12
+ HAS_NX = False
13
+
14
+ from nextrade.core.regime import detect_regime, MarketRegime
15
+ from nextrade.indicators.adaptive import adaptive_rsi, momentum_score
16
+ from nextrade.indicators.pattern import pattern_score
17
+
18
+ def _to_list(data) -> list:
19
+ if hasattr(data, '_data'): return list(data._data)
20
+ try: return list(data)
21
+ except: return data
22
+
23
+ def confluence_score(opens, highs, lows, closes) -> dict:
24
+ cl = _to_list(closes)
25
+
26
+ # Regime detection
27
+ regime_data = detect_regime(cl)
28
+ regime = regime_data["regime"]
29
+
30
+ # Momentum & RSI
31
+ mom = momentum_score(cl)
32
+ rsi = adaptive_rsi(cl)
33
+
34
+ # NexArray trading indicators
35
+ if HAS_NX:
36
+ nx_c = nx.array(cl)
37
+ grav = nx.gravity(nx_c, 20)
38
+ snap = nx.snap(nx_c, 5)
39
+ wave = nx.momentum_wave(nx_c, 5, 20)
40
+ grav_v = float(grav._data[-1])
41
+ snap_v = float(snap._data[-1])
42
+ wave_v = float(wave._data[-1])
43
+ else:
44
+ grav_v = snap_v = wave_v = 0.0
45
+
46
+ # Pattern
47
+ pat = pattern_score(opens, highs, lows, closes)
48
+
49
+ # Bobot berdasarkan regime
50
+ if regime == MarketRegime.TRENDING:
51
+ w_mom, w_pat, w_rsi, w_nx = 0.4, 0.25, 0.15, 0.20
52
+ elif regime == MarketRegime.RANGING:
53
+ w_mom, w_pat, w_rsi, w_nx = 0.15, 0.45, 0.25, 0.15
54
+ else:
55
+ w_mom, w_pat, w_rsi, w_nx = 0.25, 0.35, 0.25, 0.15
56
+
57
+ # NexArray score
58
+ nx_score = 50.0
59
+ if HAS_NX:
60
+ nx_score = 50.0
61
+ nx_score -= grav_v * 10 # gravity — jauh dari mean = reversal
62
+ nx_score += wave_v * 0.5 # momentum wave
63
+ nx_score += snap_v * 5 # snap detection
64
+ nx_score = min(max(nx_score, 0), 100)
65
+
66
+ raw = (mom * w_mom + pat["total"] * w_pat +
67
+ rsi * w_rsi + nx_score * w_nx)
68
+ confidence = round(float(min(max(raw, 0), 100)), 2)
69
+
70
+ if confidence >= 62 and pat["direction"] != "bear":
71
+ signal = "BUY"
72
+ elif confidence <= 38 or pat["direction"] == "bear":
73
+ signal = "SELL"
74
+ else:
75
+ signal = "HOLD"
76
+
77
+ return {
78
+ "signal" : signal,
79
+ "confidence": confidence,
80
+ "regime" : regime,
81
+ "details" : {
82
+ "momentum" : round(mom, 2),
83
+ "rsi" : round(rsi, 2),
84
+ "pattern" : pat,
85
+ "regime" : regime_data,
86
+ "nx_gravity": round(grav_v, 4),
87
+ "nx_snap" : snap_v,
88
+ "nx_wave" : round(wave_v, 4),
89
+ "nx_score" : round(nx_score, 2),
90
+ }
91
+ }
@@ -0,0 +1,82 @@
1
+ """
2
+ NexTrade v0.8.0 — Pattern Scorer
3
+ Menggunakan NexArray bukan NumPy!
4
+ """
5
+ import sys
6
+ sys.path.insert(0, '/home/claude/nexarray')
7
+
8
+ try:
9
+ import nexarray as nx
10
+ HAS_NX = True
11
+ except ImportError:
12
+ HAS_NX = False
13
+
14
+ def _f(x) -> float:
15
+ return float(x._data[-1] if hasattr(x,'_data') else x)
16
+
17
+ def hammer_score(open_: float, high: float,
18
+ low: float, close: float) -> float:
19
+ body = abs(close - open_)
20
+ rng = high - low + 1e-9
21
+ lower = min(open_, close) - low
22
+ upper = high - max(open_, close)
23
+ score = 0.0
24
+ score += min((lower/rng)/0.6, 1.0) * 50
25
+ score += max(1 - (body/rng)/0.3, 0) * 30
26
+ score += max(1 - upper/(rng*0.1+1e-9), 0) * 20
27
+ return round(min(score, 100.0), 2)
28
+
29
+ def engulfing_score(prev_o, prev_c, curr_o, curr_c) -> tuple:
30
+ pb = abs(float(prev_c)-float(prev_o))
31
+ cb = abs(float(curr_c)-float(curr_o))
32
+ if pb == 0: return 0.0, "none"
33
+ ratio = cb / (pb + 1e-9)
34
+ is_bull = prev_c < prev_o and curr_c > curr_o and curr_o < prev_c and curr_c > prev_o
35
+ is_bear = prev_c > prev_o and curr_c < curr_o and curr_o > prev_c and curr_c < prev_o
36
+ if not (is_bull or is_bear): return 0.0, "none"
37
+ score = min(ratio * 60, 80) + 20
38
+ return round(min(score, 100.0), 2), "bull" if is_bull else "bear"
39
+
40
+ def pattern_score(opens, highs, lows, closes) -> dict:
41
+ def _get(arr, idx):
42
+ if hasattr(arr, '_data'): return float(arr._data[idx])
43
+ return float(arr[idx])
44
+
45
+ n = len(closes) if not hasattr(closes,'_data') else closes.size
46
+ if n < 2:
47
+ return {"total":50.0,"hammer":0.0,"engulfing":0.0,"direction":"neutral"}
48
+
49
+ o = _get(opens, -1)
50
+ h = _get(highs, -1)
51
+ l = _get(lows, -1)
52
+ c = _get(closes, -1)
53
+ po = _get(opens, -2)
54
+ pc = _get(closes, -2)
55
+
56
+ if HAS_NX:
57
+ # Pakai NexArray peak/valley untuk context
58
+ c_arr = nx.array(list(closes._data) if hasattr(closes,'_data') else list(closes))
59
+ peaks = nx.peak(c_arr, order=2)
60
+ valleys= nx.valley(c_arr, order=2)
61
+ at_peak = float(peaks._data[-3]) == 1.0
62
+ at_valley = float(valleys._data[-3]) == 1.0
63
+ else:
64
+ at_peak = at_valley = False
65
+
66
+ h_score = hammer_score(o, h, l, c)
67
+ e_score, e_dir = engulfing_score(po, pc, o, c)
68
+
69
+ if e_dir == "bull" or (h_score > 50 and c > o):
70
+ direction = "bull"
71
+ elif e_dir == "bear" or (h_score > 50 and c < o):
72
+ direction = "bear"
73
+ else:
74
+ direction = "neutral"
75
+
76
+ # Boost score kalau di peak/valley
77
+ total = max(h_score, e_score)
78
+ if at_valley and direction == "bull": total = min(total * 1.2, 100)
79
+ if at_peak and direction == "bear": total = min(total * 1.2, 100)
80
+
81
+ return {"total": total, "hammer": h_score,
82
+ "engulfing": e_score, "direction": direction}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextrade-engine
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: AI Trading Engine — Ringan, Multi-Market, Bisa di HP
5
5
  Author-email: NexTrade <wafiqrazy035@gmail.com>
6
6
  License-Expression: MIT
@@ -14,6 +14,7 @@ License-File: LICENSE
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: yfinance
16
16
  Requires-Dist: scikit-learn
17
+ Requires-Dist: nexarray
17
18
  Dynamic: license-file
18
19
 
19
20
  # NexTrade 🧠
@@ -1,3 +1,4 @@
1
1
  numpy
2
2
  yfinance
3
3
  scikit-learn
4
+ nexarray
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nextrade-engine"
7
- version = "0.7.0"
7
+ version = "0.8.0"
8
8
  description = "AI Trading Engine — Ringan, Multi-Market, Bisa di HP"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -20,8 +20,9 @@ classifiers = [
20
20
  requires-python = ">=3.8"
21
21
  dependencies = [
22
22
  "numpy",
23
- "yfinance",
23
+ "yfinance",
24
24
  "scikit-learn",
25
+ "nexarray",
25
26
  ]
26
27
 
27
28
  [tool.setuptools]
@@ -1,73 +0,0 @@
1
- """
2
- NexTrade — Adaptive Indicators
3
- Indikator khas AI yang menyesuaikan diri dengan kondisi pasar.
4
- """
5
- import numpy as np
6
- from nextrade.core.regime import detect_regime, MarketRegime
7
-
8
- def adaptive_period(close: np.ndarray, base: int = 14,
9
- min_p: int = 5, max_p: int = 30) -> int:
10
- """Hitung periode optimal berdasarkan volatilitas pasar saat ini."""
11
- r = detect_regime(close)
12
- vol = r["volatility"]
13
- # Volatile → periode pendek (lebih responsif)
14
- # Tenang → periode panjang (lebih halus)
15
- if vol > 2.0:
16
- return min_p
17
- elif vol < 0.5:
18
- return max_p
19
- else:
20
- # Interpolasi linear
21
- ratio = (vol - 0.5) / (2.0 - 0.5)
22
- return int(max_p - ratio * (max_p - min_p))
23
-
24
- def adaptive_rsi(close: np.ndarray) -> float:
25
- """RSI dengan periode adaptif berdasarkan volatilitas."""
26
- period = adaptive_period(close)
27
- if len(close) < period + 1:
28
- return 50.0
29
- delta = np.diff(close[-period-1:])
30
- gain = np.where(delta > 0, delta, 0.0)
31
- loss = np.where(delta < 0, -delta, 0.0)
32
- avg_gain = np.mean(gain) + 1e-9
33
- avg_loss = np.mean(loss) + 1e-9
34
- rs = avg_gain / avg_loss
35
- return round(float(100 - 100 / (1 + rs)), 2)
36
-
37
- def adaptive_ema(close: np.ndarray) -> float:
38
- """EMA dengan periode adaptif."""
39
- period = adaptive_period(close)
40
- if len(close) < period:
41
- return float(close[-1])
42
- k = 2 / (period + 1)
43
- ema = float(close[-period])
44
- for price in close[-period+1:]:
45
- ema = price * k + ema * (1 - k)
46
- return round(ema, 6)
47
-
48
- def momentum_score(close: np.ndarray) -> float:
49
- """
50
- Skor momentum 0-100 berdasarkan:
51
- - Adaptive RSI
52
- - Posisi harga vs EMA
53
- - Kecepatan pergerakan (Rate of Change)
54
- """
55
- rsi = adaptive_rsi(close)
56
- ema = adaptive_ema(close)
57
- price = float(close[-1])
58
-
59
- # Price vs EMA score (0-100)
60
- pct_from_ema = (price - ema) / (ema + 1e-9) * 100
61
- ema_score = min(max(50 + pct_from_ema * 10, 0), 100)
62
-
63
- # Rate of Change score
64
- period = max(5, len(close) // 10)
65
- if len(close) > period:
66
- roc = (price - float(close[-period])) / (float(close[-period]) + 1e-9) * 100
67
- roc_score = min(max(50 + roc * 5, 0), 100)
68
- else:
69
- roc_score = 50.0
70
-
71
- # Gabungkan: RSI 40% + EMA 30% + ROC 30%
72
- score = rsi * 0.4 + ema_score * 0.3 + roc_score * 0.3
73
- return round(float(score), 2)
@@ -1,61 +0,0 @@
1
- """
2
- NexTrade — Confluence Score
3
- Gabungkan semua indikator AI menjadi 1 skor sinyal final.
4
- """
5
- import numpy as np
6
- from nextrade.core.regime import detect_regime, MarketRegime
7
- from nextrade.indicators.adaptive import adaptive_rsi, momentum_score
8
- from nextrade.indicators.pattern import pattern_score
9
-
10
- def confluence_score(opens: np.ndarray, highs: np.ndarray,
11
- lows: np.ndarray, closes: np.ndarray) -> dict:
12
- """
13
- Hitung confluence score dari semua indikator AI.
14
- Returns: { signal, confidence, regime, details }
15
- """
16
- close = closes
17
-
18
- # Layer 1: Regime
19
- regime_data = detect_regime(close)
20
- regime = regime_data["regime"]
21
-
22
- # Layer 2: Momentum
23
- mom = momentum_score(close)
24
- rsi = adaptive_rsi(close)
25
-
26
- # Layer 3: Pattern
27
- pat = pattern_score(opens, highs, lows, closes)
28
-
29
- # Bobot berdasarkan regime
30
- if regime == MarketRegime.TRENDING:
31
- w_mom, w_pat, w_rsi = 0.5, 0.3, 0.2
32
- elif regime == MarketRegime.RANGING:
33
- w_mom, w_pat, w_rsi = 0.2, 0.5, 0.3
34
- else: # VOLATILE
35
- w_mom, w_pat, w_rsi = 0.3, 0.4, 0.3
36
-
37
- # Normalisasi RSI ke 0-100 bull/bear
38
- rsi_score = rsi # sudah 0-100, > 50 = bullish
39
-
40
- raw = mom * w_mom + pat["total"] * w_pat + rsi_score * w_rsi
41
- confidence = round(float(min(max(raw, 0), 100)), 2)
42
-
43
- # Tentukan sinyal
44
- if confidence >= 62 and pat["direction"] != "bear":
45
- signal = "BUY"
46
- elif confidence <= 38 or pat["direction"] == "bear":
47
- signal = "SELL"
48
- else:
49
- signal = "HOLD"
50
-
51
- return {
52
- "signal" : signal,
53
- "confidence": confidence,
54
- "regime" : regime,
55
- "details" : {
56
- "momentum" : round(mom, 2),
57
- "rsi" : round(rsi, 2),
58
- "pattern" : pat,
59
- "regime" : regime_data,
60
- }
61
- }
@@ -1,76 +0,0 @@
1
- """
2
- NexTrade — Pattern Scorer
3
- Beri skor kekuatan pola candlestick, bukan hanya deteksi ya/tidak.
4
- """
5
- import numpy as np
6
-
7
- def _body(o, c): return abs(c - o)
8
- def _upper(o, c, h): return h - max(o, c)
9
- def _lower(o, c, l): return min(o, c) - l
10
- def _range(h, l): return h - l + 1e-9
11
-
12
- def hammer_score(open_: float, high: float, low: float, close: float) -> float:
13
- """Skor pola hammer / pin bar bawah (0-100)."""
14
- body = _body(open_, close)
15
- lower = _lower(open_, close, low)
16
- upper = _upper(open_, close, high)
17
- rng = _range(high, low)
18
- if rng == 0: return 0.0
19
- lower_ratio = lower / rng
20
- body_ratio = body / rng
21
- # Hammer ideal: lower > 60% range, body < 30%, upper < 10%
22
- score = 0.0
23
- score += min(lower_ratio / 0.6, 1.0) * 50 # lower shadow besar
24
- score += max(1 - body_ratio / 0.3, 0) * 30 # body kecil
25
- score += max(1 - upper / (rng * 0.1 + 1e-9), 0) * 20 # upper shadow kecil
26
- return round(min(score, 100.0), 2)
27
-
28
- def engulfing_score(prev_o: float, prev_c: float,
29
- curr_o: float, curr_c: float) -> tuple:
30
- """
31
- Skor pola engulfing bullish / bearish.
32
- Returns (score, direction) — direction: 'bull' atau 'bear'
33
- """
34
- prev_body = _body(prev_o, prev_c)
35
- curr_body = _body(curr_o, curr_c)
36
- if prev_body == 0: return 0.0, "none"
37
-
38
- ratio = curr_body / (prev_body + 1e-9)
39
- is_bull = prev_c < prev_o and curr_c > curr_o and curr_o < prev_c and curr_c > prev_o
40
- is_bear = prev_c > prev_o and curr_c < curr_o and curr_o > prev_c and curr_c < prev_o
41
-
42
- if not (is_bull or is_bear): return 0.0, "none"
43
- score = min(ratio * 60, 80) + 20 # minimum 20 kalau valid
44
- direction = "bull" if is_bull else "bear"
45
- return round(min(score, 100.0), 2), direction
46
-
47
- def pattern_score(opens: np.ndarray, highs: np.ndarray,
48
- lows: np.ndarray, closes: np.ndarray) -> dict:
49
- """
50
- Gabungan skor semua pola pada candle terbaru.
51
- Returns dict dengan skor tiap pola dan skor total.
52
- """
53
- if len(closes) < 2:
54
- return {"total": 50.0, "hammer": 0.0, "engulfing": 0.0, "direction": "neutral"}
55
-
56
- o, h, l, c = opens[-1], highs[-1], lows[-1], closes[-1]
57
- po, pc = opens[-2], closes[-2]
58
-
59
- h_score = hammer_score(o, h, l, c)
60
- e_score, e_dir = engulfing_score(po, pc, o, c)
61
-
62
- # Tentukan arah dominan
63
- if e_dir == "bull" or (h_score > 50 and c > o):
64
- direction = "bull"
65
- elif e_dir == "bear" or (h_score > 50 and c < o):
66
- direction = "bear"
67
- else:
68
- direction = "neutral"
69
-
70
- total = max(h_score, e_score)
71
- return {
72
- "total" : total,
73
- "hammer" : h_score,
74
- "engulfing" : e_score,
75
- "direction" : direction,
76
- }
File without changes