Перейти к содержимому






* * * * * 3 голосов

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

Написано Exdeath, 09 January 2013 · 5992 Просмотров

api mtgox btc-e json flash тейдинг php nginx
HelloWorld для написания торгового бота: получаем данные в JSON, парсим и обрабатываем их Возможно это первая часть цикла "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 есть отличнейшая документация на русском языке, к которой можно обратиться.

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


Собираюсь выложить в веб, сразу как добавляю в код поддержку всех пар 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;

    }
}


  • 2



Отличное начало, с нетерпением ждем продолжения!
    • 0
Обновил кусок кода, отвечающий за местоположение делений на горизонтальной шкале.
    • 0
В блогозаписи присутвует описка, но глючный движок не даёт мне её исправить, упорно пытаясь испортить форматирование исходных кодов(видно через предварительный просмотр).
Вместо:
>покупку/снятие ордеров

имелось ввиду:
>создание/снятие ордеров
    • 0
Раньше скрин открывался нормально, а теперь нет.
В общем вот полноразмерный:
0_9f6e6_6787c284_orig.png

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