nextrade-engine 0.6.0__tar.gz → 0.7.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 (29) hide show
  1. {nextrade_engine-0.6.0/nextrade/nextrade_engine.egg-info → nextrade_engine-0.7.0}/PKG-INFO +2 -1
  2. nextrade_engine-0.7.0/nextrade/nextrade/__init__.py +330 -0
  3. nextrade_engine-0.7.0/nextrade/nextrade/ml/features.py +185 -0
  4. nextrade_engine-0.7.0/nextrade/nextrade/ml/model.py +250 -0
  5. nextrade_engine-0.7.0/nextrade/nextrade/utils/__init__.py +0 -0
  6. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0/nextrade/nextrade_engine.egg-info}/PKG-INFO +2 -1
  7. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade_engine.egg-info/SOURCES.txt +3 -0
  8. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade_engine.egg-info/requires.txt +1 -0
  9. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/pyproject.toml +2 -1
  10. nextrade_engine-0.6.0/nextrade/nextrade/__init__.py +0 -194
  11. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/LICENSE +0 -0
  12. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/README.md +0 -0
  13. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/backtest/__init__.py +0 -0
  14. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/backtest/engine.py +0 -0
  15. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/core/__init__.py +0 -0
  16. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/core/brain.py +0 -0
  17. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/core/regime.py +0 -0
  18. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/data/__init__.py +0 -0
  19. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/data/fetcher.py +0 -0
  20. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/indicators/__init__.py +0 -0
  21. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/indicators/adaptive.py +0 -0
  22. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/indicators/confluence.py +0 -0
  23. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/indicators/pattern.py +0 -0
  24. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/lang.py +0 -0
  25. {nextrade_engine-0.6.0/nextrade/nextrade/utils → nextrade_engine-0.7.0/nextrade/nextrade/ml}/__init__.py +0 -0
  26. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade/utils/terminal_ui.py +0 -0
  27. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade_engine.egg-info/dependency_links.txt +0 -0
  28. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/nextrade/nextrade_engine.egg-info/top_level.txt +0 -0
  29. {nextrade_engine-0.6.0 → nextrade_engine-0.7.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextrade-engine
3
- Version: 0.6.0
3
+ Version: 0.7.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
@@ -13,6 +13,7 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: yfinance
16
+ Requires-Dist: scikit-learn
16
17
  Dynamic: license-file
17
18
 
18
19
  # NexTrade 🧠
@@ -0,0 +1,330 @@
1
+ """
2
+ NexTrade — AI Trading Engine
3
+ import nextrade → wizard muncul otomatis di terminal
4
+ """
5
+
6
+ from nextrade.lang import set_language, list_languages, get, current as _lang
7
+ from nextrade.utils.terminal_ui import (
8
+ print_welcome, print_signal, print_error,
9
+ print_loading, print_success, list_strategies, STRATEGIES
10
+ )
11
+ from nextrade.indicators.confluence import confluence_score
12
+ from nextrade.core.regime import detect_regime
13
+ from nextrade.core.brain import Brain
14
+ from nextrade.data.fetcher import fetch, fetch_multi, list_markets
15
+ import numpy as np
16
+
17
+ __version__ = "0.7.0"
18
+ __author__ = "NexTrade"
19
+
20
+ # Tampilkan wizard saat pertama kali import
21
+ print_welcome()
22
+
23
+ # Global instances
24
+ _brain = Brain.__new__(Brain)
25
+ _brain.state = None
26
+ _ml_engine = None
27
+
28
+ def _get_brain():
29
+ global _brain
30
+ if _brain.state is None:
31
+ _brain = Brain()
32
+ return _brain
33
+
34
+ def _get_ml():
35
+ global _ml_engine
36
+ return _ml_engine
37
+
38
+ # ─────────────────────────────────────────
39
+ # PUBLIC API
40
+ # ─────────────────────────────────────────
41
+
42
+ def start(strategy: str = "adaptive",
43
+ market: str = "BTCUSD",
44
+ timeframe: str = "H1",
45
+ data: dict = None,
46
+ use_ml: bool = False):
47
+ """
48
+ Jalankan NexTrade dengan strategi pilihan.
49
+
50
+ Contoh:
51
+ nextrade.start("momentum", market="BTCUSD")
52
+ nextrade.start("adaptive", market="EURUSD", use_ml=True)
53
+ """
54
+ strategy = strategy.lower().strip()
55
+ if strategy not in STRATEGIES:
56
+ print_error(f"{get('error_strategy')}: {', '.join(STRATEGIES)}")
57
+ list_strategies()
58
+ return
59
+
60
+ print_loading(f"{get('loading_strategy')} {strategy.upper()}...")
61
+
62
+ if data is None:
63
+ data = fetch(market, timeframe, bars=500)
64
+ if data is None:
65
+ print_error(get('error_data'))
66
+ return
67
+
68
+ print_loading(f"{get('loading_indicators')}...")
69
+
70
+ # ── Sinyal dari rule-based engine ─────────────────────
71
+ result = confluence_score(
72
+ opens=data["open"], highs=data["high"],
73
+ lows=data["low"], closes=data["close"],
74
+ )
75
+ brain = _get_brain()
76
+ threshold = brain.get_confidence_threshold()
77
+ confidence = result["confidence"]
78
+
79
+ if confidence >= threshold and result["details"]["pattern"]["direction"] != "bear":
80
+ signal_rb = "BUY"
81
+ elif confidence <= (100 - threshold) or result["details"]["pattern"]["direction"] == "bear":
82
+ signal_rb = "SELL"
83
+ else:
84
+ signal_rb = "HOLD"
85
+
86
+ # ── Sinyal dari ML engine (opsional) ──────────────────
87
+ ml_signal = None
88
+ ml_conf = None
89
+ ml_proba = None
90
+
91
+ if use_ml:
92
+ ml = _get_ml()
93
+ if ml is None or not ml.is_trained:
94
+ print(f" \033[33m⚠\033[0m ML belum ditraining. "
95
+ f"Jalankan \033[1mnextrade.train_ml()\033[0m dulu.\n"
96
+ f" \033[2mMenggunakan rule-based engine sementara...\033[0m\n")
97
+ else:
98
+ print_loading("Menjalankan ML engine...")
99
+ ml_signal, ml_conf, ml_proba = ml.predict(
100
+ data["open"], data["high"],
101
+ data["low"], data["close"],
102
+ data.get("volume"))
103
+
104
+ # ── Gabungkan sinyal (ML + Rule-based) ────────────────
105
+ if ml_signal and ml_conf:
106
+ # Voting: ML 60% + Rule-based 40%
107
+ signals_vote = {"BUY": 0.0, "SELL": 0.0, "HOLD": 0.0}
108
+ signals_vote[ml_signal] += ml_conf * 0.6
109
+ signals_vote[signal_rb] += confidence * 0.4
110
+ signal = max(signals_vote, key=signals_vote.get)
111
+ confidence = round(signals_vote[signal], 2)
112
+ source = "ML + Rule-based"
113
+ else:
114
+ signal = signal_rb
115
+ source = "Rule-based"
116
+
117
+ print_signal(signal=signal, confidence=confidence,
118
+ strategy=strategy, market=market)
119
+
120
+ regime = result["details"]["regime"]["regime"]
121
+ rsi = result["details"]["rsi"]
122
+ price = data["price_now"]
123
+
124
+ print(f" \033[2m{get('detail_analysis')}:\033[0m")
125
+ print(f" \033[36m{get('price_now'):<18}:\033[0m {price:,.5f}")
126
+ print(f" \033[36m{get('market_condition'):<18}:\033[0m {get(regime)}")
127
+ print(f" \033[36mRSI Adaptif :\033[0m {rsi:.1f}")
128
+ print(f" \033[36m{get('iq_model'):<18}:\033[0m {brain.state['iq']} / 100")
129
+ print(f" \033[36mEngine :\033[0m {source}")
130
+
131
+ if ml_signal:
132
+ print(f" \033[36mML Signal :\033[0m {ml_signal} "
133
+ f"\033[2m({ml_conf:.1f}%)\033[0m")
134
+ print(f" \033[36mML Probabilitas :\033[0m "
135
+ f"BUY={ml_proba.get('BUY',0):.2f} "
136
+ f"SELL={ml_proba.get('SELL',0):.2f} "
137
+ f"HOLD={ml_proba.get('HOLD',0):.2f}")
138
+ print()
139
+
140
+ return {**result, "signal": signal, "market": market,
141
+ "price": price, "strategy": strategy, "source": source}
142
+
143
+
144
+ def train_ml(market: str = "BTCUSD",
145
+ timeframe: str = "H1",
146
+ bars: int = 1000,
147
+ forward: int = 3,
148
+ threshold: float = 0.005):
149
+ """
150
+ Train ML engine NexTrade dengan data real market.
151
+
152
+ Contoh:
153
+ nextrade.train_ml("BTCUSD", "H1")
154
+ nextrade.train_ml("EURUSD", "H4", bars=2000)
155
+ nextrade.train_ml("AAPL", "D1")
156
+ """
157
+ global _ml_engine
158
+ from nextrade.ml.model import NexTradeML
159
+
160
+ print(f"\n \033[1m\033[37m NexTrade ML Training\033[0m")
161
+ print(f" \033[36m{'─' * 44}\033[0m")
162
+
163
+ data = fetch(market, timeframe, bars=bars)
164
+ if data is None:
165
+ print_error(get('error_data'))
166
+ return None
167
+
168
+ _ml_engine = NexTradeML()
169
+
170
+ # Coba load model tersimpan dulu
171
+ if _ml_engine.load():
172
+ print(f" \033[32m✓\033[0m Model tersimpan ditemukan — "
173
+ f"winrate sebelumnya: {_ml_engine.winrate:.1f}%")
174
+ print(f" \033[36m⠿\033[0m Melatih ulang dengan data terbaru...\n")
175
+
176
+ result = _ml_engine.train(
177
+ opens=data["open"], highs=data["high"],
178
+ lows=data["low"], closes=data["close"],
179
+ volume=data.get("volume"),
180
+ forward=forward, threshold=threshold,
181
+ )
182
+
183
+ # Update brain IQ berdasarkan winrate ML
184
+ brain = _get_brain()
185
+ brain.state["correct_predictions"] += int(
186
+ result["winrate"] / 100 * result["samples_train"])
187
+ brain.state["total_predictions"] += result["samples_train"]
188
+ brain.state["iq"] = min(int(result["winrate"]), 100)
189
+
190
+ print(f" \033[32m✓\033[0m IQ Model diupdate ke: "
191
+ f"\033[1m\033[33m{brain.state['iq']}\033[0m\n")
192
+
193
+ return _ml_engine
194
+
195
+
196
+ def ml_status():
197
+ """Tampilkan status ML engine."""
198
+ ml = _get_ml()
199
+ if ml is None or not ml.is_trained:
200
+ print(f"\n \033[33m⚠\033[0m ML belum ditraining. "
201
+ f"Jalankan \033[1mnextrade.train_ml()\033[0m dulu.\n")
202
+ return
203
+ print(f"\n \033[1m\033[37m NexTrade ML Status\033[0m")
204
+ print(f" \033[36m{'─' * 40}\033[0m")
205
+ if ml.train_history:
206
+ h = ml.train_history[-1]
207
+ print(f" \033[1mWinrate :\033[0m \033[33m{h['winrate']:.1f}%\033[0m")
208
+ print(f" \033[1mPrice Predictor:\033[0m {h['acc_price']:.1f}%")
209
+ print(f" \033[1mPattern Classif:\033[0m {h['acc_pattern']:.1f}%")
210
+ print(f" \033[1mSignal Filter :\033[0m {h['acc_filter']:.1f}%")
211
+ print(f" \033[1mTotal Features :\033[0m {h['features']}")
212
+ print(f" \033[1mSamples Train :\033[0m {h['samples_train']:,}")
213
+ print(f" \033[36m{'─' * 40}\033[0m\n")
214
+
215
+
216
+ def train(mode: str = "hybrid", market: str = "BTCUSD",
217
+ timeframe: str = "D1", bars: int = 1000):
218
+ """Latih brain NexTrade dengan data real market."""
219
+ print(f"\n \033[1m\033[37m Training NexTrade Brain\033[0m")
220
+ print(f" \033[36m{'─' * 40}\033[0m")
221
+ data = fetch(market, timeframe, bars=bars)
222
+ if data is None:
223
+ print_error(get('error_data'))
224
+ return
225
+ _get_brain().train(mode=mode, data=data)
226
+
227
+
228
+ def brain_status():
229
+ """Tampilkan status otak NexTrade."""
230
+ _get_brain().status()
231
+
232
+
233
+ def set_agresivitas(pct: int):
234
+ """Set agresivitas sinyal (0-100)."""
235
+ _get_brain().set_agresivitas(pct)
236
+
237
+
238
+ def scan(markets: list = None, timeframe: str = "H1",
239
+ use_ml: bool = False):
240
+ """Scan banyak market sekaligus."""
241
+ if markets is None:
242
+ markets = ["BTCUSD", "EURUSD", "XAUUSD", "AAPL", "TSLA"]
243
+
244
+ print(f"\n \033[1m\033[37m NexTrade Market Scanner\033[0m")
245
+ print(f" \033[36m{'─' * 58}\033[0m")
246
+ print(f" \033[2m{'MARKET':<12} {'HARGA':>14} {get('signal'):>8} "
247
+ f"{'CONF':>8} {'ENGINE'}\033[0m")
248
+ print(f" \033[36m{'─' * 58}\033[0m")
249
+
250
+ results = []
251
+ ml = _get_ml()
252
+
253
+ for market in markets:
254
+ data = fetch(market, timeframe, bars=300, silent=True)
255
+ if data is None: continue
256
+
257
+ result = confluence_score(
258
+ opens=data["open"], highs=data["high"],
259
+ lows=data["low"], closes=data["close"],
260
+ )
261
+
262
+ signal = result["signal"]
263
+ confidence = result["confidence"]
264
+ price = data["price_now"]
265
+ engine_tag = "rule"
266
+
267
+ if use_ml and ml and ml.is_trained:
268
+ ms, mc, _ = ml.predict(
269
+ data["open"], data["high"],
270
+ data["low"], data["close"],
271
+ data.get("volume"))
272
+ vote = {"BUY":0.0,"SELL":0.0,"HOLD":0.0}
273
+ vote[ms] += mc * 0.6
274
+ vote[signal] += confidence * 0.4
275
+ signal = max(vote, key=vote.get)
276
+ confidence = round(vote[signal], 2)
277
+ engine_tag = "ML+rule"
278
+
279
+ sc = "\033[32m" if signal=="BUY" else "\033[31m" if signal=="SELL" else "\033[33m"
280
+ print(f" {market:<12} {price:>14,.5f} {sc}{signal:>8}\033[0m "
281
+ f"{confidence:>6.1f}% \033[2m{engine_tag}\033[0m")
282
+ results.append({**result, "market": market,
283
+ "price": price, "signal": signal})
284
+
285
+ print(f" \033[36m{'─' * 58}\033[0m\n")
286
+ buys = [r for r in results if r["signal"]=="BUY"]
287
+ sells = [r for r in results if r["signal"]=="SELL"]
288
+ if buys:
289
+ best = max(buys, key=lambda x: x["confidence"])
290
+ print(f" \033[32m★ BUY : {best['market']} ({best['confidence']:.1f}%)\033[0m")
291
+ if sells:
292
+ best = max(sells, key=lambda x: x["confidence"])
293
+ print(f" \033[31m★ SELL : {best['market']} ({best['confidence']:.1f}%)\033[0m")
294
+ print()
295
+ return results
296
+
297
+
298
+ def backtest(strategy: str = "adaptive",
299
+ market: str = "BTCUSD",
300
+ timeframe: str = "D1",
301
+ bars: int = 500,
302
+ agresivitas: int = 30,
303
+ sl_pct: float = 2.0,
304
+ tp_pct: float = 4.0):
305
+ """Backtest strategi dengan data real historis."""
306
+ from nextrade.backtest.engine import run as _run
307
+ data = fetch(market, timeframe, bars=bars)
308
+ if data is None:
309
+ print_error(get('error_data'))
310
+ return
311
+ return _run(data=data, strategy=strategy, agresivitas=agresivitas,
312
+ sl_pct=sl_pct, tp_pct=tp_pct)
313
+
314
+
315
+ def info():
316
+ """Tampilkan semua strategi dan market."""
317
+ list_strategies()
318
+ list_markets()
319
+
320
+
321
+ def regime(market: str = "BTCUSD", timeframe: str = "H1") -> dict:
322
+ """Deteksi kondisi pasar saat ini."""
323
+ data = fetch(market, timeframe, bars=100, silent=True)
324
+ if data is None: return {}
325
+ return detect_regime(data["close"])
326
+
327
+
328
+ def language():
329
+ """Tampilkan semua bahasa yang tersedia."""
330
+ list_languages()
@@ -0,0 +1,185 @@
1
+ """
2
+ NexTrade ML — Feature Engineering Layer
3
+ 30 fitur dari data OHLCV untuk model ML.
4
+ """
5
+ import numpy as np
6
+
7
+ def _safe_div(a, b, fill=0.0):
8
+ return np.where(np.abs(b) > 1e-9, a / b, fill)
9
+
10
+ def _rolling(arr, n, func):
11
+ out = np.full(len(arr), np.nan)
12
+ for i in range(n-1, len(arr)):
13
+ out[i] = func(arr[i-n+1:i+1])
14
+ return out
15
+
16
+ def _ema(arr, period):
17
+ k = 2 / (period + 1)
18
+ out = np.full(len(arr), np.nan)
19
+ out[period-1] = np.mean(arr[:period])
20
+ for i in range(period, len(arr)):
21
+ out[i] = arr[i] * k + out[i-1] * (1 - k)
22
+ return out
23
+
24
+ def _rsi(close, period=14):
25
+ delta = np.diff(close, prepend=close[0])
26
+ gain = np.where(delta > 0, delta, 0.0)
27
+ loss = np.where(delta < 0, -delta, 0.0)
28
+ avg_g = _rolling(gain, period, np.mean)
29
+ avg_l = _rolling(loss, period, np.mean)
30
+ rs = _safe_div(avg_g, avg_l + 1e-9)
31
+ return 100 - 100 / (1 + rs)
32
+
33
+ def _atr(high, low, close, period=14):
34
+ tr = np.maximum(high - low,
35
+ np.maximum(np.abs(high - np.roll(close, 1)),
36
+ np.abs(low - np.roll(close, 1))))
37
+ return _rolling(tr, period, np.mean)
38
+
39
+ def _bollinger(close, period=20):
40
+ ma = _rolling(close, period, np.mean)
41
+ std = _rolling(close, period, np.std)
42
+ upper = ma + 2 * std
43
+ lower = ma - 2 * std
44
+ pct_b = _safe_div(close - lower, upper - lower + 1e-9)
45
+ width = _safe_div(upper - lower, ma + 1e-9)
46
+ return pct_b, width
47
+
48
+ def _macd(close, fast=12, slow=26, signal=9):
49
+ ema_f = _ema(close, fast)
50
+ ema_s = _ema(close, slow)
51
+ macd = ema_f - ema_s
52
+ sig = _ema(np.nan_to_num(macd), signal)
53
+ return macd, sig, macd - sig
54
+
55
+ def _stochastic(high, low, close, k=14, d=3):
56
+ lowest = _rolling(low, k, np.min)
57
+ highest = _rolling(high, k, np.max)
58
+ stoch_k = _safe_div(close - lowest, highest - lowest + 1e-9) * 100
59
+ stoch_d = _rolling(stoch_k, d, np.mean)
60
+ return stoch_k, stoch_d
61
+
62
+ def _williams_r(high, low, close, period=14):
63
+ highest = _rolling(high, period, np.max)
64
+ lowest = _rolling(low, period, np.min)
65
+ return _safe_div(highest - close, highest - lowest + 1e-9) * -100
66
+
67
+ def _cci(high, low, close, period=20):
68
+ tp = (high + low + close) / 3
69
+ ma = _rolling(tp, period, np.mean)
70
+ mad = _rolling(tp, period, lambda x: np.mean(np.abs(x - np.mean(x))))
71
+ return _safe_div(tp - ma, 0.015 * mad + 1e-9)
72
+
73
+ def _obv(close, volume):
74
+ direction = np.sign(np.diff(close, prepend=close[0]))
75
+ return np.cumsum(direction * volume)
76
+
77
+ def _vwap(high, low, close, volume):
78
+ tp = (high + low + close) / 3
79
+ cvol = np.cumsum(volume)
80
+ ctpv = np.cumsum(tp * volume)
81
+ return _safe_div(ctpv, cvol + 1e-9)
82
+
83
+ def build_features(opens: np.ndarray, highs: np.ndarray,
84
+ lows: np.ndarray, closes: np.ndarray,
85
+ volume: np.ndarray = None) -> np.ndarray:
86
+ """
87
+ Bangun 30 fitur ML dari data OHLCV.
88
+ Returns: np.ndarray shape (n, 30)
89
+ """
90
+ n = len(closes)
91
+ if volume is None:
92
+ volume = np.ones(n)
93
+
94
+ # Price returns
95
+ ret1 = _safe_div(np.diff(closes, prepend=closes[0]), closes)
96
+ ret5 = _safe_div(closes - np.roll(closes, 5), closes)
97
+ ret10 = _safe_div(closes - np.roll(closes, 10), closes)
98
+ ret20 = _safe_div(closes - np.roll(closes, 20), closes)
99
+
100
+ # Trend
101
+ ema8 = _ema(closes, 8)
102
+ ema21 = _ema(closes, 21)
103
+ ema50 = _ema(closes, 50)
104
+ p_e8 = _safe_div(closes - ema8, closes)
105
+ p_e21 = _safe_div(closes - ema21, closes)
106
+ p_e50 = _safe_div(closes - ema50, closes)
107
+ e8_e21 = _safe_div(ema8 - ema21, closes)
108
+ e21_e50= _safe_div(ema21 - ema50, closes)
109
+
110
+ # Momentum
111
+ rsi14 = _rsi(closes, 14)
112
+ rsi7 = _rsi(closes, 7)
113
+ rsi21 = _rsi(closes, 21)
114
+ _, _, macd_hist = _macd(closes)
115
+ macd_n = _safe_div(macd_hist, closes)
116
+ stoch_k, stoch_d = _stochastic(highs, lows, closes)
117
+ will_r = _williams_r(highs, lows, closes)
118
+ cci = _cci(highs, lows, closes)
119
+
120
+ # Volatility
121
+ atr_n = _safe_div(_atr(highs, lows, closes, 14), closes)
122
+ bb_pct, bb_width = _bollinger(closes)
123
+ vs5 = _rolling(ret1, 5, np.std)
124
+ vs20 = _rolling(ret1, 20, np.std)
125
+
126
+ # Volume
127
+ vol_ma = _rolling(volume, 20, np.mean)
128
+ vol_n = _safe_div(volume, vol_ma + 1)
129
+ obv_v = _obv(closes, volume)
130
+ obv_n = _safe_div(obv_v, np.abs(obv_v).max() + 1e-9)
131
+ vwap_d = _safe_div(closes - _vwap(highs, lows, closes, volume), closes)
132
+
133
+ # Candle pattern
134
+ body = np.abs(closes - opens)
135
+ rng = highs - lows + 1e-9
136
+ body_p = _safe_div(body, rng)
137
+ upper = highs - np.maximum(opens, closes)
138
+ lower = np.minimum(opens, closes) - lows
139
+ up_p = _safe_div(upper, rng)
140
+ lo_p = _safe_div(lower, rng)
141
+ is_bull= (closes > opens).astype(float)
142
+
143
+ # Position in range
144
+ h20 = _rolling(highs, 20, np.max)
145
+ l20 = _rolling(lows, 20, np.min)
146
+ pos_r = _safe_div(closes - l20, h20 - l20 + 1e-9)
147
+
148
+ features = np.column_stack([
149
+ ret1, ret5, ret10, ret20,
150
+ p_e8, p_e21, p_e50, e8_e21, e21_e50,
151
+ rsi14, rsi7, rsi21,
152
+ macd_n, stoch_k, stoch_d, will_r, cci,
153
+ atr_n, bb_pct, bb_width, vs5, vs20,
154
+ vol_n, obv_n, vwap_d,
155
+ body_p, up_p, lo_p, is_bull,
156
+ pos_r,
157
+ ])
158
+
159
+ return np.nan_to_num(features, nan=0.0, posinf=0.0, neginf=0.0)
160
+
161
+ def build_labels(closes: np.ndarray,
162
+ forward: int = 3,
163
+ threshold: float = 0.005) -> np.ndarray:
164
+ """
165
+ Label: 1=BUY, -1=SELL, 0=HOLD
166
+ Berdasarkan pergerakan harga forward bar ke depan.
167
+ """
168
+ labels = np.zeros(len(closes), dtype=int)
169
+ for i in range(len(closes) - forward):
170
+ ret = (closes[i+forward] - closes[i]) / (closes[i] + 1e-9)
171
+ if ret > threshold: labels[i] = 1
172
+ elif ret < -threshold: labels[i] = -1
173
+ return labels
174
+
175
+ def feature_names() -> list:
176
+ return [
177
+ "ret1","ret5","ret10","ret20",
178
+ "p_ema8","p_ema21","p_ema50","ema8_21","ema21_50",
179
+ "rsi14","rsi7","rsi21",
180
+ "macd","stoch_k","stoch_d","williams_r","cci",
181
+ "atr","bb_pct","bb_width","vol_std5","vol_std20",
182
+ "vol_norm","obv_norm","vwap_dist",
183
+ "body_pct","upper_wick","lower_wick","is_bull",
184
+ "pos_range",
185
+ ]
@@ -0,0 +1,250 @@
1
+ """
2
+ NexTrade ML — 3 Model Spesialis + Ensemble
3
+ Price Predictor + Pattern Classifier + Signal Filter
4
+ """
5
+ import numpy as np
6
+ import os
7
+ import pickle
8
+ from nextrade.ml.features import build_features, build_labels, feature_names
9
+
10
+ try:
11
+ from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
12
+ from sklearn.linear_model import LogisticRegression
13
+ from sklearn.preprocessing import StandardScaler
14
+ from sklearn.calibration import CalibratedClassifierCV
15
+ SKLEARN_OK = True
16
+ except ImportError:
17
+ SKLEARN_OK = False
18
+
19
+ MODEL_PATH = os.path.join(os.path.dirname(__file__), "saved_model.pkl")
20
+
21
+ class NexTradeML:
22
+ """
23
+ Engine ML NexTrade — 3 spesialis + ensemble.
24
+
25
+ Cara pakai:
26
+ ml = NexTradeML()
27
+ ml.train(opens, highs, lows, closes, volume)
28
+ signal, confidence, proba = ml.predict(opens, highs, lows, closes)
29
+ """
30
+
31
+ def __init__(self):
32
+ if not SKLEARN_OK:
33
+ raise ImportError("scikit-learn belum terinstall. "
34
+ "Jalankan: pip install scikit-learn")
35
+ self.scaler = StandardScaler()
36
+ self.is_trained = False
37
+ self.weights = np.array([0.4, 0.35, 0.25]) # bobot awal
38
+ self.train_history = []
39
+ self._build_models()
40
+
41
+ def _build_models(self):
42
+ # Model 1: Price Predictor — Random Forest
43
+ self.price_model = RandomForestClassifier(
44
+ n_estimators = 200,
45
+ max_depth = 8,
46
+ min_samples_leaf= 5,
47
+ class_weight = "balanced",
48
+ random_state = 42,
49
+ n_jobs = -1,
50
+ )
51
+ # Model 2: Pattern Classifier — Gradient Boosting
52
+ self.pattern_model = GradientBoostingClassifier(
53
+ n_estimators = 150,
54
+ max_depth = 5,
55
+ learning_rate = 0.05,
56
+ subsample = 0.8,
57
+ random_state = 42,
58
+ )
59
+ # Model 3: Signal Filter — Logistic Regression
60
+ self.filter_model = LogisticRegression(
61
+ C = 1.0,
62
+ class_weight = "balanced",
63
+ max_iter = 500,
64
+ random_state = 42,
65
+ )
66
+
67
+ def train(self, opens: np.ndarray, highs: np.ndarray,
68
+ lows: np.ndarray, closes: np.ndarray,
69
+ volume: np.ndarray = None,
70
+ forward: int = 3,
71
+ threshold: float = 0.005,
72
+ silent: bool = False) -> dict:
73
+ """
74
+ Latih semua model dengan data OHLCV.
75
+ Returns dict hasil training.
76
+ """
77
+ if not silent:
78
+ print(f"\n \033[36m⠿\033[0m Membangun {len(closes):,} fitur ML...")
79
+
80
+ X = build_features(opens, highs, lows, closes, volume)
81
+ y = build_labels(closes, forward=forward, threshold=threshold)
82
+
83
+ # Potong label terakhir yang tidak valid
84
+ valid = len(closes) - forward
85
+ X, y = X[:valid], y[:valid]
86
+
87
+ # Split train/val
88
+ split = int(valid * 0.8)
89
+ X_tr, X_val = X[:split], X[split:]
90
+ y_tr, y_val = y[:split], y[split:]
91
+
92
+ # Scale
93
+ X_tr_s = self.scaler.fit_transform(X_tr)
94
+ X_val_s = self.scaler.transform(X_val)
95
+
96
+ if not silent:
97
+ dist = {1: int((y_tr==1).sum()), -1: int((y_tr==-1).sum()),
98
+ 0: int((y_tr==0).sum())}
99
+ print(f" \033[36m⠿\033[0m Label: BUY={dist[1]} SELL={dist[-1]} HOLD={dist[0]}")
100
+ print(f" \033[36m⠿\033[0m Training 3 model spesialis...")
101
+
102
+ # Train semua model
103
+ self.price_model.fit(X_tr_s, y_tr)
104
+ if not silent: print(f" \033[32m✓\033[0m Price Predictor (RandomForest) selesai")
105
+
106
+ self.pattern_model.fit(X_tr_s, y_tr)
107
+ if not silent: print(f" \033[32m✓\033[0m Pattern Classifier (GradientBoosting) selesai")
108
+
109
+ self.filter_model.fit(X_tr_s, y_tr)
110
+ if not silent: print(f" \033[32m✓\033[0m Signal Filter (LogisticRegression) selesai")
111
+
112
+ # Evaluasi dan update bobot adaptif
113
+ acc1 = self.price_model.score(X_val_s, y_val)
114
+ acc2 = self.pattern_model.score(X_val_s, y_val)
115
+ acc3 = self.filter_model.score(X_val_s, y_val)
116
+
117
+ # Bobot proporsional ke akurasi
118
+ accs = np.array([acc1, acc2, acc3])
119
+ self.weights = accs / (accs.sum() + 1e-9)
120
+
121
+ # Hitung winrate keseluruhan
122
+ preds_val = self._ensemble_predict(X_val_s)
123
+ winrate = float(np.mean(preds_val == y_val) * 100)
124
+
125
+ result = {
126
+ "winrate" : round(winrate, 2),
127
+ "acc_price" : round(acc1 * 100, 2),
128
+ "acc_pattern" : round(acc2 * 100, 2),
129
+ "acc_filter" : round(acc3 * 100, 2),
130
+ "weights" : self.weights.tolist(),
131
+ "samples_train" : split,
132
+ "samples_val" : valid - split,
133
+ "features" : X.shape[1],
134
+ }
135
+
136
+ self.is_trained = True
137
+ self.train_history.append(result)
138
+ self._save()
139
+
140
+ if not silent:
141
+ print(f"\n \033[1m\033[37m Hasil Training ML\033[0m")
142
+ print(f" \033[36m{'─' * 44}\033[0m")
143
+ print(f" \033[1mWinrate Overall :\033[0m \033[33m{winrate:.1f}%\033[0m")
144
+ print(f" \033[1mPrice Predictor :\033[0m {acc1*100:.1f}% "
145
+ f"\033[2m(bobot {self.weights[0]:.2f})\033[0m")
146
+ print(f" \033[1mPattern Classif. :\033[0m {acc2*100:.1f}% "
147
+ f"\033[2m(bobot {self.weights[1]:.2f})\033[0m")
148
+ print(f" \033[1mSignal Filter :\033[0m {acc3*100:.1f}% "
149
+ f"\033[2m(bobot {self.weights[2]:.2f})\033[0m")
150
+ print(f" \033[36m{'─' * 44}\033[0m\n")
151
+
152
+ return result
153
+
154
+ def predict(self, opens: np.ndarray, highs: np.ndarray,
155
+ lows: np.ndarray, closes: np.ndarray,
156
+ volume: np.ndarray = None) -> tuple:
157
+ """
158
+ Prediksi sinyal untuk candle terbaru.
159
+ Returns: (signal, confidence, proba_dict)
160
+ signal: 'BUY' | 'SELL' | 'HOLD'
161
+ """
162
+ if not self.is_trained:
163
+ return "HOLD", 50.0, {"BUY": 0.33, "SELL": 0.33, "HOLD": 0.34}
164
+
165
+ X = build_features(opens, highs, lows, closes, volume)
166
+ X_s = self.scaler.transform(X[-1:])
167
+
168
+ pred = self._ensemble_predict(X_s)[0]
169
+ probas = self._ensemble_proba(X_s)[0]
170
+
171
+ # Map label ke signal
172
+ label_map = {1: "BUY", -1: "SELL", 0: "HOLD"}
173
+ signal = label_map.get(pred, "HOLD")
174
+
175
+ # Confidence = probabilitas kelas yang dipilih * 100
176
+ classes = list(self.price_model.classes_)
177
+ idx = classes.index(pred) if pred in classes else 0
178
+ confidence = float(probas[idx] * 100)
179
+
180
+ proba_dict = {}
181
+ for i, cls in enumerate(classes):
182
+ key = label_map.get(cls, "HOLD")
183
+ proba_dict[key] = round(float(probas[i]), 4)
184
+
185
+ return signal, round(confidence, 2), proba_dict
186
+
187
+ def _ensemble_predict(self, X_s: np.ndarray) -> np.ndarray:
188
+ """Gabungkan prediksi 3 model dengan bobot adaptif."""
189
+ p1 = self._get_proba(self.price_model, X_s)
190
+ p2 = self._get_proba(self.pattern_model, X_s)
191
+ p3 = self._get_proba(self.filter_model, X_s)
192
+ combined = (p1 * self.weights[0] +
193
+ p2 * self.weights[1] +
194
+ p3 * self.weights[2])
195
+ classes = list(self.price_model.classes_)
196
+ return np.array([classes[i] for i in combined.argmax(axis=1)])
197
+
198
+ def _ensemble_proba(self, X_s: np.ndarray) -> np.ndarray:
199
+ p1 = self._get_proba(self.price_model, X_s)
200
+ p2 = self._get_proba(self.pattern_model, X_s)
201
+ p3 = self._get_proba(self.filter_model, X_s)
202
+ return (p1 * self.weights[0] +
203
+ p2 * self.weights[1] +
204
+ p3 * self.weights[2])
205
+
206
+ def _get_proba(self, model, X_s):
207
+ try:
208
+ return model.predict_proba(X_s)
209
+ except Exception:
210
+ preds = model.predict(X_s)
211
+ classes = list(model.classes_)
212
+ out = np.zeros((len(preds), len(classes)))
213
+ for i, p in enumerate(preds):
214
+ out[i, classes.index(p)] = 1.0
215
+ return out
216
+
217
+ def _save(self):
218
+ try:
219
+ with open(MODEL_PATH, "wb") as f:
220
+ pickle.dump({
221
+ "price_model" : self.price_model,
222
+ "pattern_model": self.pattern_model,
223
+ "filter_model" : self.filter_model,
224
+ "scaler" : self.scaler,
225
+ "weights" : self.weights,
226
+ "history" : self.train_history,
227
+ }, f)
228
+ except Exception:
229
+ pass
230
+
231
+ def load(self) -> bool:
232
+ try:
233
+ with open(MODEL_PATH, "rb") as f:
234
+ saved = pickle.load(f)
235
+ self.price_model = saved["price_model"]
236
+ self.pattern_model = saved["pattern_model"]
237
+ self.filter_model = saved["filter_model"]
238
+ self.scaler = saved["scaler"]
239
+ self.weights = saved["weights"]
240
+ self.train_history = saved.get("history", [])
241
+ self.is_trained = True
242
+ return True
243
+ except Exception:
244
+ return False
245
+
246
+ @property
247
+ def winrate(self) -> float:
248
+ if self.train_history:
249
+ return self.train_history[-1]["winrate"]
250
+ return 0.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextrade-engine
3
- Version: 0.6.0
3
+ Version: 0.7.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
@@ -13,6 +13,7 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: yfinance
16
+ Requires-Dist: scikit-learn
16
17
  Dynamic: license-file
17
18
 
18
19
  # NexTrade 🧠
@@ -14,6 +14,9 @@ nextrade/nextrade/indicators/__init__.py
14
14
  nextrade/nextrade/indicators/adaptive.py
15
15
  nextrade/nextrade/indicators/confluence.py
16
16
  nextrade/nextrade/indicators/pattern.py
17
+ nextrade/nextrade/ml/__init__.py
18
+ nextrade/nextrade/ml/features.py
19
+ nextrade/nextrade/ml/model.py
17
20
  nextrade/nextrade/utils/__init__.py
18
21
  nextrade/nextrade/utils/terminal_ui.py
19
22
  nextrade/nextrade_engine.egg-info/PKG-INFO
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nextrade-engine"
7
- version = "0.6.0"
7
+ version = "0.7.0"
8
8
  description = "AI Trading Engine — Ringan, Multi-Market, Bisa di HP"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -21,6 +21,7 @@ requires-python = ">=3.8"
21
21
  dependencies = [
22
22
  "numpy",
23
23
  "yfinance",
24
+ "scikit-learn",
24
25
  ]
25
26
 
26
27
  [tool.setuptools]
@@ -1,194 +0,0 @@
1
- """
2
- NexTrade — AI Trading Engine
3
- import nextrade → wizard muncul otomatis di terminal
4
- """
5
-
6
- from nextrade.lang import set_language, list_languages, get, current as _lang
7
- from nextrade.utils.terminal_ui import (
8
- print_welcome, print_signal, print_error,
9
- print_loading, print_success, list_strategies, STRATEGIES
10
- )
11
- from nextrade.indicators.confluence import confluence_score
12
- from nextrade.core.regime import detect_regime
13
- from nextrade.core.brain import Brain
14
- from nextrade.data.fetcher import fetch, fetch_multi, list_markets
15
- import numpy as np
16
-
17
- __version__ = "0.6.0"
18
- __author__ = "NexTrade"
19
-
20
- # Tampilkan wizard saat pertama kali import
21
- print_welcome()
22
-
23
- # Instance brain global
24
- _brain = Brain.__new__(Brain)
25
- _brain.state = None
26
-
27
- def _get_brain():
28
- global _brain
29
- if _brain.state is None:
30
- _brain = Brain()
31
- return _brain
32
-
33
- # ─────────────────────────────────────────
34
- # PUBLIC API
35
- # ─────────────────────────────────────────
36
-
37
- def start(strategy: str = "adaptive",
38
- market: str = "BTCUSD",
39
- timeframe: str = "H1",
40
- data: dict = None):
41
- """
42
- Jalankan NexTrade dengan strategi pilihan.
43
-
44
- Contoh:
45
- nextrade.start("momentum", market="BTCUSD")
46
- nextrade.start("reversal", market="EURUSD", timeframe="H4")
47
- """
48
- strategy = strategy.lower().strip()
49
-
50
- if strategy not in STRATEGIES:
51
- print_error(f"{get('error_strategy')}: {', '.join(STRATEGIES)}")
52
- list_strategies()
53
- return
54
-
55
- print_loading(f"{get('loading_strategy')} {strategy.upper()}...")
56
-
57
- if data is None:
58
- data = fetch(market, timeframe, bars=500)
59
- if data is None:
60
- print_error(get('error_data'))
61
- return
62
-
63
- print_loading(f"{get('loading_indicators')}...")
64
-
65
- result = confluence_score(
66
- opens = data["open"],
67
- highs = data["high"],
68
- lows = data["low"],
69
- closes = data["close"],
70
- )
71
-
72
- brain = _get_brain()
73
- threshold = brain.get_confidence_threshold()
74
- confidence = result["confidence"]
75
-
76
- if confidence >= threshold and result["details"]["pattern"]["direction"] != "bear":
77
- signal = "BUY"
78
- elif confidence <= (100 - threshold) or result["details"]["pattern"]["direction"] == "bear":
79
- signal = "SELL"
80
- else:
81
- signal = "HOLD"
82
-
83
- print_signal(signal=signal, confidence=confidence,
84
- strategy=strategy, market=market)
85
-
86
- regime = result["details"]["regime"]["regime"]
87
- rsi = result["details"]["rsi"]
88
- mom = result["details"]["momentum"]
89
- price = data["price_now"]
90
-
91
- print(f" \033[2m{get('detail_analysis')}:\033[0m")
92
- print(f" \033[36m{get('price_now'):<18}:\033[0m {price:,.5f}")
93
- print(f" \033[36m{get('market_condition'):<18}:\033[0m {get(regime)}")
94
- print(f" \033[36mRSI Adaptif :\033[0m {rsi:.1f}")
95
- print(f" \033[36m{get('iq_model'):<18}:\033[0m {brain.state['iq']} / 100")
96
- print(f" \033[36m{get('agresivitas'):<18}:\033[0m {brain.state['agresivitas']}%")
97
- print()
98
-
99
- return {**result, "signal": signal, "market": market,
100
- "price": price, "strategy": strategy}
101
-
102
-
103
- def train(mode: str = "hybrid", market: str = "BTCUSD",
104
- timeframe: str = "D1", bars: int = 1000):
105
- """Latih brain NexTrade dengan data real market."""
106
- print(f"\n \033[1m\033[37m Training NexTrade Brain\033[0m")
107
- print(f" \033[36m{'─' * 40}\033[0m")
108
- data = fetch(market, timeframe, bars=bars)
109
- if data is None:
110
- print_error(get('error_data'))
111
- return
112
- _get_brain().train(mode=mode, data=data)
113
-
114
-
115
- def brain_status():
116
- """Tampilkan status otak NexTrade."""
117
- _get_brain().status()
118
-
119
-
120
- def set_agresivitas(pct: int):
121
- """Set agresivitas sinyal (0-100)."""
122
- _get_brain().set_agresivitas(pct)
123
-
124
-
125
- def scan(markets: list = None, timeframe: str = "H1"):
126
- """Scan banyak market sekaligus."""
127
- if markets is None:
128
- markets = ["BTCUSD", "EURUSD", "XAUUSD", "AAPL", "TSLA"]
129
-
130
- print(f"\n \033[1m\033[37m NexTrade Market Scanner\033[0m")
131
- print(f" \033[36m{'─' * 55}\033[0m")
132
- print(f" \033[2m{'MARKET':<12} {'HARGA':>14} {get('signal'):>8} "
133
- f"{'CONFIDENCE':>12} {get('market_condition')}\033[0m")
134
- print(f" \033[36m{'─' * 55}\033[0m")
135
-
136
- results = []
137
- for market in markets:
138
- data = fetch(market, timeframe, bars=300, silent=True)
139
- if data is None: continue
140
- result = confluence_score(
141
- opens=data["open"], highs=data["high"],
142
- lows=data["low"], closes=data["close"],
143
- )
144
- signal = result["signal"]
145
- confidence = result["confidence"]
146
- regime = result["details"]["regime"]["regime"]
147
- price = data["price_now"]
148
- sc = "\033[32m" if signal=="BUY" else "\033[31m" if signal=="SELL" else "\033[33m"
149
- print(f" {market:<12} {price:>14,.5f} {sc}{signal:>8}\033[0m "
150
- f"{confidence:>10.1f}% \033[2m{regime}\033[0m")
151
- results.append({**result, "market": market, "price": price})
152
-
153
- print(f" \033[36m{'─' * 55}\033[0m\n")
154
- buys = [r for r in results if r["signal"]=="BUY"]
155
- sells = [r for r in results if r["signal"]=="SELL"]
156
- if buys:
157
- best = max(buys, key=lambda x: x["confidence"])
158
- print(f" \033[32m★ BUY : {best['market']} ({best['confidence']:.1f}%)\033[0m")
159
- if sells:
160
- best = max(sells, key=lambda x: x["confidence"])
161
- print(f" \033[31m★ SELL : {best['market']} ({best['confidence']:.1f}%)\033[0m")
162
- print()
163
- return results
164
-
165
-
166
- def backtest(strategy: str = "adaptive", market: str = "BTCUSD",
167
- timeframe: str = "D1", bars: int = 500,
168
- agresivitas: int = 30, sl_pct: float = 2.0, tp_pct: float = 4.0):
169
- """Backtest strategi dengan data real historis."""
170
- from nextrade.backtest.engine import run as _run
171
- data = fetch(market, timeframe, bars=bars)
172
- if data is None:
173
- print_error(get('error_data'))
174
- return
175
- return _run(data=data, strategy=strategy, agresivitas=agresivitas,
176
- sl_pct=sl_pct, tp_pct=tp_pct)
177
-
178
-
179
- def info():
180
- """Tampilkan semua strategi dan market."""
181
- list_strategies()
182
- list_markets()
183
-
184
-
185
- def regime(market: str = "BTCUSD", timeframe: str = "H1") -> dict:
186
- """Deteksi kondisi pasar saat ini."""
187
- data = fetch(market, timeframe, bars=100, silent=True)
188
- if data is None: return {}
189
- return detect_regime(data["close"])
190
-
191
-
192
- def language():
193
- """Tampilkan semua bahasa yang tersedia."""
194
- list_languages()
File without changes