feat: Add Heikin-Ashi scanner to technical scanner framework
This commit is contained in:
parent
a24605e839
commit
aec5929ea7
@ -12,7 +12,7 @@ def technical_scanner_page():
|
|||||||
with scanner_tab:
|
with scanner_tab:
|
||||||
scanner_type = st.selectbox(
|
scanner_type = st.selectbox(
|
||||||
"Select Scanner",
|
"Select Scanner",
|
||||||
["SunnyBands", "ATR-EMA", "ATR-EMA v2"],
|
["SunnyBands", "ATR-EMA", "ATR-EMA v2", "Heikin-Ashi"],
|
||||||
key="tech_scanner_type"
|
key="tech_scanner_type"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from datetime import datetime
|
|||||||
from screener.t_sunnyband import run_sunny_scanner
|
from screener.t_sunnyband import run_sunny_scanner
|
||||||
from screener.t_atr_ema import run_atr_ema_scanner
|
from screener.t_atr_ema import run_atr_ema_scanner
|
||||||
from screener.t_atr_ema_v2 import run_atr_ema_scanner_v2
|
from screener.t_atr_ema_v2 import run_atr_ema_scanner_v2
|
||||||
|
from screener.t_heikinashi import run_heikin_ashi_scanner
|
||||||
|
|
||||||
def run_technical_scanner(scanner_choice: str, start_date: str, end_date: str,
|
def run_technical_scanner(scanner_choice: str, start_date: str, end_date: str,
|
||||||
min_price: float, max_price: float, min_volume: int,
|
min_price: float, max_price: float, min_volume: int,
|
||||||
@ -26,7 +27,8 @@ def run_technical_scanner(scanner_choice: str, start_date: str, end_date: str,
|
|||||||
scanner_map = {
|
scanner_map = {
|
||||||
"sunnybands": lambda: run_sunny_scanner(min_price, max_price, min_volume, portfolio_size, interval, start_dt, end_dt),
|
"sunnybands": lambda: run_sunny_scanner(min_price, max_price, min_volume, portfolio_size, interval, start_dt, end_dt),
|
||||||
"atr-ema": lambda: run_atr_ema_scanner(min_price, max_price, min_volume, portfolio_size, interval, start_dt, end_dt),
|
"atr-ema": lambda: run_atr_ema_scanner(min_price, max_price, min_volume, portfolio_size, interval, start_dt, end_dt),
|
||||||
"atr-ema_v2": lambda: run_atr_ema_scanner_v2(min_price, max_price, min_volume, portfolio_size, interval, start_dt, end_dt)
|
"atr-ema_v2": lambda: run_atr_ema_scanner_v2(min_price, max_price, min_volume, portfolio_size, interval, start_dt, end_dt),
|
||||||
|
"heikin-ashi": lambda: run_heikin_ashi_scanner(min_price, max_price, min_volume, portfolio_size, interval, start_dt, end_dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
scanner_func = scanner_map.get(scanner_choice)
|
scanner_func = scanner_map.get(scanner_choice)
|
||||||
|
|||||||
119
src/screener/t_heikinashi.py
Normal file
119
src/screener/t_heikinashi.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from db.db_connection import create_client
|
||||||
|
from utils.data_utils import (
|
||||||
|
get_stock_data, validate_signal_date, print_signal,
|
||||||
|
save_signals_to_csv, get_qualified_stocks
|
||||||
|
)
|
||||||
|
from utils.scanner_utils import initialize_scanner, process_signal_data
|
||||||
|
from trading.position_calculator import PositionCalculator
|
||||||
|
|
||||||
|
def calculate_heikin_ashi(df: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
"""Calculate Heikin Ashi candles from regular OHLC data"""
|
||||||
|
ha_close = (df['open'] + df['high'] + df['low'] + df['close']) / 4
|
||||||
|
ha_open = pd.Series(index=df.index)
|
||||||
|
ha_open.iloc[0] = df['open'].iloc[0]
|
||||||
|
for i in range(1, len(df)):
|
||||||
|
ha_open.iloc[i] = (ha_open.iloc[i-1] + ha_close.iloc[i-1]) / 2
|
||||||
|
ha_high = df[['high', 'open', 'close']].max(axis=1)
|
||||||
|
ha_low = df[['low', 'open', 'close']].min(axis=1)
|
||||||
|
|
||||||
|
return pd.DataFrame({
|
||||||
|
'ha_open': ha_open,
|
||||||
|
'ha_high': ha_high,
|
||||||
|
'ha_low': ha_low,
|
||||||
|
'ha_close': ha_close
|
||||||
|
})
|
||||||
|
|
||||||
|
def check_entry_signal(df: pd.DataFrame) -> list:
|
||||||
|
"""
|
||||||
|
Check for bullish Heikin Ashi signals
|
||||||
|
|
||||||
|
Args:
|
||||||
|
df (pd.DataFrame): DataFrame with price data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List of tuples (signal, date, signal_data) for each signal found
|
||||||
|
"""
|
||||||
|
if len(df) < 3: # Need at least 3 bars for comparison
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Calculate Heikin Ashi values
|
||||||
|
ha_df = calculate_heikin_ashi(df)
|
||||||
|
signals = []
|
||||||
|
|
||||||
|
# Start from index 2 to compare with previous candles
|
||||||
|
for i in range(2, len(df)):
|
||||||
|
current = ha_df.iloc[i]
|
||||||
|
prev = ha_df.iloc[i-1]
|
||||||
|
prev2 = ha_df.iloc[i-2]
|
||||||
|
|
||||||
|
# Bullish signal conditions:
|
||||||
|
# 1. Current candle is bullish (close > open)
|
||||||
|
# 2. Previous candle was bullish
|
||||||
|
# 3. Previous to previous candle was bearish (transition point)
|
||||||
|
if (current['ha_close'] > current['ha_open'] and
|
||||||
|
prev['ha_close'] > prev['ha_open'] and
|
||||||
|
prev2['ha_close'] < prev2['ha_open']):
|
||||||
|
|
||||||
|
signal_data = {
|
||||||
|
'price': df.iloc[i]['close'],
|
||||||
|
'ha_open': current['ha_open'],
|
||||||
|
'ha_close': current['ha_close'],
|
||||||
|
'ha_high': current['ha_high'],
|
||||||
|
'ha_low': current['ha_low']
|
||||||
|
}
|
||||||
|
signals.append((True, df.iloc[i]['date'], signal_data))
|
||||||
|
|
||||||
|
return signals
|
||||||
|
|
||||||
|
def run_heikin_ashi_scanner(min_price: float, max_price: float, min_volume: int,
|
||||||
|
portfolio_size: float = None, interval: str = "1d",
|
||||||
|
start_date: datetime = None, end_date: datetime = None) -> None:
|
||||||
|
"""
|
||||||
|
Run Heikin Ashi scanner to find bullish reversal patterns
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Initialize scanner components
|
||||||
|
interval, start_date, end_date, qualified_stocks, calculator = initialize_scanner(
|
||||||
|
min_price=min_price,
|
||||||
|
max_price=max_price,
|
||||||
|
min_volume=min_volume,
|
||||||
|
portfolio_size=portfolio_size,
|
||||||
|
interval=interval,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date
|
||||||
|
)
|
||||||
|
|
||||||
|
if not qualified_stocks:
|
||||||
|
return
|
||||||
|
|
||||||
|
bullish_signals = []
|
||||||
|
|
||||||
|
for ticker, current_price, current_volume, last_update, stock_type in qualified_stocks:
|
||||||
|
try:
|
||||||
|
df = get_stock_data(ticker, start_date, end_date, interval)
|
||||||
|
|
||||||
|
if df.empty or len(df) < 3: # Need at least 3 bars
|
||||||
|
continue
|
||||||
|
|
||||||
|
signals = check_entry_signal(df)
|
||||||
|
for signal, signal_date, signal_data in signals:
|
||||||
|
signal_data['date'] = signal_date
|
||||||
|
entry_data = process_signal_data(
|
||||||
|
ticker, signal_data, current_volume,
|
||||||
|
last_update, stock_type, calculator
|
||||||
|
)
|
||||||
|
bullish_signals.append(entry_data)
|
||||||
|
print_signal(entry_data, "🟢")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing {ticker}: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
save_signals_to_csv(bullish_signals, 'heikin_ashi')
|
||||||
|
return bullish_signals
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error during scan: {str(e)}")
|
||||||
|
return []
|
||||||
Loading…
Reference in New Issue
Block a user