Перейти к содержанию
  • записи
    3
  • комментариев
    48
  • просмотров
    70 838

HelloWorld для написания торгового бота: получаем данные в JSON, парсим и обрабатываем их


Exdeath

7 771 просмотр

Возможно это первая часть цикла "HelloWorld для написания торгового бота", а возможно единственный HelloWorld от меня, я пока ещё точно не могу сказать. Мой пример будет на Flash ActionScript 3. Во-первых я "на ты" именно с флешем. Во-вторых, в сети уже есть множество примеров-hellworldов для Гокса и BTC-E, на многих языках, но ActionScript 3 я не видел. И в-третьих, флеш позволяет создавать интерфейсы на самый любой вкус без каких-либо ограничений. Интерфейс можно сделать удобным и практичным, а при необходимости, ещё и красивым и динамичным. Для создания бота без GUI я бы предпочёл Python или Perl. Но используя GUI, можно создать торговые инструменты с подсказками, автоматизацией и с функцией автопилота, в работу которого можно в любой момент вмешаться. По-моему, это намного лучше чем демон, работающий сам по себе.

 

Однако, в рамках данной статьи в примерах кода пока что не будет запросов на сделки и покупку/снятие ордеров, хотя это и немаловажно, при написании бота это все-таки не самый первый этап, а некий HelloWorld №2. Решения о сделках или ордерах должны приниматься на основе данных, полученных от бирж, поэтому самый первый этап это получение данных и их обработка. А как только данные из JSON будут преобразованы в переменные и/или массивы ими можно будет свободно пользоваться в программном коде для решения необходимых задач.

 

В качестве примера обработки спарсинного JSON, будет приведён исходный код графического отображения глубины торгов(как на mtgoxlive.com). Для этого нужно получить и спарить данные о глубине, полученные от биржи.

 

 

Чтобы приведённый мной код успешно скомпилировался, я рекомендую компилировать под версию флешплеера не ниже 11.2. Под 10-ю можно даже не пытаться: там нет встроенного парсера JSON, который будет использоваться в моём коде. Также будет чуток PHP-кода. Это связано с тем, что политика безопасности флешплеера не позволяет подгрузить данные с другого домена. Это обходится с помощью вебсервера и PHP.

 

Итак данные о глубине торгов биржи BTC-E по паре btc/usd расположены тут: https://btc-e.com/api/2/btc_usd/depth (ранее: https://btc-e.com/api/2/1/depth)

Чтобы они были доступны с того же домена на котором расположен swf-файл, нужно использовать следующий php-код:

<?php
$q = $_GET['q'];
if($q=="depth_btcusd")$page = file_get_contents("https://btc-e.com/api/2/btc_usd/depth");
if($q=="trades_btcusd")$page = file_get_contents("https://btc-e.com/api/2/btc_usd/trades");
if($q=="ticker_btcusd")$page = file_get_contents("https://btc-e.com/api/2/btc_usd/ticker");


if($q=="depth_btcrur")$page = file_get_contents("https://btc-e.com/api/2/btc_rur/depth");
if($q=="trades_btcrur")$page = file_get_contents("https://btc-e.com/api/2/btc_rur/trades");
if($q=="ticker_btcrur")$page = file_get_contents("https://btc-e.com/api/2/btc_rur/ticker");

#и т. д.


echo $page;
?>

Переменная q нужна, чтобы не плодить огромное количество PHP-файлов, а использовать один.

Запрос на получение глубины торгов по паре btc/usd с локального домена будет выглядеть так:

localhost/btce.php?q=depth_btcusd

 

Что касается гокса, он также отдаёт данные в формате JSON немного другим способом и использование file_get_contents не прокатает, но можно сделать так:

<?php
$q = $_GET['q'];
if($q=="depth_btcusd")$ch = curl_init('https://mtgox.com/api/0/data/getDepth.php?Currency=USD');
#и аналогично с прошлым примером


curl_setopt($ch, CURLOPT_USERAGENT, "GoxFlashViewer public alpha");
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);


$page=curl_exec($ch);
curl_close($ch);
echo $page;
?>

 

В результате, на своём хосте мы получим способ выдачи и разметку JSON в абсолютно том же виде, как и для BTC-E. Для меня это было немаловажно т.к. к тому времени, как я прикрутил cURL с своему Nginx(без Apache), код для графического представления глубины для BTC-E уже был написан.

 

 

Далее приведу исходник флешки, снабжённый некоторыми комментариями в функции, строящей график, обрабатывая полученные данные:

 

