Nekto Finkelmaer
Nekto Finkelmaer личный блог
27 марта 2024, 10:05

Робот для торговли спреда внутри канала, пробоя или отбоя от уровня (с исходниками на Java Script)

Добавил в скрип выбор рынка (Индексы, Фондовый, Валютный, Срочный)

В настоящее время скрип позволяет торговать спред в канале, пробой или отбой от границы канала, волны элиота.
Также на графике можно отрисовать два инструмента (или больше, но наглядность снижается).
Например отображение на одном графике базового актива и фьючерса можно использовать для арбитражных стратегий.

Если кто не догадался — на графике 2 таймфрейма, час и день.
Синие линии — границы канала, черные — зигзаг. Те что потолще — дневные, потоньше — часовые.
Например вчера график индекса ММВБ нарисовал классический пробой линии поддержки и возврат к ней снизу, и застрял в узком диапазоне консолидации.

Таким образом, согласно канонов технического анализа, пробой уровня 3288 — вернет индекс в границы дневного канала с продолжением роста, отбой вниз означат разворот тренда.

Робот для торговли спреда внутри канала, пробоя или отбоя от уровня (с исходниками на Java Script)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>

<form name="search">
	<se <a name="cut"></a> lect name="market" id="market">
		<option value="index" >Индексы</option>
		<option value="currency">Валюта</option>
		<option value="shares">Фондовый</option>
		<option value="futures">Срочный</option>
	</select>
    <input type="text" name="key" placeholder="Тикер" id="key"/>
	<input type="checkbox" name="markup" checked><span>Разметка</span>
    <input type="button" name="buttonChart" value="График" />
