Open Source: Пишем бота для биржи BTC-E

от автора


Добрый день, дорогой %username%!
Совсем недавно я узнал про такую замечательную вещь, как биткоин. Не секрет, что последние 2-3 месяца наблюдался бурный рост этой криптовалюты. Казалось бы, ну есть у нас криптовалюта, а сбывать ее где? После моего предыдущего поста у меня появилось несколько лайткоинов. Естественно, я начал думать о способе сбыта легкой криптовалюты. Немного погуглив, я набрел на биржу BTC-E и, о великий котэ, увидел заветную кнопочку «Trade API».

С этого момента судьба следующих пары-тройки дней для меня была предопределена — я загорелся идеей воспользоваться этим самым API и сделать миллионы грязных долларов написать бота, который автоматически бы сбывал и покупал ресурсы. А так как я fully proficient (взято с моего профиля на elance.com) в Objective C, на нем я писать и буду.

Три дня прошло, и я все-таки осилил API. Зачем я выкладываю все это в открытый доступ? Что же, на это есть ряд причин:

  • Пора уже начинать делать что-то open source
  • Нужно популяризировать криптовалюту
  • Логики в моем «наброске» очень мало, так что алгоритмы купли\продажи заинтересованному придется писать самостоятельно
  • Мне банально нужен человек, финансист, который наглядно объяснит какими путями нужно идти. Возможно, даже согласится работать вместе

Внимание! Под катом разбор основных моментов работы с API; подводные камни, о которые мне пришлось споткнуться; исходники.

1. Разбор API

Заходим на сайт биржы в раздел FAQ, открываем вкладку «API». Можно покликать по Public Api, ответы будут приходить в JSON формате. Нравится мне этот формат. Уже хорошо, идем дальше.
Переходим по ссылке «Trade Api». Что мы видим? Нужно просто отправлять POST запросы и будет нам счастье! Отлично, а как это на Objective C-то реализовать? Вот незадача! До этого момента я ведь никогда и не думал о подобном на моем родном языке. Ничего страшного, разберемся.
Что у нас тут? Посылаем ключ API из профиля в заголовок «api»; POST запрос добавляем как тело запроса; а в «sign» передаем хеш нашего тела запроса, подписанный секретным ключом «secret». Круто. Где их взять? Заходим в профиль.

Ничего себе! И то, и то в одном месте! Вот это да, пока что я иду на правильном пути. Главное не забыть установить нужные права для наших ключей.
Давайте взглянем на тело стандартного ответа с публичного запроса из API. Попробуем узнать курс BTC/USD. Шлепнем по запросу. Получаем примерно следующее:

{"ticker":{"high":139,"low":126.6,"avg":132.8,"vol":971784.85609,"vol_cur":7345.76864,"last":136.396,"buy":137,"sell":136.396,"server_time":1365246479}} 

Про JSON рассказывать не буду, в интернете по нему полно туториалов. Нас интересуют покупка и продажа. Напишем небольшой код для реализации запроса и его обработки! Вперед к коду!

Жми меня!

- (void)updateLtcToUsdRate {     dispatch_async(kBgQueue, ^{         NSData *data = [apiHandler getResponseFromPublicServerUrl:@"https://btc-e.com/api/2/ltc_usd/ticker"];                  NSError* error;         NSDictionary* json = [NSJSONSerialization                               JSONObjectWithData:data                               options:kNilOptions                               error:&error];                  NSDictionary* ticker = [json objectForKey:@"ticker"];         NSString *sell = [ticker objectForKey:@"sell"];         NSString *buy = [ticker objectForKey:@"buy"];         ltcToUsdSellRateLabel.stringValue = addDollarSign(sell);         ltcToUsdBuyRateLabel.stringValue = addDollarSign(buy);     }); }  - (NSData *)getResponseFromPublicServerUrl:(NSString *)urlString {     NSURL *url = [NSURL URLWithString:urlString];     NSData *data = [NSData dataWithContentsOfURL:url];     return data; } 

Пройдемся по коду. В проекте у нас пара классов, и BtceApiHandler работает с запросами на сервер. getResponseFromPublicServerUrl — это метод BtceApiHandler класса. Здесь никаких POST запросов отправлять не нужно, просто забираем данные с сервера. Дальше слегка обрабатываем JSON и получаем значения sell и buy. Покажем их на экран нашему пользователю (пока что только нам).
А что за addDollarSign? А вот тут я постиг приятные плюшки функций в Objective C. До сегодняшнего момента я их редко использовал, старался их избегать, но теперь легким движением руки появляются вот такие крендельки. Мелочь, а приятно.

NSString *addDollarSign(NSString *string) {     NSString *newString = [NSString stringWithFormat:@"$%@",string];     return newString; } 

Ну, с публичным API вроде как разобрались, если нет — Вам дорога к мануалу.
Что же происходит с личным API? Пока я разобрался с POST запросами в Objective C, я потерял приличное количество нервных клеток. Да и в самом API есть примеры алгоритмов и ввода, а что должно быть на выходе между Вами и сервером — не понятно. Пришлось спрашивать в чате самой биржи. Благо, приятные люди там есть, и мне помогли.
В чем проблема? Наверное, в том, что я никогда и не работал с POST запросами на своем родном языке. Только Java, только хардкор. Прыгнем в код класса BtceApiHandler.

- (void)setupInitialValues {     api_key = @"P55XJOOF-207D9CCC-A7NDKIYK-KIMFCOXC-WJPJ21XY";     secret_key = @"5bb4f0c55d6100d583505dc98eaff1df7d1ba281b5f8365686090d5cbbdb9846"; } 

Нужно инициализировать две важные переменные. Позже можно будет сделать специальное окошко и так далее, но я просто захордкодил ключи. Не обольщайтесь, их я уже отключил.

Жми меня!

- (NSData *)getResponseFromServerForPost:(NSDictionary *)postDictionary {     NSString *post;     int i = 0;     for (NSString *key in [postDictionary allKeys]) {         NSString *value = [postDictionary objectForKey:key];         if (i==0)             post = [NSString stringWithFormat:@"%@=%@", key, value];         else             post = [NSString stringWithFormat:@"%@&%@=%@", post, key, value];         i++;     }     post = [NSString stringWithFormat:@"%@&nonce=%@", post, getNonce()];          NSString *signedPost = hmacForKeyAndData(secret_key, post);          NSMutableURLRequest *request = [[NSMutableURLRequest alloc]                                     initWithURL:                                     [NSURL URLWithString:@"https://btc-e.com/tapi"]];     [request setHTTPMethod:@"POST"];     [request setValue:api_key forHTTPHeaderField:@"key"];     [request setValue:signedPost forHTTPHeaderField:@"sign"];     [request setHTTPBody:[post dataUsingEncoding: NSUTF8StringEncoding]];          NSURLResponse *theResponse = NULL;     NSError *theError = NULL;     NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&theResponse error:&theError];     return responseData; } 

А вот и монструозный метод отправки и обработки POST запроса! Мы передаем в него аргументом NSDictionary, в котором храним связки параметр-значение. Первая часть преобразует наш словарь в POST запрос, вторая часть забивает наш запрос стандартными данными (тип данных, url, key, sign), третья часть посылает запрос и возвращает NSData c ответом.

Жми меня!

NSString *hmacForKeyAndData(NSString *key, NSString *data) {     const char *cKey = [key cStringUsingEncoding:NSASCIIStringEncoding];     const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];     unsigned char cHMAC[CC_SHA512_DIGEST_LENGTH];     CCHmac(kCCHmacAlgSHA512, cKey, strlen(cKey), cData, strlen(cData), cHMAC);     NSMutableString *hashString = [NSMutableString stringWithCapacity:sizeof(cHMAC) * 2];     for (int i = 0; i < sizeof(cHMAC); i++) {         [hashString appendFormat:@"%02x", cHMAC[i]];     }     return hashString; } 

Особое внимание стоит уделить коду подписи данных в «sign». Почему? Да я на него убил больше суток! Больше суток хождения по интернетам и стаковерфловам. Происходит все просто: используя встроенные возможности языка, мы подписываем ключом данные и вытягиваем SHA512 хеш. Цикл, думаю, объяснять не стоит — там и так все очевидно. Раньше пытался получить значение через description наших данных — не получалось. Пришлось спросить на stackoverflow про этот небольшой циклик.

Жми меня!

NSString *getNonce() {     NSTimeInterval timeStamp = [[NSDate date] timeIntervalSinceDate:[NSDate dateWithString:@"2012-04-18 00:00:01 +0600"]];     int currentNonce = [NSNumber numberWithDouble: timeStamp].intValue;     NSString *nonceString = [NSString stringWithFormat:@"%i",currentNonce];     return nonceString; } 

А еще есть функция getNonce(). Параметр nonce передавать в POST запросе обязательно. Это инкремент, так что каждое последующее число должно быть больше предыдущего. Вот такие пироги. Ах да, эта функция считает время, прошедшее с моего совершеннолетия.

2. Что умеет делать наш бот?

Пока мы можем мониторить интересную информацию, смотреть за ростом и падением криптовалют. Ничего особенного, можно сказать, я просто обернул API в Objective C. Небольшой скриншот програмки-на-коленке:

3. Что делать дальше?

API побеждено, дело техники закончено. Впереди автоматизация процесса — а это уйма работы над алгоритмами. Сам я далеко не финансист, да что уж, даже не начинающий трейдер на рынке. Если вдруг кто из желающих здесь захочет со мной поработать, и возможно даже поднять немного денег — милости прошу ко мне в различные IM, контакты есть в моем хабрацентре.
А вдруг кто из Objective C программистов захочет позаниматься в свободное время этими механизмами и созданием со мной юзер-френдли UI? Опять же, милости прошу в IM.

Где код?

Исходники тут. Приятного просмотра. Все под лицензией GPL (первая попавшаяся мне open source лицензия). Качайте, компилируйте, пользуйтесь. Не продавайте.

Если Вы нашли какую-то опечатку и\или неточность — напишите, пожалуйста, мне в личку или в комментарии. Всем спасибо, а я пойду спать. Это API заставляло меня спать по 4-6 часов в сутки. Наконец-то высплюсь. Всем спасибо.

ссылка на оригинал статьи http://habrahabr.ru/post/175703/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *