mirror of
https://github.com/quachtinh113/main-fx.git
synced 2026-08-17 20:38:04 +00:00
update quant
This commit is contained in:
@@ -1,52 +1,28 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import os
|
import numpy as np
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
def preprocess_all_symbols():
|
def clean_and_prepare_data(df, symbol_name):
|
||||||
symbols = ['EURUSDm', 'GBPUSDm', 'USDJPYm', 'USDCHFm', 'USDCADm', 'NZDUSDm']
|
# 1. Loại bỏ nến trùng lặp
|
||||||
timeframes = ['M15', 'H1', 'H4']
|
df = df[~df.index.duplicated(keep='first')]
|
||||||
raw_base = Path("data/raw")
|
|
||||||
processed_base = Path("data/processed")
|
|
||||||
|
|
||||||
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:
|
# 3. Tạo khung thời gian đầy đủ (không ngắt quãng)
|
||||||
for tf in timeframes:
|
# Giả sử khung M15, ta tạo range từ đầu đến cuối
|
||||||
file_path = raw_base / symbol / tf / f"{symbol}_{tf}.parquet"
|
full_range = pd.date_range(start=df.index.min(), end=df.index.max(), freq='15min')
|
||||||
|
df = df.reindex(full_range)
|
||||||
if not file_path.exists():
|
|
||||||
continue
|
# 4. Điền nến thiếu (ffill cho giá, 0 cho volume)
|
||||||
|
df[['open', 'high', 'low', 'close']] = df[['open', 'high', 'low', 'close']].ffill()
|
||||||
# 1. Đọc dữ liệu
|
df['tick_volume'] = df['tick_volume'].fillna(0)
|
||||||
df = pd.read_parquet(file_path)
|
|
||||||
initial_count = len(df)
|
# 5. Loại bỏ ngày cuối tuần (Forex nghỉ)
|
||||||
|
# Thứ 7 (5) và Chủ Nhật (6)
|
||||||
# 2. Xử lý logic cơ bản
|
df = df[df.index.dayofweek < 5]
|
||||||
# Chuyển index sang datetime nếu chưa có
|
|
||||||
df.index = pd.to_datetime(df['timestamp'])
|
return df
|
||||||
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()
|
|
||||||
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.align_timeframes(df_m15, df_h1, df_h4)
|
||||||
df = self.classify(df)
|
df = self.classify(df)
|
||||||
df = self.generate_signal(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