</form>
<div id="printBlock"></div>
<canvas id="chart" width="4000" height="600"></canvas>
<script>
const keyBox = document.search.key;
const keyButton = document.search.buttonChart;
 
 function DrawChart(prices,minPrice,maxPrice,maxVolume,ZZ,ZZDay){

	var chart = document.getElementById("chart");
	
	if (chart.getContext) {
		var ctx = chart.getContext("2d");
		ctx.lineWidth=1;
		Point = 0;
		for (let i = prices.length-1; i >= 0; i--) { 
			var K=600/(maxPrice-minPrice);
			var pOpen = Math.trunc((prices[i].Open-minPrice)*K);
			var pClose = Math.trunc((prices[i].Close-minPrice)*K);
			
			var pMin = Math.trunc((prices[i].Low-minPrice)*K);
			var pMax = Math.trunc((prices[i].High-minPrice)*K);
			
			var dStart = 600-Math.max(pOpen,pClose);
			var dFinish = 600-Math.min(pOpen,pClose);
			
			var dMin = 600-pMin;
			var dMax = 600-pMax;
			
			var pVolume = Math.trunc((prices[i].Volume)/maxVolume*100);
			var dVolume = 600-pVolume;
			
			var AA = prices[i].Close - prices[i].Open;
			
			//ctx.beginPath();
			if (pOpen==pClose) {
				
				ctx.fillStyle = "rgb(0,0,0)";
				dFinish = dStart+1;
				//console.log("-"+(Point*4)+" "+dStart+" "+dFinish + " "+AA+" "+pOpen+" "+pClose+" "+K);
				
			}
			else {
				
				if (prices[i].Open>prices[i].Close) {
					ctx.fillStyle = "rgb("+FirstColor+",0,0,"+Transp+")";
				}
				else{
					ctx.fillStyle = "rgb(0,"+FirstColor+",0,"+Transp+")";
				}
				
			}
			
			
			if (Point==0) {
				
				ctx.fillRect(Point+FirstPos, dStart, 40, 1);
				
				ctx.textBaseline = "bottom";
				ctx.font = "bold 8px Arial";
				
				ctx.fillText(prices[i].Close, Point+FirstPos, dStart-2);
				
				Point = Point+11;
			}
			
			//console.log(""+(Point*4)+" "+dStart+" "+dFinish + " "+AA+" "+pOpen+" "+pClose+" "+K);
			ctx.fillRect(Point*4, dStart, 3, dFinish-dStart);
			
			ctx.fillStyle = ctx.fillStyle.replace(")",",0.7)");
			
			//console.log(""+prices[i].Volume+" "+maxVolume + " "+pVolume+" "+dVolume+" "+ctx.fillStyle);
			
			ctx.fillRect(Point*4, dVolume, 3, 600-dVolume);
			
			ctx.fillStyle = "rgb(0,0,0,"+Transp+")";
			ctx.fillRect(Point*4+1, dMax, 1, dStart-dMax);
			ctx.fillRect(Point*4+1, dFinish, 1, dMin-dFinish);
			Point ++;
		}
		
		
		
		DrawZZ(ZZ,ctx,prices,maxPrice,minPrice);
		ctx.lineWidth=2;
		DrawZZ(ZZDay,ctx,prices,maxPrice,minPrice);
		
		var priceLevel = [];
		
		ctx.lineWidth=1;
		ctx.strokeStyle = "rgb(0,0,200,"+Transp+")";
		DrawK(ZZ,ctx,prices,maxPrice,minPrice,1,3,priceLevel);
		DrawK(ZZ,ctx,prices,maxPrice,minPrice,3,5,priceLevel);
		DrawK(ZZ,ctx,prices,maxPrice,minPrice,1,5,priceLevel);
		
		DrawK(ZZ,ctx,prices,maxPrice,minPrice,2,4,priceLevel);
		DrawK(ZZ,ctx,prices,maxPrice,minPrice,4,6,priceLevel);
		DrawK(ZZ,ctx,prices,maxPrice,minPrice,2,6,priceLevel);
		
		ctx.lineWidth=2;
		
		DrawK(ZZDay,ctx,prices,maxPrice,minPrice,1,3,priceLevel);
		DrawK(ZZDay,ctx,prices,maxPrice,minPrice,3,5,priceLevel);
		DrawK(ZZDay,ctx,prices,maxPrice,minPrice,1,5,priceLevel);
		
		DrawK(ZZDay,ctx,prices,maxPrice,minPrice,2,4,priceLevel);
		DrawK(ZZDay,ctx,prices,maxPrice,minPrice,4,6,priceLevel);
		DrawK(ZZDay,ctx,prices,maxPrice,minPrice,2,6,priceLevel);
		
		priceLevel.sort(function(a,b){ 
							if (a.Price<b.Price) return -1; else if (a.Price>b.Price) return 1; else return 0;
						});
		
		
		priceLevelGroup = [];
		maxVolume = 0;
		
		percentGroupPrice = 0.01*(maxPrice - minPrice)/minPrice;
		
		for (let i = 0; i < priceLevel.length; i++) {
			
			console.log(""+priceLevel[i].Price+" "+priceLevel[i].Volume);
			
			if (i==0) {
				priceLevelGroup[0] = {Price:priceLevel[i].Price,Volume:priceLevel[i].Volume};
				
			}
			else{
				if (priceLevel[i].Price/priceLevelGroup[priceLevelGroup.length-1].Price-1<percentGroupPrice) {
					
						priceLevelGroup[priceLevelGroup.length-1].Price = 
											(priceLevel[i].Volume*priceLevel[i].Price
											+priceLevelGroup[priceLevelGroup.length-1].Volume*priceLevelGroup[priceLevelGroup.length-1].Price)
											/(priceLevel[i].Volume+priceLevelGroup[priceLevelGroup.length-1].Volume);
						priceLevelGroup[priceLevelGroup.length-1].Volume = priceLevelGroup[priceLevelGroup.length-1].Volume+priceLevel[i].Volume;
						
				}
				else{
					priceLevelGroup[priceLevelGroup.length] = {Price:priceLevel[i].Price,Volume:priceLevel[i].Volume};
				}
						
			};
			if (priceLevelGroup[priceLevelGroup.length-1].Volume>maxVolume){
				maxVolume = priceLevelGroup[priceLevelGroup.length-1].Volume;
			}
			
		}
		
		for (let i = 0; i < priceLevelGroup.length; i++) {
			
			
			pOpen = Math.trunc((priceLevelGroup[i].Price-minPrice)*K);
			
			dStart = 600-pOpen;
			
			
				
			ctx.textBaseline = "bottom";
			
			ctx.font = ""+(Math.trunc(priceLevelGroup[i].Volume/maxVolume*12)+6)+"px Arial";
			
			console.log("g "+maxVolume+" "+priceLevelGroup[i].Volume+" "+ctx.font+" "+(Math.trunc(priceLevelGroup[i].Volume/maxVolume*12)+6));
			
			//console.log("g "+priceLevelGroup[i].Price+" "+priceLevelGroup[i].Volume+" "+ctx.font+" "+(Math.trunc(priceLevelGroup[i].Volume/maxVolume*12)+6));
			
			percent = Math.trunc((prices[prices.length-1].Close - priceLevelGroup[i].Price)/prices[prices.length-1].Close*10000)/100;
			if (percent<0) {percent=-percent;ctx.fillStyle = "rgb(100,150,100,1)";}
			else {ctx.fillStyle = "rgb(150,100,100,1)"};
			ctx.fillRect(FirstPos, dStart, 40, 1);
			ctx.fillText(""+Math.trunc(priceLevelGroup[i].Price*100)/100+" ("+percent+"%)", FirstPos, dStart-1);
				
		}
		
		FirstPos = FirstPos + 60;
		
	}
 }
 
 function DrawZZ(ZZ,ctx,prices,maxPrice,minPrice){
		
		ctx.fillStyle = "rgb(0,0,0,"+Transp+")";
		ctx.beginPath();
		
		for (let i = 0; i < ZZ.length; i++) {
			
			Point = (prices.length - ZZ[i].Point+10)*4+1;
			var K=600/(maxPrice-minPrice);
			pPrice = Math.trunc((ZZ[i].price-minPrice)*K);
			dStart = 600-pPrice;
			
			//console.log(""+ZZ[i].price+" "+dStart+ " "+ZZ[i].Point+" "+ZZ[i].T);
			
			if (i==0) {
				ctx.moveTo(Point,dStart);
			} else {
				ctx.lineTo(Point,dStart);
			}
		}
		if (document.search.markup.checked) {ctx.stroke()};
 }