package {
   import flash.display.StageAlign;
   import flash.display.StageScaleMode;
   import ru.exdeath.as3gui.WindowSkin;
   //классы Window и WindowSkin написаны мной; их код также будет приведён
   import flash.display.Sprite;
   import ru.exdeath.as3gui.Window;
   import flash.system.Security;
   import flash.events.Event;
   import flash.net.URLLoader;
   import flash.net.URLRequest;
   import flash.display.MovieClip;
   import flash.text.TextField;
   /**
    * @author exdeath
    */
   [sWF(width="700", height="580", frameRate="60", backgroundColor="#ffffff")]
   public class btce extends Sprite{
       public function btce() {

           //отключаем изменение маштабирования
           //при изменении размера рабочей области:
           stage.scaleMode = StageScaleMode.NO_SCALE;
           stage.align = StageAlign.TOP_LEFT;

           var loadComplete:Boolean=false;
           var request_depth:URLRequest = new URLRequest("http://localhost/btce.php?q=depth_btcusd");

           var loader:URLLoader = new URLLoader();


           loader.addEventListener(Event.COMPLETE, completeHandler);

           loader.load(request_depth);



           var txt:TextField = new TextField();
           txt.selectable=false;



           this.addChild(txt);

           try
           {
               loader.load(request_depth);
           }
           catch (error:ArgumentError)
           {
               txt.text="An ArgumentError has occurred.";
           }
           catch (error:SecurityError)
           {
               txt.text="A SecurityError has occurred.";
           }

           var n:uint=0;
           addEventListener(Event.ENTER_FRAME, onEnterFrame);
           function onEnterFrame(event:Event):void{
               if(!loadComplete){
                   txt.text="Загрузка.";
                   for(var i:uint=0;i<n%3;i++)txt.appendText(".");
                   n++;
               }
           }

           function completeHandler(event:Event):void {
               loadComplete=true;
               txt.text="done";
               var BtceDepth:Object = JSON.parse(loader.data);
               var BtceAsks:Array=BtceDepth.asks;
               var BtceBids:Array=BtceDepth.bids;

               creatDepthTxt();
               createDepthGraph();

               function creatDepthTxt():void{

                   var depthTxt:Window=new Window(450,300,null,"Стакан BTC-e BTC/USD");
                   addChild(depthTxt);

                   var asksField:TextField=new TextField();
                   var bidsField:TextField=new TextField();

                   depthTxt.box.addChild(asksField);
                   depthTxt.box.addChild(bidsField);

                   bidsField.x=200;
                   asksField.width=bidsField.width=200;
                   asksField.height=bidsField.height=300;
                   asksField.border=bidsField.border=true;

                   asksField.text="Предложение:\n";
                   bidsField.multiline=true;
                   bidsField.htmlText="Спрос:<br>";
                   for (var i in BtceAsks){
                       asksField.text+=BtceAsks[i][1]+" по "+BtceAsks[i][0]+"\n";
                   }
                   for (i in BtceBids){
                       if(BtceBids[i][1]>10.0)bidsField.htmlText+="<font color='#ff0000'>"+BtceBids[i][1]+" по "+BtceBids[i][0]+"</font><br>";
                       else bidsField.htmlText+=BtceBids[i][1]+" по "+BtceBids[i][0]+"<br>";

                   }
               }

               function createDepthGraph():void{
                   var depthGraphSkin:WindowSkin=new WindowSkin();
                   var depthGraph:Window=new Window(500, 300, depthGraphSkin,"Глубина торгов BTC-e BTC/USD");
                   addChild(depthGraph);

                   //сдвигаем окно из нуля, чтобы за ним было видно предыдущее
                   depthGraph.x=100;
                   depthGraph.y=200;

                   //рисуем оси координат
                   var x0:Number=250;
                   var y0:Number=290;

                   depthGraph.box.graphics.lineStyle(1);
                   depthGraph.box.graphics.moveTo(x0, 0);
                   depthGraph.box.graphics.lineTo(x0, 300);
                   depthGraph.box.graphics.moveTo(0, y0);
                   depthGraph.box.graphics.lineTo(500,y0);

                   //устанавливаем значения
                   //1px=0.01$
                   //1px=25btc
                   var stepX:Number=.01;
                   var btcStep:Number=25;

                   //узнаём крайнии значения
                   //trace(BtceBids[0][0],BtceAsks[0][0]);

                   //вычисляем среднее значение, там будет центр
                   var middle:Number=(BtceBids[0][0]+BtceAsks[0][0])/2;

                   trace(middle);

                   var AsksVector:Vector.<Number>=new Vector.<Number>(250);
                   var BidsVector:Vector.<Number>=new Vector.<Number>(250);

                   //пробегаемся по ордерам
                   for (var i in BtceAsks){
                       //смотрим
                       //цена=BtceAsks[i][0];количество=BtceAsks[i][1];
                       //прибавляем количество к соотв.значениям

                       for (var j in AsksVector){
                           if(BtceAsks[0][0]+j*stepX>BtceAsks[i][0])AsksVector[j]+=BtceAsks[i][1];
                       }
                   }
                   //аналогично для Bids
                   for (i in BtceBids){
                       for (j in BidsVector){
                           if(BtceBids[0][0]-j*stepX<BtceBids[i][0])BidsVector[j]+=BtceBids[i][1];
                       }
                   }

                   //рисуем график
                   depthGraph.box.graphics.lineStyle(2);
                   depthGraph.box.graphics.moveTo(x0, y0);
                   for (j in AsksVector){
                       //trace(AsksVector[j]);
                       depthGraph.box.graphics.lineTo(x0+j+1, y0-int(AsksVector[j]/btcStep));
                   }

                   //для Bids
                   depthGraph.box.graphics.moveTo(x0, y0);
                   for (j in BidsVector){
                       depthGraph.box.graphics.lineTo(x0-j, y0-int(BidsVector[j]/btcStep));
                   }
                   //рисуем деления колва биткоинов
                   //пока статично, потом сделать опционально
                   depthGraph.box.graphics.lineStyle(1,0xff0000);
                   var valuesY:Vector.<TextField>=new Vector.<TextField>(7);
                   for(i=0;i<7;i++){
                       depthGraph.box.graphics.moveTo(x0-3,y0-(i+1)*40);
                       depthGraph.box.graphics.lineTo(x0+3,y0-(i+1)*40);
                       valuesY[i]=new TextField();
                       depthGraph.box.addChild(valuesY[i]);
                       valuesY[i].x=x0+4;
                       valuesY[i].y=y0-(i+1)*40-8;
                       valuesY[i].text=(i+1)+"k";
                       valuesY[i].selectable=false;
                       valuesY[i].textColor=0xff0000;    

                   }


                   //считаем сколько требуется значений
                   //деление будет указывать каждое целое значение курса
                   //т.к. пока всё статично, можно заранее сказать, что 500*0.01$=5$
                   var valuesXN:int;
                   valuesXN=4;



                   //указываем значение курса в нуле
                   var currentCostTF:TextField=new TextField();

                   depthGraph.box.addChild(currentCostTF);
                   currentCostTF.text=BtceBids[0][0]+" "+BtceAsks[0][0];
                   currentCostTF.x=x0+10;
                   currentCostTF.y=y0-20;
                   currentCostTF.textColor=0xff0000;
                   currentCostTF.selectable=false;


                   //рисуем деления
                   var valuesX:Vector.<TextField>=new Vector.<TextField>(valuesXN);
                   //вычисляем местоположение самого левого деления и его значение
                   //значение: 
                   var valueMin:uint=int(middle-2.5)+1;
                   //местоположение:
                   var valueMinX:Number=(int(middle-.5)-middle+1.5)*100;
                   var valueXcurrent:Number;


                   for(i=0;i<valuesXN;i++){
                       valuesX[i]=new TextField();
                       valuesX[i].selectable=false;
                       valuesX[i].textColor=0xff0000;

                       valueXcurrent=valueMinX+i*100;

                       depthGraph.box.graphics.moveTo(valueXcurrent, y0-3);
                       depthGraph.box.graphics.lineTo(valueXcurrent, y0+3);
                       depthGraph.box.addChild(valuesX[i]);
                       valuesX[i].text=String(valueMin+i);
                       valuesX[i].y=y0-4;
                       valuesX[i].x=valueXcurrent-7;

                   }

               }


           }


       }
   }
}


 

