Jump to content
  • entries
    3
  • comments
    48
  • views
    70617

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


Exdeath

7685 views

Возможно это первая часть цикла "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 Comments


Recommended Comments

Обновил кусок кода, отвечающий за местоположение делений на горизонтальной шкале.

Link to comment

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

Вместо:

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

 

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

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

Link to comment

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

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

0_9f6e6_6787c284_orig.png

 

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

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...