function DrawK(ZZ,ctx,prices,maxPrice,minPrice,p1,p2,priceLevel){
		
		
		if (ZZ.length-p1<0 || ZZ.length-p2<0) {return};
		
		var K=600/(maxPrice-minPrice);
		
		first = ZZ.length - p1;
		second = ZZ.length - p2;
		
		//console.log(""+first+" "+second+" "+ZZ.length);
		
		fPoint = (prices.length-ZZ[first].Point+10)*4+1;
		sPoint = (prices.length-ZZ[second].Point+10)*4+1;
			
		ePrice=ZZ[second].price+(prices.length-ZZ[second].Point)*(ZZ[first].price-ZZ[second].price)/(ZZ[first].Point-ZZ[second].Point)
		
		priceLevel[priceLevel.length] = {Price:ePrice,Volume:(ZZ[second].Volume+ZZ[first].Volume)};
		priceLevel[priceLevel.length] = {Price:ZZ[first].price,Volume:(ZZ[first].Volume/2)};
		priceLevel[priceLevel.length] = {Price:ZZ[second].price,Volume:(ZZ[second].Volume/2)};
		
		
		//console.log(""+ZZ[first].Point+" "+ZZ[second].Point+" "+" "+prices.length+" "+ZZ[first].price+" "+ZZ[second].price+" "+ePrice);
		
		fPrice = Math.trunc((ZZ[first].price-minPrice)*K);
		sPrice = Math.trunc((ZZ[second].price-minPrice)*K);
		ePrice = Math.trunc((ePrice-minPrice)*K);
		
		dStart = 600-sPrice;
		dFinish = 600-ePrice;
		
		
		//console.log(""+first+" "+second+" "+fPrice+" "+sPrice+" "+ePrice);
		if (document.search.markup.checked) {
			//ctx.fillStyle = "rgb(0,0,255,0.7)";
			//console.log(""+ctx.fillStyle);
			ctx.beginPath();
			ctx.moveTo(sPoint,dStart);
			ctx.lineTo(40,dFinish);
			ctx.stroke();
			
			ctx.beginPath();
			ctx.moveTo(sPoint,dStart);
			ctx.lineTo(40,dStart);
			ctx.stroke();
			
			ctx.beginPath();
			ctx.moveTo(fPoint,600-fPrice);
			ctx.lineTo(40,600-fPrice);
			ctx.stroke();
		};
		
 }
 
 function FillArray (data,tiker){

	var prices = [];
	var pricesDay = [];
	var ZZ = [];
	var ZZDay = [];
	var minPrice = 9999999;
	var maxPrice = 0;
	var maxVolume = 0;
	
	let md = data['candles']['data'];
	 
	for (let i = 0; i < md.length; i++) {
		var cl = prices.length;
		prices[cl] = [];
		prices[cl] = {Period:md[i][6], Open:md[i][0], Close:md[i][1],Low:md[i][3], High:md[i][2], Volume:(md[i][5] == 0 ? md[i][4] : md[i][5])}
		if (minPrice>prices[cl].Low) {
				minPrice = prices[cl].Low;
		}
		if (maxPrice<prices[cl].High) {
				maxPrice = prices[cl].High;
		}
		if (maxVolume<prices[cl].Volume) {
				maxVolume = prices[cl].Volume;
		}
		
		var cDay = prices[cl].Period;
		cDay = cDay.substring(0,10);
		
		
		PosDay=pricesDay.findIndex(item => item.Period == cDay);
		if (PosDay==-1) {PosDay=pricesDay.length; pricesDay[PosDay]={Period:cDay,High:prices[cl].High,Low:prices[cl].Low,PeriodHi:i,PeriodLow:i,Volume:0}}
		if (pricesDay[PosDay].High<prices[cl].High) {pricesDay[PosDay].High=prices[cl].High;pricesDay[PosDay].PeriodHi=cl;}
		if (pricesDay[PosDay].Low>prices[cl].Low) {pricesDay[PosDay].Low=prices[cl].Low;pricesDay[PosDay].PeriodLow=cl;}
		pricesDay[PosDay].Volume = pricesDay[PosDay].Volume+prices[cl].Volume;
		
		if (cl>1) {
			if (ZZ.length == 0) {
				ZZ[0] = {price:(prices[cl-2].Open>prices[cl].Close ? prices[cl-2].High:prices[cl-2].Low), 
								Point:cl-2, 
								T:(prices[cl-2].Open>prices[cl].Close ? -1:1),
								Volume:prices[cl-2].Volume}
				
				//console.log("f "+ZZ[ZZ.length-1].price+" "+" "+ZZ[ZZ.length-1].Point+" "+ZZ[ZZ.length-1].T);
				
			} else {
				if (ZZ[ZZ.length-1].T == 1) { 
					//console.log("up "+ZZ[ZZ.length-1].price+" "+" "+ZZ[ZZ.length-1].Point+" "+ZZ[ZZ.length-1].T);
				
					if (prices[cl-2].High<prices[cl].High || prices[cl-1].High<prices[cl].High || prices[cl-2].Low<prices[cl].Low || prices[cl-1].Low<prices[cl].Low) {}
					else {
							ZZ[ZZ.length]={price:(Math.max(prices[cl-2].High,prices[cl-1].High)),
									       Point:(prices[cl-2].High>prices[cl-1].High ? cl-2:cl-1),
										   T:-1,
										   Volume:(prices[cl-2].High>prices[cl-1].High ? prices[cl-2].Volume:prices[cl-1].Volume)}
							
							//console.log("up "+ZZ[ZZ.length-1].price+" "+" "+ZZ[ZZ.length-1].Point+" "+ZZ[ZZ.length-1].T);
						}
					} 
				else {
					if (ZZ[ZZ.length-1].T == -1) { 
						if (prices[cl-2].Low>prices[cl].Low || prices[cl-1].Low>prices[cl].Low || prices[cl-2].High>prices[cl].High || prices[cl-1].High>prices[cl].High) {}
						else {
							ZZ[ZZ.length]={price:(Math.min(prices[cl-2].Low,prices[cl-1].Low)),
										   Point:(prices[cl-2].Low<prices[cl-1].Low ? cl-2:cl-1),
										   T:1,
										   Volume:(prices[cl-2].Low<prices[cl-1].Low ? prices[cl-2].Volume:prices[cl-1].Volume)}
							//console.log("dw "+ZZ[ZZ.length-1].price+" "+" "+ZZ[ZZ.length-1].Point+" "+ZZ[ZZ.length-1].T);
						}
						 
					}
				}
				
			}
		}
	}
	
	for (let cl = 0; cl < pricesDay.length; cl++) {
		//console.log(""+pricesDay[cl].Period+" "+pricesDay[cl].High+" "+pricesDay[cl].Low+" "+pricesDay[cl].Volume+" "+pricesDay[cl].PeriodHi+" "+pricesDay[cl].PeriodLow);
		
		if (cl>1) {
			if (ZZDay.length == 0) {
				ZZDay[0] = {price:(pricesDay[cl-2].Open>pricesDay[cl].Close ? pricesDay[cl-2].High:pricesDay[cl-2].Low), 
							Point:(pricesDay[cl-2].Open>pricesDay[cl].Close ? pricesDay[cl-2].PeriodHi:pricesDay[cl-2].PeriodLow), 
							T:(pricesDay[cl-2].Open>pricesDay[cl].Close ? -1:1),
							Volume:prices[cl-2].Volume}
				
				//console.log("f "+ZZDay[ZZDay.length-1].price+" "+" "+ZZDay[ZZDay.length-1].Point+" "+ZZDay[ZZDay.length-1].T);
				
			} else {
				if (ZZDay[ZZDay.length-1].T == 1) { 
					//console.log("up "+ZZDay[ZZDay.length-1].price+" "+" "+ZZDay[ZZDay.length-1].Point+" "+ZZDay[ZZDay.length-1].T);
				
					if (pricesDay[cl-2].High<pricesDay[cl].High || pricesDay[cl-1].High<pricesDay[cl].High || pricesDay[cl-2].Low<pricesDay[cl].Low || pricesDay[cl-1].Low<pricesDay[cl].Low) {}
					else {
							ZZDay[ZZDay.length]={price:(Math.max(pricesDay[cl-2].High,pricesDay[cl-1].High)),
											     Point:(pricesDay[cl-2].High>pricesDay[cl-1].High ? pricesDay[cl-2].PeriodHi:pricesDay[cl-1].PeriodHi),
												 T:-1,
												 Volume:(pricesDay[cl-2].High>pricesDay[cl-1].High ? pricesDay[cl-2].Volume:pricesDay[cl-1].Volume)}
							
							//console.log("up "+ZZDay[ZZDay.length-1].price+" "+" "+ZZDay[ZZDay.length-1].Point+" "+ZZDay[ZZDay.length-1].T);
						}
					} 
				else {
					if (ZZDay[ZZDay.length-1].T == -1) { 
						if (pricesDay[cl-2].Low>pricesDay[cl].Low || pricesDay[cl-1].Low>pricesDay[cl].Low || pricesDay[cl-2].High>pricesDay[cl].High || pricesDay[cl-1].High>pricesDay[cl].High) {}
						else {
							ZZDay[ZZDay.length]={price:(Math.min(pricesDay[cl-2].Low,pricesDay[cl-1].Low)),
												 Point:(pricesDay[cl-2].Low<pricesDay[cl-1].Low ? pricesDay[cl-2].PeriodLow:pricesDay[cl-1].PeriodLow),
												 T:1,
												 Volume:(pricesDay[cl-2].Low<pricesDay[cl-1].Low ? pricesDay[cl-2].Volume:pricesDay[cl-1].Volume)}
							//console.log("dw "+ZZDay[ZZDay.length-1].price+" "+" "+ZZDay[ZZDay.length-1].Point+" "+ZZDay[ZZDay.length-1].T);
						}
						 
					}
				}
				
			}
		}
	}
	
	const printBlock = document.getElementById("printBlock");
    printBlock.textContent = printBlock.textContent+" "+tiker+"("+prices[prices.length-1].Period+")";

	DrawChart(prices,minPrice,maxPrice,maxVolume,ZZ,ZZDay);
	return prices;
 }
 
 function strRight(n,str){
   return str.substring(str.length-n,str.length);
 }  