Если вы незнакомы с синтаксисом AS3 и вам непонятна та или иная строчка, то на adobe.com есть отличнейшая документация на русском языке, к которой можно обратиться.

 

вот что получилось(кликабельное превью):

0_9f6e6_6787c284_M.png

 

 

Собираюсь выложить в веб, сразу как добавляю в код поддержку всех пар BTC-E и пар гокса с наибольшими объёмами и улучшу юзабилити. Хотя, если кто-нибудь попросит, могу выложить и текущею версию. А далее время от времени планирую добавлять функционал. Буду рад любым предложениям и идеям насчёт того, что требуется реализовать.

 

IDE на скриншоте: кроссплатформенный FDT 5 на основе Eclipse в ОС Ubuntu Linux 12.04.1 с Openbox вместо Unity.

И автономный адобовский флешплеер, который можно скачать здесь:

http://www.adobe.com.../downloads.html

И то и другое доступно под GNU/Linux, Windows и Mac OS X; как для 32-х, так и для 64-х битных версий данных систем.

Правда, для работы флешплеера на 64-битном Линуксе нужно доустановить некоторые 32-хбитные пакеты. (Какие именно можно узнать с помощью ldd)

 

Если файл запускается из локальной файловой системы, то скорее всего нужно будет сходить в каталог:

