|
|
| import MetaTrader5 as mt5
|
| import pandas as pd
|
| import numpy as np
|
| import plotly.graph_objects as go
|
| from datetime import datetime, timedelta
|
|
|
|
|
|
|
|
|
| SYMBOL = "XAUUSDc"
|
| TIMEFRAME = mt5.TIMEFRAME_M3
|
| N_BARS = 2000
|
| VG = None
|
| VG_MULTIPLIER = 0.5
|
| VOLUME_BOOM_FACTOR = 2
|
| VOLUME_BOOM_LOOKBACK = 10
|
|
|
|
|
| VTR_SPECS = {
|
| "VTR_3_12": (3, 12),
|
| "VTR_10_1": (10, 1),
|
| "VTR_11_2": (11, 2),
|
| }
|
|
|
| WILDER_PERIOD = 14
|
| DMAG_THRESHOLD = 20
|
| VERBOSE = True
|
|
|
|
|
|
|
|
|
| def to_dataframe(mt5_rates):
|
| df = pd.DataFrame(mt5_rates)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=False)
|
| return df
|
|
|
| def true_range(high, low, close_prev):
|
| return np.maximum.reduce([
|
| high - low,
|
| np.abs(high - close_prev),
|
| np.abs(low - close_prev)
|
| ])
|
|
|
| def wilder_smoothing(series, period):
|
| """
|
| Wilder smoothing:
|
| first value = series[:period].sum()
|
| subsequent: prev_smoothed - (prev_smoothed / period) + current
|
| This returns an array aligned with original series (np.nan for first entries before period)
|
| """
|
| series = np.asarray(series, dtype=float)
|
| out = np.full_like(series, np.nan)
|
| if len(series) < period:
|
| return out
|
|
|
| first = series[:period].sum()
|
| out[period-1] = first
|
| prev = first
|
| for i in range(period, len(series)):
|
| curr = series[i]
|
| prev = prev - (prev / period) + curr
|
| out[i] = prev
|
| return out
|
|
|
|
|
|
|
|
|
| if not mt5.initialize():
|
| raise RuntimeError("Failed to initialize MT5. Make sure terminal is running and logged in.")
|
|
|
|
|
| rates = mt5.copy_rates_from_pos(SYMBOL, TIMEFRAME, 0, N_BARS)
|
| if rates is None or len(rates) == 0:
|
| mt5.shutdown()
|
| raise RuntimeError(f"No data returned for {SYMBOL}. Check symbol name and MT5 market watch.")
|
|
|
| df = to_dataframe(rates)
|
|
|
| df = pd.DataFrame({
|
| 'time': pd.to_datetime(df['time'], unit='s'),
|
| 'open': df['open'],
|
| 'high': df['high'],
|
| 'low': df['low'],
|
| 'close': df['close'],
|
| 'tick_volume': df['tick_volume'],
|
| })
|
| df.set_index('time', inplace=True)
|
| df.sort_index(inplace=True)
|
|
|
|
|
| high = df['high'].values
|
| low = df['low'].values
|
| close = df['close'].values
|
| vol = df['tick_volume'].values
|
|
|
|
|
|
|
|
|
| if VG is None:
|
| med = np.median(vol[-100:]) if len(vol) >= 100 else np.median(vol)
|
| VG = max(1.0, med * VG_MULTIPLIER)
|
| if VERBOSE:
|
| print(f"Volume Gate (VG) set to: {VG:.2f}")
|
|
|
|
|
| volume_gate = vol >= VG
|
|
|
|
|
| vol_sum_prev_k = np.array([np.sum(vol[max(0,i-VOLUME_BOOM_LOOKBACK):i]) for i in range(len(vol))])
|
| volume_boom = vol >= (VOLUME_BOOM_FACTOR * (vol_sum_prev_k))
|
|
|
| volume_boom[:VOLUME_BOOM_LOOKBACK+1] = False
|
|
|
| df['volume_gate'] = volume_gate
|
| df['volume_boom'] = volume_boom
|
|
|
|
|
|
|
|
|
|
|
| close_prev = np.roll(close, 1)
|
| close_prev[0] = close[0]
|
| TR = true_range(high, low, close_prev)
|
| df['TR'] = TR
|
|
|
|
|
| for name, (m, l) in VTR_SPECS.items():
|
|
|
| if l <= 0:
|
| df[name] = 0.0
|
| else:
|
| df[name] = m * df['TR'].rolling(window=l, min_periods=l).mean()
|
|
|
| recent_TR_mean = df['TR'].rolling(window=50, min_periods=1).mean()
|
|
|
| VTR_THRESHOLD_MULT = 1.2
|
| df['vtr_threshold'] = recent_TR_mean * VTR_THRESHOLD_MULT
|
|
|
|
|
| df['vtr_all_above'] = True
|
| for name in VTR_SPECS.keys():
|
| df['vtr_all_above'] &= (df[name] > df['vtr_threshold'])
|
|
|
|
|
|
|
|
|
|
|
| up_move = high - np.roll(high, 1)
|
| down_move = np.roll(low, 1) - low
|
| up_move[0] = 0.0
|
| down_move[0] = 0.0
|
| plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0.0)
|
| minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0.0)
|
| df['+DM_raw'] = plus_dm
|
| df['-DM_raw'] = minus_dm
|
|
|
|
|
| smoothed_TR = wilder_smoothing(df['TR'].values, WILDER_PERIOD)
|
| smoothed_plus_dm = wilder_smoothing(df['+DM_raw'].values, WILDER_PERIOD)
|
| smoothed_minus_dm = wilder_smoothing(df['-DM_raw'].values, WILDER_PERIOD)
|
|
|
|
|
| df['TR_smooth'] = smoothed_TR
|
| df['+DM_smooth'] = smoothed_plus_dm
|
| df['-DM_smooth'] = smoothed_minus_dm
|
|
|
|
|
| df['+DI'] = 100.0 * (df['+DM_smooth'] / df['TR_smooth'])
|
| df['-DI'] = 100.0 * (df['-DM_smooth'] / df['TR_smooth'])
|
|
|
|
|
| df['DX'] = 100.0 * (np.abs(df['+DI'] - df['-DI']) / (df['+DI'] + df['-DI']))
|
|
|
| df['DX'] = df['DX'].fillna(0.0)
|
|
|
|
|
|
|
| dx_vals = df['DX'].values
|
| DMag = wilder_smoothing(dx_vals, WILDER_PERIOD)
|
| df['DMag'] = DMag
|
|
|
|
|
| df['DMag_rising'] = df['DMag'] > np.roll(df['DMag'], 1)
|
| df['DMag_rising'].fillna(False, inplace=True)
|
|
|
|
|
|
|
|
|
|
|
| df['direction_bull'] = df['+DI'] > df['-DI']
|
| df['direction_bear'] = df['+DI'] < df['-DI']
|
|
|
|
|
| df['signal_buy'] = (
|
| df['volume_gate'] &
|
| df['volume_boom'] &
|
| df['vtr_all_above'] &
|
| df['direction_bull'] &
|
| (df['DMag'] > DMAG_THRESHOLD) &
|
| df['DMag_rising']
|
| )
|
|
|
| df['signal_sell'] = (
|
| df['volume_gate'] &
|
| df['volume_boom'] &
|
| df['vtr_all_above'] &
|
| df['direction_bear'] &
|
| (df['DMag'] > DMAG_THRESHOLD) &
|
| df['DMag_rising']
|
| )
|
|
|
|
|
| valid_idx = ~df[['TR', '+DM_smooth', '-DM_smooth', 'TR_smooth', 'DMag']].isnull().any(axis=1)
|
| df.loc[~valid_idx, ['signal_buy','signal_sell']] = False
|
|
|
|
|
|
|
|
|
| print("Preparing plot...")
|
|
|
| candlestick = go.Candlestick(
|
| x=df.index,
|
| open=df['open'],
|
| high=df['high'],
|
| low=df['low'],
|
| close=df['close'],
|
| name='Price',
|
| increasing_line_color='green',
|
| decreasing_line_color='red',
|
| opacity=0.9
|
| )
|
|
|
| volume_bar = go.Bar(
|
| x=df.index,
|
| y=df['tick_volume'],
|
| name='Tick Volume',
|
| yaxis='y2',
|
| opacity=0.6
|
| )
|
|
|
|
|
| buy_markers = go.Scatter(
|
| x=df.index[df['signal_buy']],
|
| y=df['low'][df['signal_buy']] * 0.997,
|
| mode='markers',
|
| marker=dict(symbol='triangle-up', size=12, color='lime'),
|
| name='Buy Signal'
|
| )
|
| sell_markers = go.Scatter(
|
| x=df.index[df['signal_sell']],
|
| y=df['high'][df['signal_sell']] * 1.003,
|
| mode='markers',
|
| marker=dict(symbol='triangle-down', size=12, color='magenta'),
|
| name='Sell Signal'
|
| )
|
|
|
|
|
| vtr_traces = []
|
| for name in VTR_SPECS.keys():
|
| vtr_traces.append(go.Scatter(
|
| x=df.index,
|
| y=df[name],
|
| mode='lines',
|
| name=name,
|
| line=dict(width=1),
|
| yaxis='y3'
|
| ))
|
|
|
| dmag_trace = go.Scatter(
|
| x=df.index,
|
| y=df['DMag'],
|
| mode='lines',
|
| name='DMag (ADX)',
|
| line=dict(width=2, dash='dash'),
|
| yaxis='y4'
|
| )
|
|
|
| layout = go.Layout(
|
| title=f"{SYMBOL} - Signals demo (no backtest) — last {len(df)} bars",
|
| xaxis=dict(rangeslider=dict(visible=False)),
|
| yaxis=dict(title="Price"),
|
| yaxis2=dict(title="Volume", overlaying='y', side='right', showgrid=False, position=0.98),
|
| yaxis3=dict(title="VTRs (scaled)", overlaying='y', side='left', anchor='free', position=0.02, showgrid=False),
|
| yaxis4=dict(title="DMag", overlaying='y', side='right', anchor='free', position=0.98, showgrid=False),
|
| legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1),
|
| height=800
|
| )
|
|
|
| fig = go.Figure(data=[candlestick, volume_bar, buy_markers, sell_markers] + vtr_traces + [dmag_trace], layout=layout)
|
|
|
|
|
| fig.add_hline(y=VG, line=dict(color='gray', dash='dot'), annotation_text="Volume Gate", annotation_position="top left")
|
|
|
|
|
| fig.show()
|
|
|
|
|
|
|
|
|
| mt5.shutdown()
|
|
|
| print("Done. Signals plotted. Review the chart to inspect markers and indicator overlays.")
|
|
|
|
|
| import MetaTrader5 as mt5
|
| import pandas as pd
|
| import numpy as np
|
| import plotly.graph_objects as go
|
| from datetime import datetime, timedelta
|
|
|
|
|
|
|
|
|
| SYMBOL = "XAUUSDc"
|
| TIMEFRAME = mt5.TIMEFRAME_M3
|
| N_BARS = 2000
|
| VG = None
|
| VG_MULTIPLIER = 0.5
|
| VOLUME_BOOM_FACTOR = 2
|
| VOLUME_BOOM_LOOKBACK = 10
|
|
|
|
|
| VTR_SPECS = {
|
| "VTR_3_12": (3, 12),
|
| "VTR_10_1": (10, 1),
|
| "VTR_11_2": (11, 2),
|
| }
|
|
|
| WILDER_PERIOD = 14
|
| DMAG_THRESHOLD = 20
|
| VERBOSE = True
|
|
|
|
|
|
|
|
|
| def to_dataframe(mt5_rates):
|
| df = pd.DataFrame(mt5_rates)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=False)
|
| return df
|
|
|
| def true_range(high, low, close_prev):
|
| return np.maximum.reduce([
|
| high - low,
|
| np.abs(high - close_prev),
|
| np.abs(low - close_prev)
|
| ])
|
|
|
| def wilder_smoothing(series, period):
|
| """
|
| Wilder smoothing:
|
| first value = series[:period].sum()
|
| subsequent: prev_smoothed - (prev_smoothed / period) + current
|
| This returns an array aligned with original series (np.nan for first entries before period)
|
| """
|
| series = np.asarray(series, dtype=float)
|
| out = np.full_like(series, np.nan)
|
| if len(series) < period:
|
| return out
|
|
|
| first = series[:period].sum()
|
| out[period-1] = first
|
| prev = first
|
| for i in range(period, len(series)):
|
| curr = series[i]
|
| prev = prev - (prev / period) + curr
|
| out[i] = prev
|
| return out
|
|
|
|
|
|
|
|
|
| if not mt5.initialize():
|
| raise RuntimeError("Failed to initialize MT5. Make sure terminal is running and logged in.")
|
|
|
|
|
| rates = mt5.copy_rates_from_pos(SYMBOL, TIMEFRAME, 0, N_BARS)
|
| if rates is None or len(rates) == 0:
|
| mt5.shutdown()
|
| raise RuntimeError(f"No data returned for {SYMBOL}. Check symbol name and MT5 market watch.")
|
|
|
| df = to_dataframe(rates)
|
|
|
| df = pd.DataFrame({
|
| 'time': pd.to_datetime(df['time'], unit='s'),
|
| 'open': df['open'],
|
| 'high': df['high'],
|
| 'low': df['low'],
|
| 'close': df['close'],
|
| 'tick_volume': df['tick_volume'],
|
| })
|
| df.set_index('time', inplace=True)
|
| df.sort_index(inplace=True)
|
|
|
|
|
| high = df['high'].values
|
| low = df['low'].values
|
| close = df['close'].values
|
| vol = df['tick_volume'].values
|
|
|
|
|
|
|
|
|
| if VG is None:
|
| med = np.median(vol[-100:]) if len(vol) >= 100 else np.median(vol)
|
| VG = max(1.0, med * VG_MULTIPLIER)
|
| if VERBOSE:
|
| print(f"Volume Gate (VG) set to: {VG:.2f}")
|
|
|
|
|
| volume_gate = vol >= VG
|
|
|
|
|
| vol_sum_prev_k = np.array([np.sum(vol[max(0,i-VOLUME_BOOM_LOOKBACK):i]) for i in range(len(vol))])
|
| volume_boom = vol >= (VOLUME_BOOM_FACTOR * (vol_sum_prev_k))
|
|
|
| volume_boom[:VOLUME_BOOM_LOOKBACK+1] = False
|
|
|
| df['volume_gate'] = volume_gate
|
| df['volume_boom'] = volume_boom
|
|
|
|
|
|
|
|
|
|
|
| close_prev = np.roll(close, 1)
|
| close_prev[0] = close[0]
|
| TR = true_range(high, low, close_prev)
|
| df['TR'] = TR
|
|
|
|
|
| for name, (m, l) in VTR_SPECS.items():
|
|
|
| if l <= 0:
|
| df[name] = 0.0
|
| else:
|
| df[name] = m * df['TR'].rolling(window=l, min_periods=l).mean()
|
|
|
| recent_TR_mean = df['TR'].rolling(window=50, min_periods=1).mean()
|
|
|
| VTR_THRESHOLD_MULT = 1.2
|
| df['vtr_threshold'] = recent_TR_mean * VTR_THRESHOLD_MULT
|
|
|
|
|
| df['vtr_all_above'] = True
|
| for name in VTR_SPECS.keys():
|
| df['vtr_all_above'] &= (df[name] > df['vtr_threshold'])
|
|
|
|
|
|
|
|
|
|
|
| up_move = high - np.roll(high, 1)
|
| down_move = np.roll(low, 1) - low
|
| up_move[0] = 0.0
|
| down_move[0] = 0.0
|
| plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0.0)
|
| minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0.0)
|
| df['+DM_raw'] = plus_dm
|
| df['-DM_raw'] = minus_dm
|
|
|
|
|
| smoothed_TR = wilder_smoothing(df['TR'].values, WILDER_PERIOD)
|
| smoothed_plus_dm = wilder_smoothing(df['+DM_raw'].values, WILDER_PERIOD)
|
| smoothed_minus_dm = wilder_smoothing(df['-DM_raw'].values, WILDER_PERIOD)
|
|
|
|
|
| df['TR_smooth'] = smoothed_TR
|
| df['+DM_smooth'] = smoothed_plus_dm
|
| df['-DM_smooth'] = smoothed_minus_dm
|
|
|
|
|
| df['+DI'] = 100.0 * (df['+DM_smooth'] / df['TR_smooth'])
|
| df['-DI'] = 100.0 * (df['-DM_smooth'] / df['TR_smooth'])
|
|
|
|
|
| df['DX'] = 100.0 * (np.abs(df['+DI'] - df['-DI']) / (df['+DI'] + df['-DI']))
|
|
|
| df['DX'] = df['DX'].fillna(0.0)
|
|
|
|
|
|
|
| dx_vals = df['DX'].values
|
| DMag = wilder_smoothing(dx_vals, WILDER_PERIOD)
|
| df['DMag'] = DMag
|
|
|
|
|
| df['DMag_rising'] = df['DMag'] > np.roll(df['DMag'], 1)
|
| df['DMag_rising'].fillna(False, inplace=True)
|
|
|
|
|
|
|
|
|
|
|
| df['direction_bull'] = df['+DI'] > df['-DI']
|
| df['direction_bear'] = df['+DI'] < df['-DI']
|
|
|
|
|
| df['signal_buy'] = (
|
| df['volume_gate'] &
|
| df['volume_boom'] &
|
| df['vtr_all_above'] &
|
| df['direction_bull'] &
|
| (df['DMag'] > DMAG_THRESHOLD) &
|
| df['DMag_rising']
|
| )
|
|
|
| df['signal_sell'] = (
|
| df['volume_gate'] &
|
| df['volume_boom'] &
|
| df['vtr_all_above'] &
|
| df['direction_bear'] &
|
| (df['DMag'] > DMAG_THRESHOLD) &
|
| df['DMag_rising']
|
| )
|
|
|
|
|
| valid_idx = ~df[['TR', '+DM_smooth', '-DM_smooth', 'TR_smooth', 'DMag']].isnull().any(axis=1)
|
| df.loc[~valid_idx, ['signal_buy','signal_sell']] = False
|
|
|
|
|
|
|
|
|
| print("Preparing plot...")
|
|
|
| candlestick = go.Candlestick(
|
| x=df.index,
|
| open=df['open'],
|
| high=df['high'],
|
| low=df['low'],
|
| close=df['close'],
|
| name='Price',
|
| increasing_line_color='green',
|
| decreasing_line_color='red',
|
| opacity=0.9
|
| )
|
|
|
| volume_bar = go.Bar(
|
| x=df.index,
|
| y=df['tick_volume'],
|
| name='Tick Volume',
|
| yaxis='y2',
|
| opacity=0.6
|
| )
|
|
|
|
|
| buy_markers = go.Scatter(
|
| x=df.index[df['signal_buy']],
|
| y=df['low'][df['signal_buy']] * 0.997,
|
| mode='markers',
|
| marker=dict(symbol='triangle-up', size=12, color='lime'),
|
| name='Buy Signal'
|
| )
|
| sell_markers = go.Scatter(
|
| x=df.index[df['signal_sell']],
|
| y=df['high'][df['signal_sell']] * 1.003,
|
| mode='markers',
|
| marker=dict(symbol='triangle-down', size=12, color='magenta'),
|
| name='Sell Signal'
|
| )
|
|
|
|
|
| vtr_traces = []
|
| for name in VTR_SPECS.keys():
|
| vtr_traces.append(go.Scatter(
|
| x=df.index,
|
| y=df[name],
|
| mode='lines',
|
| name=name,
|
| line=dict(width=1),
|
| yaxis='y3'
|
| ))
|
|
|
| dmag_trace = go.Scatter(
|
| x=df.index,
|
| y=df['DMag'],
|
| mode='lines',
|
| name='DMag (ADX)',
|
| line=dict(width=2, dash='dash'),
|
| yaxis='y4'
|
| )
|
|
|
| layout = go.Layout(
|
| title=f"{SYMBOL} - Signals demo (no backtest) — last {len(df)} bars",
|
| xaxis=dict(rangeslider=dict(visible=False)),
|
| yaxis=dict(title="Price"),
|
| yaxis2=dict(title="Volume", overlaying='y', side='right', showgrid=False, position=0.98),
|
| yaxis3=dict(title="VTRs (scaled)", overlaying='y', side='left', anchor='free', position=0.02, showgrid=False),
|
| yaxis4=dict(title="DMag", overlaying='y', side='right', anchor='free', position=0.98, showgrid=False),
|
| legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1),
|
| height=800
|
| )
|
|
|
| fig = go.Figure(data=[candlestick, volume_bar, buy_markers, sell_markers] + vtr_traces + [dmag_trace], layout=layout)
|
|
|
|
|
| fig.add_hline(y=VG, line=dict(color='gray', dash='dot'), annotation_text="Volume Gate", annotation_position="top left")
|
|
|
|
|
| fig.show()
|
|
|
|
|
|
|
|
|
| mt5.shutdown()
|
|
|
| print("Done. Signals plotted. Review the chart to inspect markers and indicator overlays.")
|
|
|
| import MetaTrader5 as mt5
|
| import pandas as pd
|
| import numpy as np
|
| import plotly.graph_objects as go
|
| from datetime import datetime, timedelta
|
|
|
|
|
|
|
|
|
| SYMBOL = "XAUUSDc"
|
| TIMEFRAME = mt5.TIMEFRAME_M3
|
| N_BARS = 2000
|
| VG = None
|
| VG_MULTIPLIER = 0.5
|
| VOLUME_BOOM_FACTOR = 2
|
| VOLUME_BOOM_LOOKBACK = 10
|
|
|
|
|
| VTR_SPECS = {
|
| "VTR_3_12": (3, 12),
|
| "VTR_10_1": (10, 1),
|
| "VTR_11_2": (11, 2),
|
| }
|
|
|
| WILDER_PERIOD = 14
|
| DMAG_THRESHOLD = 20
|
| VERBOSE = True
|
|
|
|
|
|
|
|
|
| def to_dataframe(mt5_rates):
|
| df = pd.DataFrame(mt5_rates)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=False)
|
| return df
|
|
|
| def true_range(high, low, close_prev):
|
| return np.maximum.reduce([
|
| high - low,
|
| np.abs(high - close_prev),
|
| np.abs(low - close_prev)
|
| ])
|
|
|
| def wilder_smoothing(series, period):
|
| """
|
| Wilder smoothing:
|
| first value = series[:period].sum()
|
| subsequent: prev_smoothed - (prev_smoothed / period) + current
|
| This returns an array aligned with original series (np.nan for first entries before period)
|
| """
|
| series = np.asarray(series, dtype=float)
|
| out = np.full_like(series, np.nan)
|
| if len(series) < period:
|
| return out
|
|
|
| first = series[:period].sum()
|
| out[period-1] = first
|
| prev = first
|
| for i in range(period, len(series)):
|
| curr = series[i]
|
| prev = prev - (prev / period) + curr
|
| out[i] = prev
|
| return out
|
|
|
|
|
|
|
|
|
| if not mt5.initialize():
|
| raise RuntimeError("Failed to initialize MT5. Make sure terminal is running and logged in.")
|
|
|
|
|
| rates = mt5.copy_rates_from_pos(SYMBOL, TIMEFRAME, 0, N_BARS)
|
| if rates is None or len(rates) == 0:
|
| mt5.shutdown()
|
| raise RuntimeError(f"No data returned for {SYMBOL}. Check symbol name and MT5 market watch.")
|
|
|
| df = to_dataframe(rates)
|
|
|
| df = pd.DataFrame({
|
| 'time': pd.to_datetime(df['time'], unit='s'),
|
| 'open': df['open'],
|
| 'high': df['high'],
|
| 'low': df['low'],
|
| 'close': df['close'],
|
| 'tick_volume': df['tick_volume'],
|
| })
|
| df.set_index('time', inplace=True)
|
| df.sort_index(inplace=True)
|
|
|
|
|
| high = df['high'].values
|
| low = df['low'].values
|
| close = df['close'].values
|
| vol = df['tick_volume'].values
|
|
|
|
|
|
|
|
|
| if VG is None:
|
| med = np.median(vol[-100:]) if len(vol) >= 100 else np.median(vol)
|
| VG = max(1.0, med * VG_MULTIPLIER)
|
| if VERBOSE:
|
| print(f"Volume Gate (VG) set to: {VG:.2f}")
|
|
|
|
|
| volume_gate = vol >= VG
|
|
|
|
|
| vol_sum_prev_k = np.array([np.sum(vol[max(0,i-VOLUME_BOOM_LOOKBACK):i]) for i in range(len(vol))])
|
| volume_boom = vol >= (VOLUME_BOOM_FACTOR * (vol_sum_prev_k))
|
|
|
| volume_boom[:VOLUME_BOOM_LOOKBACK+1] = False
|
|
|
| df['volume_gate'] = volume_gate
|
| df['volume_boom'] = volume_boom
|
|
|
|
|
|
|
|
|
|
|
| close_prev = np.roll(close, 1)
|
| close_prev[0] = close[0]
|
| TR = true_range(high, low, close_prev)
|
| df['TR'] = TR
|
|
|
|
|
| for name, (m, l) in VTR_SPECS.items():
|
|
|
| if l <= 0:
|
| df[name] = 0.0
|
| else:
|
| df[name] = m * df['TR'].rolling(window=l, min_periods=l).mean()
|
|
|
| recent_TR_mean = df['TR'].rolling(window=50, min_periods=1).mean()
|
|
|
| VTR_THRESHOLD_MULT = 1.2
|
| df['vtr_threshold'] = recent_TR_mean * VTR_THRESHOLD_MULT
|
|
|
|
|
| df['vtr_all_above'] = True
|
| for name in VTR_SPECS.keys():
|
| df['vtr_all_above'] &= (df[name] > df['vtr_threshold'])
|
|
|
|
|
|
|
|
|
|
|
| up_move = high - np.roll(high, 1)
|
| down_move = np.roll(low, 1) - low
|
| up_move[0] = 0.0
|
| down_move[0] = 0.0
|
| plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0.0)
|
| minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0.0)
|
| df['+DM_raw'] = plus_dm
|
| df['-DM_raw'] = minus_dm
|
|
|
|
|
| smoothed_TR = wilder_smoothing(df['TR'].values, WILDER_PERIOD)
|
| smoothed_plus_dm = wilder_smoothing(df['+DM_raw'].values, WILDER_PERIOD)
|
| smoothed_minus_dm = wilder_smoothing(df['-DM_raw'].values, WILDER_PERIOD)
|
|
|
|
|
| df['TR_smooth'] = smoothed_TR
|
| df['+DM_smooth'] = smoothed_plus_dm
|
| df['-DM_smooth'] = smoothed_minus_dm
|
|
|
|
|
| df['+DI'] = 100.0 * (df['+DM_smooth'] / df['TR_smooth'])
|
| df['-DI'] = 100.0 * (df['-DM_smooth'] / df['TR_smooth'])
|
|
|
|
|
| df['DX'] = 100.0 * (np.abs(df['+DI'] - df['-DI']) / (df['+DI'] + df['-DI']))
|
|
|
| df['DX'] = df['DX'].fillna(0.0)
|
|
|
|
|
|
|
| dx_vals = df['DX'].values
|
| DMag = wilder_smoothing(dx_vals, WILDER_PERIOD)
|
| df['DMag'] = DMag
|
|
|
|
|
| df['DMag_rising'] = df['DMag'] > np.roll(df['DMag'], 1)
|
| df['DMag_rising'].fillna(False, inplace=True)
|
|
|
|
|
|
|
|
|
|
|
| df['direction_bull'] = df['+DI'] > df['-DI']
|
| df['direction_bear'] = df['+DI'] < df['-DI']
|
|
|
|
|
| df['signal_buy'] = (
|
| df['volume_gate'] &
|
| df['volume_boom'] &
|
| df['vtr_all_above'] &
|
| df['direction_bull'] &
|
| (df['DMag'] > DMAG_THRESHOLD) &
|
| df['DMag_rising']
|
| )
|
|
|
| df['signal_sell'] = (
|
| df['volume_gate'] &
|
| df['volume_boom'] &
|
| df['vtr_all_above'] &
|
| df['direction_bear'] &
|
| (df['DMag'] > DMAG_THRESHOLD) &
|
| df['DMag_rising']
|
| )
|
|
|
|
|
| valid_idx = ~df[['TR', '+DM_smooth', '-DM_smooth', 'TR_smooth', 'DMag']].isnull().any(axis=1)
|
| df.loc[~valid_idx, ['signal_buy','signal_sell']] = False
|
|
|
|
|
|
|
|
|
| print("Preparing plot...")
|
|
|
| candlestick = go.Candlestick(
|
| x=df.index,
|
| open=df['open'],
|
| high=df['high'],
|
| low=df['low'],
|
| close=df['close'],
|
| name='Price',
|
| increasing_line_color='green',
|
| decreasing_line_color='red',
|
| opacity=0.9
|
| )
|
|
|
| volume_bar = go.Bar(
|
| x=df.index,
|
| y=df['tick_volume'],
|
| name='Tick Volume',
|
| yaxis='y2',
|
| opacity=0.6
|
| )
|
|
|
|
|
| buy_markers = go.Scatter(
|
| x=df.index[df['signal_buy']],
|
| y=df['low'][df['signal_buy']] * 0.997,
|
| mode='markers',
|
| marker=dict(symbol='triangle-up', size=12, color='lime'),
|
| name='Buy Signal'
|
| )
|
| sell_markers = go.Scatter(
|
| x=df.index[df['signal_sell']],
|
| y=df['high'][df['signal_sell']] * 1.003,
|
| mode='markers',
|
| marker=dict(symbol='triangle-down', size=12, color='magenta'),
|
| name='Sell Signal'
|
| )
|
|
|
|
|
| vtr_traces = []
|
| for name in VTR_SPECS.keys():
|
| vtr_traces.append(go.Scatter(
|
| x=df.index,
|
| y=df[name],
|
| mode='lines',
|
| name=name,
|
| line=dict(width=1),
|
| yaxis='y3'
|
| ))
|
|
|
| dmag_trace = go.Scatter(
|
| x=df.index,
|
| y=df['DMag'],
|
| mode='lines',
|
| name='DMag (ADX)',
|
| line=dict(width=2, dash='dash'),
|
| yaxis='y4'
|
| )
|
|
|
| layout = go.Layout(
|
| title=f"{SYMBOL} - Signals demo (no backtest) — last {len(df)} bars",
|
| xaxis=dict(rangeslider=dict(visible=False)),
|
| yaxis=dict(title="Price"),
|
| yaxis2=dict(title="Volume", overlaying='y', side='right', showgrid=False, position=0.98),
|
| yaxis3=dict(title="VTRs (scaled)", overlaying='y', side='left', anchor='free', position=0.02, showgrid=False),
|
| yaxis4=dict(title="DMag", overlaying='y', side='right', anchor='free', position=0.98, showgrid=False),
|
| legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1),
|
| height=800
|
| )
|
|
|
| fig = go.Figure(data=[candlestick, volume_bar, buy_markers, sell_markers] + vtr_traces + [dmag_trace], layout=layout)
|
|
|
|
|
| fig.add_hline(y=VG, line=dict(color='gray', dash='dot'), annotation_text="Volume Gate", annotation_position="top left")
|
|
|
|
|
| fig.show()
|
|
|
|
|
|
|
|
|
| mt5.shutdown()
|
|
|
| print("Done. Signals plotted. Review the chart to inspect markers and indicator overlays.")
|
|
|
| import MetaTrader5 as mt5
|
| import pandas as pd
|
| import numpy as np
|
| import plotly.graph_objects as go
|
| from datetime import datetime, timedelta
|
|
|
|
|
|
|
|
|
| SYMBOL = "XAUUSDc"
|
| TIMEFRAME = mt5.TIMEFRAME_M3
|
| N_BARS = 2000
|
| VG = None
|
| VG_MULTIPLIER = 0.5
|
| VOLUME_BOOM_FACTOR = 2
|
| VOLUME_BOOM_LOOKBACK = 10
|
|
|
|
|
| VTR_SPECS = {
|
| "VTR_3_12": (3, 12),
|
| "VTR_10_1": (10, 1),
|
| "VTR_11_2": (11, 2),
|
| }
|
|
|
| WILDER_PERIOD = 14
|
| DMAG_THRESHOLD = 20
|
| VERBOSE = True
|
|
|
|
|
|
|
|
|
| def to_dataframe(mt5_rates):
|
| df = pd.DataFrame(mt5_rates)
|
| df['time'] = pd.to_datetime(df['time'], unit='s')
|
| df.set_index('time', inplace=False)
|
| return df
|
|
|
| def true_range(high, low, close_prev):
|
| return np.maximum.reduce([
|
| high - low,
|
| np.abs(high - close_prev),
|
| np.abs(low - close_prev)
|
| ])
|
|
|
| def wilder_smoothing(series, period):
|
| """
|
| Wilder smoothing:
|
| first value = series[:period].sum()
|
| subsequent: prev_smoothed - (prev_smoothed / period) + current
|
| This returns an array aligned with original series (np.nan for first entries before period)
|
| """
|
| series = np.asarray(series, dtype=float)
|
| out = np.full_like(series, np.nan)
|
| if len(series) < period:
|
| return out
|
|
|
| first = series[:period].sum()
|
| out[period-1] = first
|
| prev = first
|
| for i in range(period, len(series)):
|
| curr = series[i]
|
| prev = prev - (prev / period) + curr
|
| out[i] = prev
|
| return out
|
|
|
|
|
|
|
|
|
| if not mt5.initialize():
|
| raise RuntimeError("Failed to initialize MT5. Make sure terminal is running and logged in.")
|
|
|
|
|
| rates = mt5.copy_rates_from_pos(SYMBOL, TIMEFRAME, 0, N_BARS)
|
| if rates is None or len(rates) == 0:
|
| mt5.shutdown()
|
| raise RuntimeError(f"No data returned for {SYMBOL}. Check symbol name and MT5 market watch.")
|
|
|
| df = to_dataframe(rates)
|
|
|
| df = pd.DataFrame({
|
| 'time': pd.to_datetime(df['time'], unit='s'),
|
| 'open': df['open'],
|
| 'high': df['high'],
|
| 'low': df['low'],
|
| 'close': df['close'],
|
| 'tick_volume': df['tick_volume'],
|
| })
|
| df.set_index('time', inplace=True)
|
| df.sort_index(inplace=True)
|
|
|
|
|
| high = df['high'].values
|
| low = df['low'].values
|
| close = df['close'].values
|
| vol = df['tick_volume'].values
|
|
|
|
|
|
|
|
|
| if VG is None:
|
| med = np.median(vol[-100:]) if len(vol) >= 100 else np.median(vol)
|
| VG = max(1.0, med * VG_MULTIPLIER)
|
| if VERBOSE:
|
| print(f"Volume Gate (VG) set to: {VG:.2f}")
|
|
|
|
|
| volume_gate = vol >= VG
|
|
|
|
|
| vol_sum_prev_k = np.array([np.sum(vol[max(0,i-VOLUME_BOOM_LOOKBACK):i]) for i in range(len(vol))])
|
| volume_boom = vol >= (VOLUME_BOOM_FACTOR * (vol_sum_prev_k))
|
|
|
| volume_boom[:VOLUME_BOOM_LOOKBACK+1] = False
|
|
|
| df['volume_gate'] = volume_gate
|
| df['volume_boom'] = volume_boom
|
|
|
|
|
|
|
|
|
|
|
| close_prev = np.roll(close, 1)
|
| close_prev[0] = close[0]
|
| TR = true_range(high, low, close_prev)
|
| df['TR'] = TR
|
|
|
|
|
| for name, (m, l) in VTR_SPECS.items():
|
|
|
| if l <= 0:
|
| df[name] = 0.0
|
| else:
|
| df[name] = m * df['TR'].rolling(window=l, min_periods=l).mean()
|
|
|
| recent_TR_mean = df['TR'].rolling(window=50, min_periods=1).mean()
|
|
|
| VTR_THRESHOLD_MULT = 1.2
|
| df['vtr_threshold'] = recent_TR_mean * VTR_THRESHOLD_MULT
|
|
|
|
|
| df['vtr_all_above'] = True
|
| for name in VTR_SPECS.keys():
|
| df['vtr_all_above'] &= (df[name] > df['vtr_threshold'])
|
|
|
|
|
|
|
|
|
|
|
| up_move = high - np.roll(high, 1)
|
| down_move = np.roll(low, 1) - low
|
| up_move[0] = 0.0
|
| down_move[0] = 0.0
|
| plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0.0)
|
| minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0.0)
|
| df['+DM_raw'] = plus_dm
|
| df['-DM_raw'] = minus_dm
|
|
|
|
|
| smoothed_TR = wilder_smoothing(df['TR'].values, WILDER_PERIOD)
|
| smoothed_plus_dm = wilder_smoothing(df['+DM_raw'].values, WILDER_PERIOD)
|
| smoothed_minus_dm = wilder_smoothing(df['-DM_raw'].values, WILDER_PERIOD)
|
|
|
|
|
| df['TR_smooth'] = smoothed_TR
|
| df['+DM_smooth'] = smoothed_plus_dm
|
| df['-DM_smooth'] = smoothed_minus_dm
|
|
|
|
|
| df['+DI'] = 100.0 * (df['+DM_smooth'] / df['TR_smooth'])
|
| df['-DI'] = 100.0 * (df['-DM_smooth'] / df['TR_smooth'])
|
|
|
|
|
| df['DX'] = 100.0 * (np.abs(df['+DI'] - df['-DI']) / (df['+DI'] + df['-DI']))
|
|
|
| df['DX'] = df['DX'].fillna(0.0)
|
|
|
|
|
|
|
| dx_vals = df['DX'].values
|
| DMag = wilder_smoothing(dx_vals, WILDER_PERIOD)
|
| df['DMag'] = DMag
|
|
|
|
|
| df['DMag_rising'] = df['DMag'] > np.roll(df['DMag'], 1)
|
| df['DMag_rising'].fillna(False, inplace=True)
|
|
|
|
|
|
|
|
|
|
|
| df['direction_bull'] = df['+DI'] > df['-DI']
|
| df['direction_bear'] = df['+DI'] < df['-DI']
|
|
|
|
|
| df['signal_buy'] = (
|
| df['volume_gate'] &
|
| df['volume_boom'] &
|
| df['vtr_all_above'] &
|
| df['direction_bull'] &
|
| (df['DMag'] > DMAG_THRESHOLD) &
|
| df['DMag_rising']
|
| )
|
|
|
| df['signal_sell'] = (
|
| df['volume_gate'] &
|
| df['volume_boom'] &
|
| df['vtr_all_above'] &
|
| df['direction_bear'] &
|
| (df['DMag'] > DMAG_THRESHOLD) &
|
| df['DMag_rising']
|
| )
|
|
|
|
|
| valid_idx = ~df[['TR', '+DM_smooth', '-DM_smooth', 'TR_smooth', 'DMag']].isnull().any(axis=1)
|
| df.loc[~valid_idx, ['signal_buy','signal_sell']] = False
|
|
|
|
|
|
|
|
|
| print("Preparing plot...")
|
|
|
| candlestick = go.Candlestick(
|
| x=df.index,
|
| open=df['open'],
|
| high=df['high'],
|
| low=df['low'],
|
| close=df['close'],
|
| name='Price',
|
| increasing_line_color='green',
|
| decreasing_line_color='red',
|
| opacity=0.9
|
| )
|
|
|
| volume_bar = go.Bar(
|
| x=df.index,
|
| y=df['tick_volume'],
|
| name='Tick Volume',
|
| yaxis='y2',
|
| opacity=0.6
|
| )
|
|
|
|
|
| buy_markers = go.Scatter(
|
| x=df.index[df['signal_buy']],
|
| y=df['low'][df['signal_buy']] * 0.997,
|
| mode='markers',
|
| marker=dict(symbol='triangle-up', size=12, color='lime'),
|
| name='Buy Signal'
|
| )
|
| sell_markers = go.Scatter(
|
| x=df.index[df['signal_sell']],
|
| y=df['high'][df['signal_sell']] * 1.003,
|
| mode='markers',
|
| marker=dict(symbol='triangle-down', size=12, color='magenta'),
|
| name='Sell Signal'
|
| )
|
|
|
|
|
| vtr_traces = []
|
| for name in VTR_SPECS.keys():
|
| vtr_traces.append(go.Scatter(
|
| x=df.index,
|
| y=df[name],
|
| mode='lines',
|
| name=name,
|
| line=dict(width=1),
|
| yaxis='y3'
|
| ))
|
|
|
| dmag_trace = go.Scatter(
|
| x=df.index,
|
| y=df['DMag'],
|
| mode='lines',
|
| name='DMag (ADX)',
|
| line=dict(width=2, dash='dash'),
|
| yaxis='y4'
|
| )
|
|
|
| layout = go.Layout(
|
| title=f"{SYMBOL} - Signals demo (no backtest) — last {len(df)} bars",
|
| xaxis=dict(rangeslider=dict(visible=False)),
|
| yaxis=dict(title="Price"),
|
| yaxis2=dict(title="Volume", overlaying='y', side='right', showgrid=False, position=0.98),
|
| yaxis3=dict(title="VTRs (scaled)", overlaying='y', side='left', anchor='free', position=0.02, showgrid=False),
|
| yaxis4=dict(title="DMag", overlaying='y', side='right', anchor='free', position=0.98, showgrid=False),
|
| legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1),
|
| height=800
|
| )
|
|
|
| fig = go.Figure(data=[candlestick, volume_bar, buy_markers, sell_markers] + vtr_traces + [dmag_trace], layout=layout)
|
|
|
|
|
| fig.add_hline(y=VG, line=dict(color='gray', dash='dot'), annotation_text="Volume Gate", annotation_position="top left")
|
|
|
|
|
| fig.show()
|
|
|
|
|
|
|
|
|
| mt5.shutdown()
|
|
|
| print("Done. Signals plotted. Review the chart to inspect markers and indicator overlays.") |