// обработчик изменения текста
function onchange(e){
    // получаем элемент printBlock
    
	const tiker = document.getElementById("key").value;
	const market = document.getElementById("market").value;
    // получаем новое значение
    const val = tiker;
	
	if (market == "index") {
		var URL = "https://iss.moex.com/iss/engines/stock/markets/index/securities/<TIKER>/candles.json?from=<DateFrom>&interval=60";
	}
	if (market == "currency") {
		var URL = "https://iss.moex.com/iss/engines/currency/markets/selt/securities/<TIKER>/candles.json?from=<DateFrom>&interval=60";
	}
	if (market == "shares") {
		var URL = "https://iss.moex.com/iss/engines/stock/markets/shares/securities/<TIKER>/candles.json?from=<DateFrom>&interval=60";
	}
	if (market == "futures") {
		var URL = "https://iss.moex.com/iss/engines/futures/markets/forts/securities/<TIKER>/candles.json?from=<DateFrom>&interval=60";
	}
	
	
	
	URL = URL.replace("<TIKER>", val);
	
	var now = new Date();
	now.setDate(now.getDate() - 45);
	console.log(now);

	var Month=now.getMonth()+1;
	var year=now.getFullYear();
	
	//if (Month<0) {Month=12+Month+1; year--;};
	var startdata = (year).toString()+"-"+strRight(2,"0"+Month.toString())+"-"+strRight(2,"0"+now.getDate().toString());
	
	URL = URL.replace("<DateFrom>", startdata);
	console.log(URL);
	fetch(URL)
		.then(function (response) {
			return response.json();
		})
		.then(function (data) {
			
			FillArray(data,tiker);
			
			FirstColor = Math.trunc(FirstColor*2/3);
			Transp = Math.trunc(Transp*2/3*10)/10;
			
			
		})
  
}

