Добавил в скрип выбор рынка (Индексы, Фондовый, Валютный, Срочный)
В настоящее время скрип позволяет торговать спред в канале, пробой или отбой от границы канала, волны элиота.
Также на графике можно отрисовать два инструмента (или больше, но наглядность снижается).
Например отображение на одном графике базового актива и фьючерса можно использовать для арбитражных стратегий.
Если кто не догадался — на графике 2 таймфрейма, час и день.
Синие линии — границы канала, черные — зигзаг. Те что потолще — дневные, потоньше — часовые.
Например вчера график индекса ММВБ нарисовал классический пробой линии поддержки и возврат к ней снизу, и застрял в узком диапазоне консолидации.
Таким образом, согласно канонов технического анализа, пробой уровня 3288 — вернет индекс в границы дневного канала с продолжением роста, отбой вниз означат разворот тренда.
<!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>
var K=_chartHeight/(maxPrice-minPrice)
Здесь другая Целевая Аудитория.
… Здесь другая Целевая Аудитория...
Это вам она сама сказала?
Я сейчас прикручу к этому скрипту что то типа — «прошлая торговая сессия закрылась какой то свечой, открылись расчётно, пойдём туда если пробъем… Или сюда если повезет».
Затем настрою автоматическую отправку постов и вот тогда посмотрим у кого будут все лайки мира.
Код ничто — прибыль все.
Полагаю ваш код очень не плох, на счёт прибыли судить не могу, это слишком субъективное мнение
Свою доходность за первые 3 месяца опубликовал в блоге, моя меня устраивает.