Возможно это первая часть цикла "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; } }
4 Comments
Recommended Comments
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 accountSign in
Already have an account? Sign in here.
Sign In Now