var FirstPos = 0;
var FirstColor = 250;
var Transp = 1;
document.search.markup.checked = false;
//keyBox.addEventListener("change", onchange);
keyButton.addEventListener("click", onchange);
</script>
</body>
</html>
12 Комментариев
  • Sprite
    27 марта 2024, 11:16
    var K=600/(maxPrice-minPrice)
    var K=_chartHeight/(maxPrice-minPrice)
  • Василий Федорович
    27 марта 2024, 11:39
    Зачем пытаетесь переформатировать Смартлаб в MQL5?
    Здесь другая Целевая Аудитория.
      • Василий Федорович
        27 марта 2024, 12:16
        Nekto Finkelmaer, нет, не она, а статистика. Подсчитайте в разделе Торговый софт количество статей, просмотров и комментариев.
          • Василий Федорович
            27 марта 2024, 12:54
            Nekto Finkelmaer, да не вопрос, я первый ЗА! Просто я говорю, что это трудный путь. Здесь считается за достоинство — торговля в ручную, 19 век какой-то.
              • Василий Федорович
                27 марта 2024, 13:17
                Nekto Finkelmaer, ну-ну, а 21 век — век романтиков. Я такого кода за последние 20 лет насмотрелся — мегатоннами. Ключевое слово у всех, как приговор, «если повезет». Хотя бы написали «с высокой вероятностью».
                  • Василий Федорович
                    27 марта 2024, 13:47
                    Nekto Finkelmaer, я не планировал, я мечтаю увидеть здесь код, хотя бы в перспективе приближающийся к моему. За последние 5 лет — видел здесь только 2 раза. Да и дело не в коде, а в алгоритме.

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн