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