python-mt5-support-resistan.../tests/verify.py
2026-11-01 14:36:24 +03:00
125 lines
5.2 KiB
Python
"""Synthetic verification. Native MetaEditor compilation is a separate check."""
import csv
import importlib.util
import math
from pathlib import Path
import shutil
import subprocess
import tempfile
from datetime import datetime, timedelta
ROOT = Path(__file__).resolve().parents[1]
spec = importlib.util.spec_from_file_location('zones', ROOT/'python/zone_reference.py')
zones = importlib.util.module_from_spec(spec)
spec.loader.exec_module(zones)
def bars(n=240):
result = []
start = datetime(2025, 1, 1)
for i in range(n):
price = 2000+5*math.sin(i/6)+math.sin(i/2)
result.append(dict(time=(start+timedelta(minutes=i)).isoformat(' '),
open=price-.1, close=price, high=price+.7, low=price-.8))
return result
def body(source, name):
start = source.index(('bool ' if name == 'SRPivot' else 'double ' if name == 'SRHalfWidth' else 'int ')+name+'(')
opening = source.index('{', start)
depth, i = 1, opening+1
while depth:
depth += (source[i] == '{')-(source[i] == '}')
i += 1
return source[start:i]
def main():
sample = bars()
expected = zones.detect(sample, show_broken=True, max_zones=100)
assert expected
for z in expected:
assert z['available'] > z['pivot']
assert z['lower'] < z['upper']
assert not z['broken'] or z['ended'] > z['available']
forming_changed = [dict(b) for b in sample]
forming_changed[-1].update(high=999999, low=.01, close=123)
assert zones.detect(forming_changed, show_broken=True, max_zones=100) == expected
small = bars(6)
for b in small:
b.update(open=10, close=10, high=11, low=9)
small[2]['low'] = 8
assert zones.detect(small[:-1], period=1) == []
one = zones.detect(small, period=1)
assert len(one) == 1 and one[0]['pivot'] == small[2]['time']
assert one[0]['available'] == small[5]['time']
tied = [dict(b) for b in small]
tied[1]['low'] = 8
assert zones.detect(tied, period=1) == []
assert len(zones.detect(sample, max_zones=3, show_broken=True)) == 3
with tempfile.TemporaryDirectory() as directory:
directory = Path(directory)
f = directory/'bars.csv'
with f.open('w', newline='') as out:
writer = csv.writer(out, delimiter='\t')
writer.writerow(['<DATE>','<TIME>','<OPEN>','<HIGH>','<LOW>','<CLOSE>'])
for b in sample:
date, time = b['time'].split()
writer.writerow([date.replace('-', '.'),time,b['open'],b['high'],b['low'],b['close']])
assert zones.load_bars(f) == sample
print('Python checks passed: confirmation delay, forming exclusion, strict ties, bounds, cap and CSV import.')
if not shutil.which('g++'):
print('C++ parity checks SKIPPED: install g++ to run them; native MT5 compilation still required.')
return
source = (ROOT/'MQL5/Indicators/SR_Research_Zones.mq5').read_text()
core = '\n'.join(body(source, n) for n in ('SRPivot','SRHalfWidth','SRBreakIndex'))
core = core.replace('const MqlRates &r[]', 'const std::vector<MqlRates> &r')
cpp = '''#include <vector>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <iomanip>
#include <cassert>
struct MqlRates { double open, high, low, close; };
double MathAbs(double x){return std::abs(x);}
double MathMax(double a,double b){return std::max(a,b);}
'''+core+'\nint main(){std::vector<MqlRates> r={\n'
cpp += ',\n'.join('{'+','.join(repr(b[k]) for k in ('open','high','low','close'))+'}' for b in sample)
cpp += '''};
std::cout<<std::setprecision(17);
for(int i=12;i+2<=(int)r.size()-2;i++)for(int side:{1,-1}){
if(!SRPivot(r,i,2,side))continue;
double a=side>0?r[i].low:r[i].high;
double w=SRHalfWidth(r,i+2,14,.25,.01);
int b=SRBreakIndex(r,i+3,r.size()-2,side,a-w,a+w,2);
std::cout<<i<<","<<side<<","<<a-w<<","<<a+w<<","<<b<<"\\n";
}
std::vector<MqlRates> q(5,{10,11,9,10});
q[0].close=8;q[1].close=10;q[2].close=8;q[3].close=8;
assert(SRBreakIndex(q,0,3,1,9,11,2)==3);
q[2].close=9;assert(SRBreakIndex(q,0,3,1,9,11,2)==-1);
q[0].close=12;q[1].close=12;
assert(SRBreakIndex(q,0,1,-1,9,11,2)==1);
}
'''
(directory/'test.cpp').write_text(cpp)
binary = directory/'test'
subprocess.run(['g++','-std=c++17','-O2',str(directory/'test.cpp'),'-o',str(binary)], check=True)
result = subprocess.run([str(binary)], capture_output=True, text=True, check=True)
actual = list(csv.reader(result.stdout.splitlines()))
assert len(actual) == len(expected)
for row, z in zip(actual, expected):
i, side, lo, hi, broken = row
assert sample[int(i)]['time'] == z['pivot']
assert ('SUPPORT' if int(side)>0 else 'RESISTANCE') == z['side']
assert math.isclose(float(lo), z['lower'], abs_tol=1e-10)
assert math.isclose(float(hi), z['upper'], abs_tol=1e-10)
assert (int(broken)>=0) == z['broken']
if z['broken']:
assert sample[int(broken)+1]['time'] == z['ended']
print(f'C++ parity passed: {len(actual)} zone records; actual MQL pivot, width and break bodies.')
print('Break checks passed: consecutive closes, reset at boundary and sell-side symmetry.')
if __name__ == '__main__':
main()