~/.macromedia/Flash_Player/#Security/FlashPlayerTrust (GNU/Linux)

C:\Users\<имя пользователя>\AppData\Roaming\Macromedia\Flash Player\#Security\FlashPlayerTrust (Windows 7)

C:\Documents and Settings\<имя пользователя>\Application Data\Macromedia\Flash Player\#Security\FlashPlayerTrust (Windows XP)

/Users/<имя пользователя>/Library/Preferences/Macromedia/Flash Player/#Security/FlashPlayerTrust (Mac OS X)

 

Создать там файл с любым именем и расширением .cfg или воспользоваться уже созданным *.cfg

И прописать путь к каталогу из которого запускается swf-файл

У меня прописано так:

//home/exdeath/Dropbox/workspace/fdt/btc/bin/

/home/exdeath/Dropbox/workspace/fdt/btc/bin/

 

 

И как и обещал привожу текущие версии моих классов Window и WindowSkin:

package ru.exdeath.as3gui {
   import flash.text.TextField;
   import flash.events.MouseEvent;
   import flash.display.Sprite;
   /**
    * @author exdeath
    */
   public class Window extends Sprite{

       public var box:Sprite = new Sprite;
       private var title:Sprite = new Sprite;
       private var TextInTitle:TextField=new TextField();
       //public var defaultSkin:WindowSkin = new WindowSkin();

       public function Window(width:Number,height:Number,skin:WindowSkin=null,titletext:String=""){
           //рисуем
           //var title:Sprite = new Sprite;

           if(skin==null)skin=new WindowSkin();
           //var titleH:Number=20;

           this.addChild(title);
           this.addChild(box);

           title.graphics.beginFill(skin.titleColor,skin.titleAlpha);
           title.graphics.lineStyle(skin.titleBorder,skin.titleBorderColor,skin.titleBorderAlpha);
           title.graphics.drawRect(0, -skin.titleH, width, skin.titleH);
           title.graphics.endFill();

           box.graphics.beginFill(skin.boxColor,skin.boxAlpha);
           box.graphics.lineStyle(skin.boxBorder,skin.boxBorderColor,skin.boxBorderAlpha);
           box.graphics.drawRect(0,0,width,height);
           box.graphics.endFill();

           //сдвигаем на высоту title
           this.y+=skin.titleH;

           //делаем окошки перетаскиваемыми
           title.addEventListener(MouseEvent.MOUSE_DOWN, mousePress);
           title.addEventListener(MouseEvent.MOUSE_UP, mouseRelease);
           function mousePress(e:MouseEvent):void{
               startDrag();

           }
           function mouseRelease(e:MouseEvent):void{
               stopDrag();
           }

           //помещаем в верхний слой, когда активно

           //при клике по окну, поднимаем его наверх (a=при нажатии по title; b=при нажатии по всему окну)
           //потом надо будет сделать булеву переменную в WindowSkin

           addEventListener(MouseEvent.MOUSE_DOWN, win2top); //b
           /*
           title.addEventListener(MouseEvent.MOUSE_DOWN, win2top); //a
           doubleClickEnabled = true;
           addEventListener(MouseEvent.DOUBLE_CLICK, win2top);
           */

           //создаём текстовое поле, в котором будет отображаться название окна

           title.addChild(TextInTitle);
           TextInTitle.selectable=false;
           TextInTitle.textColor=skin.textInTitleColor;
           TextInTitle.text=titletext;
           TextInTitle.x=0;//skin.titleH;//отступ для красоты
           TextInTitle.y=-skin.titleH;
           TextInTitle.width=this.width;



       }
       private function win2top(e:MouseEvent):void {
           parent.setChildIndex(this, parent.numChildren-1);
       }
   }
}

package ru.exdeath.as3gui {
   /**
    * @author exdeath
    */
   public class WindowSkin extends Object{
       public var titleH:Number=15;
       public var titleColor:Number=0x000000;
       public var titleAlpha:Number=1;
       public var titleBorder:Number=1;
       public var titleBorderColor:Number=0x000000;
       public var titleBorderAlpha:Number=1;
       public var boxColor:Number=0xFFFFFF;
       public var boxAlpha:Number=1;
       public var boxBorder:Number=1;
       public var boxBorderColor:Number=0x000000;
       public var boxBorderAlpha:Number=1;

       public var textInTitleColor:Number=0xFFFFFF;

   }
}

4 Комментария


Рекомендуемые комментарии

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

Вместо:

>покупку/снятие ордеров

 

имелось ввиду:

>создание/снятие ордеров

Ссылка на комментарий

Раньше скрин открывался нормально, а теперь нет.

В общем вот полноразмерный:

0_9f6e6_6787c284_orig.png

 

(Пост не редактирую т.к. разметка исходного кода портится)

Ссылка на комментарий

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти
  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...