mirror of
https://github.com/quachtinh113/main-fx.git
synced 2026-07-28 02:47:48 +00:00
update quant
This commit is contained in:
@@ -1,52 +1,28 @@
|
||||
import pandas as pd
|
||||
import os
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
|
||||
def preprocess_all_symbols():
|
||||
symbols = ['EURUSDm', 'GBPUSDm', 'USDJPYm', 'USDCHFm', 'USDCADm', 'NZDUSDm']
|
||||
timeframes = ['M15', 'H1', 'H4']
|
||||
raw_base = Path("data/raw")
|
||||
processed_base = Path("data/processed")
|
||||
def clean_and_prepare_data(df, symbol_name):
|
||||
# 1. Loại bỏ nến trùng lặp
|
||||
df = df[~df.index.duplicated(keep='first')]
|
||||
|
||||
print("--- BẮT ĐẦU TIỀN XỬ LÝ DỮ LIỆU ---")
|
||||
# 2. Đảm bảo nến logic (OHLC)
|
||||
bad_candles = df[(df['high'] < df['low']) | (df['high'] < df['open']) | (df['high'] < df['close'])]
|
||||
if not bad_candles.empty:
|
||||
print(f"Cảnh báo: Phát hiện {len(bad_candles)} nến lỗi tại {symbol_name}")
|
||||
# Có thể xóa hoặc sửa tùy bạn, ở đây ta chọn xóa nến lỗi cực nặng
|
||||
df = df.drop(bad_candles.index)
|
||||
|
||||
for symbol in symbols:
|
||||
for tf in timeframes:
|
||||
file_path = raw_base / symbol / tf / f"{symbol}_{tf}.parquet"
|
||||
|
||||
if not file_path.exists():
|
||||
continue
|
||||
|
||||
# 1. Đọc dữ liệu
|
||||
df = pd.read_parquet(file_path)
|
||||
initial_count = len(df)
|
||||
|
||||
# 2. Xử lý logic cơ bản
|
||||
# Chuyển index sang datetime nếu chưa có
|
||||
df.index = pd.to_datetime(df['timestamp'])
|
||||
df = df.sort_index()
|
||||
|
||||
# Loại bỏ trùng lặp
|
||||
df = df[~df.index.duplicated(keep='first')]
|
||||
|
||||
# Loại bỏ nến lỗi (High < Low)
|
||||
df = df[df['high'] >= df['low']]
|
||||
|
||||
# Lọc từ năm 2022 trở lại đây như bạn yêu cầu
|
||||
df = df.loc['2022-01-01':]
|
||||
|
||||
# 3. Loại bỏ ngày cuối tuần (Forex nghỉ)
|
||||
df = df[df.index.dayofweek < 5]
|
||||
|
||||
# 4. Lưu dữ liệu sạch
|
||||
output_dir = processed_base / symbol / tf
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_path = output_dir / f"{symbol}_{tf}_clean.parquet"
|
||||
df.to_parquet(output_path)
|
||||
|
||||
print(f"[OK] {symbol} {tf}: {initial_count} -> {len(df)} nến (Đã lưu tại {output_path})")
|
||||
|
||||
print("--- HOÀN THÀNH ---")
|
||||
|
||||
if __name__ == "__main__":
|
||||
preprocess_all_symbols()
|
||||
# 3. Tạo khung thời gian đầy đủ (không ngắt quãng)
|
||||
# Giả sử khung M15, ta tạo range từ đầu đến cuối
|
||||
full_range = pd.date_range(start=df.index.min(), end=df.index.max(), freq='15min')
|
||||
df = df.reindex(full_range)
|
||||
|
||||
# 4. Điền nến thiếu (ffill cho giá, 0 cho volume)
|
||||
df[['open', 'high', 'low', 'close']] = df[['open', 'high', 'low', 'close']].ffill()
|
||||
df['tick_volume'] = df['tick_volume'].fillna(0)
|
||||
|
||||
# 5. Loại bỏ ngày cuối tuần (Forex nghỉ)
|
||||
# Thứ 7 (5) và Chủ Nhật (6)
|
||||
df = df[df.index.dayofweek < 5]
|
||||
|
||||
return df
|
||||
Binary file not shown.
@@ -0,0 +1,214 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class LiquidityEngine:
|
||||
"""
|
||||
Liquidity detection engine (MVP v1)
|
||||
|
||||
Mục tiêu:
|
||||
- tìm swing high / swing low
|
||||
- tìm vùng quét thanh khoản trên / dưới
|
||||
- đánh dấu equal highs / equal lows đơn giản
|
||||
- phục vụ chiến lược Smart Money:
|
||||
liquidity -> price action -> ADX/ATR -> RSI trigger
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
swing_lookback: int = 20,
|
||||
equal_threshold: float = 0.0005,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
swing_lookback: số nến lookback để tìm swing liquidity
|
||||
equal_threshold: ngưỡng xem 2 đỉnh/đáy là gần bằng nhau
|
||||
- với EURUSD có thể dùng 0.0003 ~ 0.0010
|
||||
- với USDJPY nên chỉnh riêng lớn hơn, ví dụ 0.03 ~ 0.10
|
||||
"""
|
||||
self.swing_lookback = swing_lookback
|
||||
self.equal_threshold = equal_threshold
|
||||
|
||||
def _validate_input(self, df: pd.DataFrame) -> None:
|
||||
required_cols = {"open", "high", "low", "close"}
|
||||
missing = required_cols - set(df.columns)
|
||||
if missing:
|
||||
raise ValueError(f"Missing required columns: {sorted(missing)}")
|
||||
|
||||
if len(df) < self.swing_lookback + 5:
|
||||
raise ValueError(
|
||||
f"Not enough rows for liquidity detection. "
|
||||
f"Need at least {self.swing_lookback + 5}, got {len(df)}"
|
||||
)
|
||||
|
||||
def detect_swing_liquidity(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Tìm swing high / swing low bằng rolling window.
|
||||
Dùng shift(1) để tránh nhìn vào chính nến hiện tại.
|
||||
"""
|
||||
df = df.copy()
|
||||
|
||||
df["swing_high"] = df["high"].rolling(self.swing_lookback).max().shift(1)
|
||||
df["swing_low"] = df["low"].rolling(self.swing_lookback).min().shift(1)
|
||||
|
||||
return df
|
||||
|
||||
def detect_equal_high_low(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Equal highs / equal lows đơn giản:
|
||||
- so sánh high hiện tại với high trước đó
|
||||
- so sánh low hiện tại với low trước đó
|
||||
"""
|
||||
df = df.copy()
|
||||
|
||||
prev_high = df["high"].shift(1)
|
||||
prev_low = df["low"].shift(1)
|
||||
|
||||
df["equal_highs"] = (df["high"] - prev_high).abs() <= self.equal_threshold
|
||||
df["equal_lows"] = (df["low"] - prev_low).abs() <= self.equal_threshold
|
||||
|
||||
return df
|
||||
|
||||
def detect_liquidity_sweeps(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Phát hiện sweep thanh khoản.
|
||||
|
||||
Sweep up:
|
||||
- high chọc lên swing_high
|
||||
- nhưng close đóng lại dưới swing_high
|
||||
=> quét buy-side liquidity rồi bị đạp xuống
|
||||
|
||||
Sweep down:
|
||||
- low chọc xuống swing_low
|
||||
- nhưng close đóng lại trên swing_low
|
||||
=> quét sell-side liquidity rồi bị kéo lên
|
||||
"""
|
||||
df = df.copy()
|
||||
|
||||
df["took_buy_side"] = df["high"] > df["swing_high"]
|
||||
df["took_sell_side"] = df["low"] < df["swing_low"]
|
||||
|
||||
df["sweep_buy_side"] = (
|
||||
(df["high"] > df["swing_high"])
|
||||
& (df["close"] < df["swing_high"])
|
||||
)
|
||||
|
||||
df["sweep_sell_side"] = (
|
||||
(df["low"] < df["swing_low"])
|
||||
& (df["close"] > df["swing_low"])
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
def detect_reclaim(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Reclaim sau sweep:
|
||||
- nến trước sweep
|
||||
- nến hiện tại tiếp tục xác nhận quay lại trong vùng
|
||||
|
||||
Dùng cho logic:
|
||||
sweep trước -> reclaim sau -> mới cho trigger RSI / price action
|
||||
"""
|
||||
df = df.copy()
|
||||
|
||||
df["reclaim_after_sell_sweep"] = (
|
||||
df["sweep_sell_side"].shift(1).fillna(False)
|
||||
& (df["close"] > df["swing_low"])
|
||||
)
|
||||
|
||||
df["reclaim_after_buy_sweep"] = (
|
||||
df["sweep_buy_side"].shift(1).fillna(False)
|
||||
& (df["close"] < df["swing_high"])
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
def classify_liquidity_context(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Phân loại context thanh khoản đơn giản.
|
||||
"""
|
||||
df = df.copy()
|
||||
df["liquidity_context"] = "neutral"
|
||||
|
||||
df.loc[df["sweep_sell_side"], "liquidity_context"] = "sell_side_swept"
|
||||
df.loc[df["sweep_buy_side"], "liquidity_context"] = "buy_side_swept"
|
||||
df.loc[df["reclaim_after_sell_sweep"], "liquidity_context"] = "bullish_reclaim"
|
||||
df.loc[df["reclaim_after_buy_sweep"], "liquidity_context"] = "bearish_reclaim"
|
||||
|
||||
return df
|
||||
|
||||
def run(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Full pipeline.
|
||||
"""
|
||||
self._validate_input(df)
|
||||
|
||||
df = df.copy().sort_index()
|
||||
|
||||
df = self.detect_swing_liquidity(df)
|
||||
df = self.detect_equal_high_low(df)
|
||||
df = self.detect_liquidity_sweeps(df)
|
||||
df = self.detect_reclaim(df)
|
||||
df = self.classify_liquidity_context(df)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
symbol = "EURUSDm"
|
||||
timeframe = "M15"
|
||||
path = f"data/processed/{symbol}/{timeframe}/{symbol}_{timeframe}_clean.parquet"
|
||||
|
||||
try:
|
||||
df = pd.read_parquet(path)
|
||||
|
||||
engine = LiquidityEngine(
|
||||
swing_lookback=20,
|
||||
equal_threshold=0.0005,
|
||||
)
|
||||
result = engine.run(df)
|
||||
|
||||
cols = [
|
||||
"open",
|
||||
"high",
|
||||
"low",
|
||||
"close",
|
||||
"swing_high",
|
||||
"swing_low",
|
||||
"equal_highs",
|
||||
"equal_lows",
|
||||
"took_buy_side",
|
||||
"took_sell_side",
|
||||
"sweep_buy_side",
|
||||
"sweep_sell_side",
|
||||
"reclaim_after_buy_sweep",
|
||||
"reclaim_after_sell_sweep",
|
||||
"liquidity_context",
|
||||
]
|
||||
|
||||
print(f"--- Liquidity test for {symbol} {timeframe} ---")
|
||||
print(result[cols].tail(10))
|
||||
|
||||
sweeps = result[result["sweep_buy_side"] | result["sweep_sell_side"]]
|
||||
print(f"\nTotal sweeps found: {len(sweeps)}")
|
||||
|
||||
if not sweeps.empty:
|
||||
print("\nLast 5 sweep events:")
|
||||
print(
|
||||
sweeps[
|
||||
[
|
||||
"high",
|
||||
"low",
|
||||
"close",
|
||||
"swing_high",
|
||||
"swing_low",
|
||||
"sweep_buy_side",
|
||||
"sweep_sell_side",
|
||||
"liquidity_context",
|
||||
]
|
||||
].tail(5)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Liquidity engine test error: {e}")
|
||||
@@ -0,0 +1,261 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class PriceActionEngine:
|
||||
"""
|
||||
Price Action confirmation engine (MVP v1)
|
||||
|
||||
Mục tiêu:
|
||||
- xác nhận phản ứng giá sau liquidity sweep
|
||||
- tạo các cờ price action sạch để strategy dùng
|
||||
- không dự đoán, chỉ đọc hành vi nến đã đóng
|
||||
|
||||
Output chính:
|
||||
- bullish_rejection
|
||||
- bearish_rejection
|
||||
- bullish_engulfing
|
||||
- bearish_engulfing
|
||||
- close_back_above_swing_low
|
||||
- close_back_below_swing_high
|
||||
- bullish_momentum_close
|
||||
- bearish_momentum_close
|
||||
- pa_bullish_confirm
|
||||
- pa_bearish_confirm
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
min_wick_body_ratio: float = 1.5,
|
||||
min_body_ratio: float = 0.4,
|
||||
momentum_close_ratio: float = 0.7,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
min_wick_body_ratio:
|
||||
tỷ lệ wick/body tối thiểu để xem là rejection rõ
|
||||
min_body_ratio:
|
||||
tỷ lệ body/range tối thiểu để xem thân nến đủ rõ
|
||||
momentum_close_ratio:
|
||||
vị trí đóng cửa trong range nến để xem là momentum close
|
||||
0.7 nghĩa là đóng trong 30% đầu/cuối cây nến
|
||||
"""
|
||||
self.min_wick_body_ratio = min_wick_body_ratio
|
||||
self.min_body_ratio = min_body_ratio
|
||||
self.momentum_close_ratio = momentum_close_ratio
|
||||
|
||||
def _validate_input(self, df: pd.DataFrame) -> None:
|
||||
required = {"open", "high", "low", "close"}
|
||||
missing = required - set(df.columns)
|
||||
if missing:
|
||||
raise ValueError(f"Missing required columns: {sorted(missing)}")
|
||||
|
||||
if len(df) < 5:
|
||||
raise ValueError("Not enough rows for price action detection.")
|
||||
|
||||
def _base_features(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
df = df.copy()
|
||||
|
||||
df["range"] = (df["high"] - df["low"]).clip(lower=1e-12)
|
||||
df["body"] = (df["close"] - df["open"]).abs()
|
||||
df["body_ratio"] = df["body"] / df["range"]
|
||||
|
||||
df["upper_wick"] = df["high"] - df[["open", "close"]].max(axis=1)
|
||||
df["lower_wick"] = df[["open", "close"]].min(axis=1) - df["low"]
|
||||
|
||||
df["bullish_bar"] = df["close"] > df["open"]
|
||||
df["bearish_bar"] = df["close"] < df["open"]
|
||||
|
||||
return df
|
||||
|
||||
def detect_rejection_wicks(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Bullish rejection:
|
||||
- lower wick dài
|
||||
- thân nến không quá nhỏ
|
||||
- đóng nến thiên về phía trên
|
||||
|
||||
Bearish rejection:
|
||||
- upper wick dài
|
||||
- thân nến không quá nhỏ
|
||||
- đóng nến thiên về phía dưới
|
||||
"""
|
||||
df = df.copy()
|
||||
|
||||
df["bullish_rejection"] = (
|
||||
(df["lower_wick"] >= df["body"] * self.min_wick_body_ratio)
|
||||
& (df["body_ratio"] >= self.min_body_ratio)
|
||||
& (df["close"] >= df["low"] + df["range"] * 0.6)
|
||||
)
|
||||
|
||||
df["bearish_rejection"] = (
|
||||
(df["upper_wick"] >= df["body"] * self.min_wick_body_ratio)
|
||||
& (df["body_ratio"] >= self.min_body_ratio)
|
||||
& (df["close"] <= df["low"] + df["range"] * 0.4)
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
def detect_engulfing(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Engulfing đơn giản theo thân nến.
|
||||
"""
|
||||
df = df.copy()
|
||||
|
||||
prev_open = df["open"].shift(1)
|
||||
prev_close = df["close"].shift(1)
|
||||
|
||||
prev_bullish = prev_close > prev_open
|
||||
prev_bearish = prev_close < prev_open
|
||||
|
||||
curr_bullish = df["close"] > df["open"]
|
||||
curr_bearish = df["close"] < df["open"]
|
||||
|
||||
prev_body_low = pd.concat([prev_open, prev_close], axis=1).min(axis=1)
|
||||
prev_body_high = pd.concat([prev_open, prev_close], axis=1).max(axis=1)
|
||||
|
||||
curr_body_low = df[["open", "close"]].min(axis=1)
|
||||
curr_body_high = df[["open", "close"]].max(axis=1)
|
||||
|
||||
df["bullish_engulfing"] = (
|
||||
prev_bearish
|
||||
& curr_bullish
|
||||
& (curr_body_low <= prev_body_low)
|
||||
& (curr_body_high >= prev_body_high)
|
||||
)
|
||||
|
||||
df["bearish_engulfing"] = (
|
||||
prev_bullish
|
||||
& curr_bearish
|
||||
& (curr_body_low <= prev_body_low)
|
||||
& (curr_body_high >= prev_body_high)
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
def detect_close_back_inside(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Nếu dataframe đã có swing_high / swing_low từ liquidity engine
|
||||
thì tạo thêm tín hiệu close back inside range.
|
||||
"""
|
||||
df = df.copy()
|
||||
|
||||
if "swing_low" in df.columns:
|
||||
df["close_back_above_swing_low"] = (
|
||||
(df["low"] < df["swing_low"])
|
||||
& (df["close"] > df["swing_low"])
|
||||
)
|
||||
else:
|
||||
df["close_back_above_swing_low"] = False
|
||||
|
||||
if "swing_high" in df.columns:
|
||||
df["close_back_below_swing_high"] = (
|
||||
(df["high"] > df["swing_high"])
|
||||
& (df["close"] < df["swing_high"])
|
||||
)
|
||||
else:
|
||||
df["close_back_below_swing_high"] = False
|
||||
|
||||
return df
|
||||
|
||||
def detect_momentum_close(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Momentum close:
|
||||
- bullish: đóng gần đỉnh nến
|
||||
- bearish: đóng gần đáy nến
|
||||
"""
|
||||
df = df.copy()
|
||||
|
||||
upper_threshold = 1.0 - self.momentum_close_ratio
|
||||
lower_threshold = self.momentum_close_ratio
|
||||
|
||||
df["bullish_momentum_close"] = (
|
||||
(df["close"] >= df["low"] + df["range"] * lower_threshold)
|
||||
& (df["body_ratio"] >= self.min_body_ratio)
|
||||
& df["bullish_bar"]
|
||||
)
|
||||
|
||||
df["bearish_momentum_close"] = (
|
||||
(df["close"] <= df["low"] + df["range"] * upper_threshold)
|
||||
& (df["body_ratio"] >= self.min_body_ratio)
|
||||
& df["bearish_bar"]
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
def build_confirmation_flags(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Gom cụm xác nhận bullish / bearish.
|
||||
"""
|
||||
df = df.copy()
|
||||
|
||||
df["pa_bullish_confirm"] = (
|
||||
df["bullish_rejection"]
|
||||
| df["bullish_engulfing"]
|
||||
| df["close_back_above_swing_low"]
|
||||
| df["bullish_momentum_close"]
|
||||
)
|
||||
|
||||
df["pa_bearish_confirm"] = (
|
||||
df["bearish_rejection"]
|
||||
| df["bearish_engulfing"]
|
||||
| df["close_back_below_swing_high"]
|
||||
| df["bearish_momentum_close"]
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
def run(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
self._validate_input(df)
|
||||
|
||||
df = df.copy().sort_index()
|
||||
df = self._base_features(df)
|
||||
df = self.detect_rejection_wicks(df)
|
||||
df = self.detect_engulfing(df)
|
||||
df = self.detect_close_back_inside(df)
|
||||
df = self.detect_momentum_close(df)
|
||||
df = self.build_confirmation_flags(df)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
symbol = "EURUSDm"
|
||||
timeframe = "M15"
|
||||
path = f"data/processed/{symbol}/{timeframe}/{symbol}_{timeframe}_clean.parquet"
|
||||
|
||||
try:
|
||||
df = pd.read_parquet(path)
|
||||
|
||||
engine = PriceActionEngine()
|
||||
result = engine.run(df)
|
||||
|
||||
cols = [
|
||||
"open",
|
||||
"high",
|
||||
"low",
|
||||
"close",
|
||||
"bullish_rejection",
|
||||
"bearish_rejection",
|
||||
"bullish_engulfing",
|
||||
"bearish_engulfing",
|
||||
"close_back_above_swing_low",
|
||||
"close_back_below_swing_high",
|
||||
"bullish_momentum_close",
|
||||
"bearish_momentum_close",
|
||||
"pa_bullish_confirm",
|
||||
"pa_bearish_confirm",
|
||||
]
|
||||
|
||||
print(f"--- Price Action test for {symbol} {timeframe} ---")
|
||||
print(result[cols].tail(10))
|
||||
|
||||
bullish = result[result["pa_bullish_confirm"]]
|
||||
bearish = result[result["pa_bearish_confirm"]]
|
||||
|
||||
print(f"\nBullish confirmations: {len(bullish)}")
|
||||
print(f"Bearish confirmations: {len(bearish)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Price action engine test error: {e}")
|
||||
@@ -85,4 +85,38 @@ class RSIMultiTimeframe:
|
||||
df = self.align_timeframes(df_m15, df_h1, df_h4)
|
||||
df = self.classify(df)
|
||||
df = self.generate_signal(df)
|
||||
return df
|
||||
return df
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pandas as pd
|
||||
import os
|
||||
|
||||
# 1. Thử load dữ liệu thực tế (đã processed) để test
|
||||
symbol = "EURUSDm"
|
||||
base_path = f"data/processed/{symbol}"
|
||||
|
||||
try:
|
||||
df_m15 = pd.read_parquet(f"{base_path}/M15/{symbol}_M15_clean.parquet")
|
||||
df_h1 = pd.read_parquet(f"{base_path}/H1/{symbol}_H1_clean.parquet")
|
||||
df_h4 = pd.read_parquet(f"{base_path}/H4/{symbol}_H4_clean.parquet")
|
||||
|
||||
# 2. Khởi tạo Engine
|
||||
engine = RSIMultiTimeframe()
|
||||
|
||||
# 3. Chạy thử
|
||||
print(f"--- Đang test chiến thuật cho {symbol} ---")
|
||||
result = engine.run(df_m15, df_h1, df_h4)
|
||||
|
||||
# 4. In kết quả ra màn hình
|
||||
signals = result[result['signal'] != 0]
|
||||
print(f"Tổng số nến: {len(result)}")
|
||||
print(f"Số lượng tín hiệu tìm thấy: {len(signals)}")
|
||||
|
||||
if not signals.empty:
|
||||
print("\n5 tín hiệu cuối cùng:")
|
||||
print(signals[['rsi_h4', 'rsi_h1', 'rsi_m15', 'signal']].tail())
|
||||
|
||||
except Exception as e:
|
||||
print(f"Lỗi khi chạy test: {e}")
|
||||
print("Hãy chắc chắn bạn đã chạy preprocessor.py trước đó.")
|
||||
Reference in New Issue
Block a user