File Coverage

blib/lib/cexio.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             #######################################
2             # CEX.IO Beta Trade API Perl Module #
3             # version 0.2.3 #
4             # #
5             # Author: Michael W. Renz #
6             # Created: 03-Jun-2014 #
7             #######################################
8              
9             =pod
10              
11             =head1 NAME
12              
13             cexio - perl module interface to cex.io/ghash.io's API
14              
15             =head1 SYNOPSIS
16              
17             use cexio;
18             use Data::Dumper;
19              
20             # public functions do not require any options
21             my $cexio_object = new cexio();
22              
23             print Dumper( $cexio_object->ticker("GHS", "BTC" ) ) ."\n";
24             print Dumper( $cexio_object->order_book("GHS", "BTC", "100") ) ."\n";
25             print Dumper( $cexio_object->trade_history("GHS", "BTC", "1184696000") ) ."\n";
26              
27             # private functions require username, key, and secret to be set
28             my $cexio_object = new cexio( { username => "sample-user", key => "this-is-not-a-real-key", secret => "this-is-not-a-real-secret" } );
29              
30             print Dumper( $cexio_object->open_orders("GHS","BTC") ) ."\n";
31             print Dumper( $cexio_object->cancel_order("1") ) ."\n";
32             print Dumper( $cexio_object->place_order("GHS", "BTC", "buy", "1", "0.007") ) ."\n";
33             print Dumper( $cexio_object->hashrate() ) ."\n";
34             print Dumper( $cexio_object->workers() ) ."\n";
35              
36              
37             =head1 DESCRIPTION
38              
39             Implements the cex.io API described at https://cex.io/api as a perl module
40              
41             =cut
42              
43             package cexio;
44              
45             # standard includes
46 1     1   23151 use strict;
  1         3  
  1         50  
47 1     1   4 use warnings;
  1         2  
  1         45  
48 1     1   6 use Exporter;
  1         8  
  1         58  
49 1     1   7 use Carp;
  1         2  
  1         89  
50              
51             my $modname="cexio-perl-module";
52             my $modver="0.2.3";
53              
54 1     1   4 use vars qw($VERSION);
  1         2  
  1         117  
55             $cexio::VERSION = '0.2.3';
56              
57 1     1   468 use JSON;
  0            
  0            
58             use Digest::SHA qw(hmac_sha256_hex);
59             use Hash::Flatten qw(:all);
60             use LWP::UserAgent;
61              
62             my $ua = LWP::UserAgent->new();
63             $ua->agent("${modname}/${modver}");
64             $ua->timeout(1);
65              
66             my $opcount = 0;
67              
68             my %cexio = ( urls => { api_url => "https://cex.io/api" } );
69              
70             # cexio URLs
71             # public/nonauthenticated functions
72             # These are all GET requests
73             $cexio{urls}{api}{ticker} = $cexio{urls}{api_url}. "/ticker/";
74             $cexio{urls}{api}{order_book} = $cexio{urls}{api_url}. "/order_book/";
75             $cexio{urls}{api}{trade_history} = $cexio{urls}{api_url}. "/trade_history/";
76              
77             # private functions
78             # These are all POST requests
79             $cexio{urls}{api}{balance} = $cexio{urls}{api_url}. "/balance/";
80             $cexio{urls}{api}{open_orders} = $cexio{urls}{api_url}. "/open_orders/";
81             $cexio{urls}{api}{cancel_order} = $cexio{urls}{api_url}. "/cancel_order/";
82             $cexio{urls}{api}{place_order} = $cexio{urls}{api_url}. "/place_order/";
83              
84             # ghash.io private functions
85             $cexio{urls}{api}{ghash_url} = $cexio{urls}{api_url}. "/ghash.io";
86             $cexio{urls}{api}{ghash}{hashrate} = $cexio{urls}{api}{ghash_url}. "/hashrate";
87             $cexio{urls}{api}{ghash}{workers} = $cexio{urls}{api}{ghash_url}. "/workers";
88              
89             my $o = new Hash::Flatten();
90              
91             =pod
92              
93             =over 4
94              
95             =item my $cexio_object = new cexio( \%options );
96              
97             The only time you need to pass options to new() is when you are using a private function.
98              
99             The only options you need to pass are 'username', 'key', and 'secret'.
100              
101             =back
102              
103             =cut
104              
105             sub new
106             {
107             my ($class, $options) = @_;
108             $options = {} unless ref $options eq 'HASH';
109             my $self = {
110             %$options
111             };
112              
113             # We only expect the following options:
114             # - key - needed for private functions
115             # - secret - needed for private functions
116             # - username - needed for private functions
117              
118             return bless($self, $class);
119             }
120              
121             =pod
122              
123             =head2 Public functions
124              
125             =over 4
126              
127             =item my $ticker = $cexio_object->ticker( $primary, $secondary );
128              
129             Returns the current ticker for orders of $primary for $secondary.
130              
131             =item my $order_book = $cexio_object->order_book( $primary, $secondary, $depth );
132              
133             Returns the current list of bids and asks for price and amount of $primary for $secondary
134              
135             Setting $depth is optional, and will limit the amount of orders returned.
136              
137             =item my $trade_history = $cexio_object->trade_history( $primary, $secondary, $since );
138              
139             Returns the current trade history of $primary for $secondary since $since trade id.
140              
141             Setting $since is optional, and will limit the number of trades returned.
142              
143             =back
144              
145             =cut
146              
147             # subroutines for public functions
148             sub ticker
149             {
150             my ($self, $primary, $secondary) = @_;
151             return $o->unflatten( $self->_json_get($cexio{urls}{api}{ticker}.$primary."/".$secondary) );
152             }
153              
154             sub order_book
155             {
156             my ($self, $primary, $secondary, $depth) = @_;
157             if ( defined($depth) ) { $secondary .= "/?depth=${depth}"; }
158             return $o->unflatten( $self->_json_get($cexio{urls}{api}{order_book}.$primary."/".$secondary) );
159             }
160              
161             sub trade_history
162             {
163             my ($self, $primary, $secondary, $since) = @_;
164             if ( defined($since) ) { $secondary .= "/?since=${since}"; }
165             return $self->_json_get($cexio{urls}{api}{trade_history}.$primary."/".$secondary);
166             }
167              
168             =pod
169              
170             =head2 Private functions
171              
172             =over 4
173              
174             =item my $open_orders = $cexio_object->open_orders( $primary, $secondary );
175              
176             Returns an array of open orders of $primary for $secondary that includes:
177             id - order id
178             time - timestamp
179             type - buy or sell
180             price - price
181             amount - amount
182             pending - pending amount (if partially executed)
183              
184             =item my $cancel_order = $cexio_object->cancel_order( $order_id );
185              
186             Returns 'true' if $order_id has been found and cancelled.
187              
188             =item my $place_order = $cexio_object->place_order( $primary, $secondary, "buy" | "sell", $quantity, $price );
189              
190             Places an order of $primary for $secondary that is either "buy" or "sell" of $quantity at $price.
191              
192             Returns an associative array representing the order placed:
193             id - order id
194             time - timestamp
195             type - buy or sell
196             price - price
197             amount - amount
198             pending - pending amount (if partially executed)
199              
200             =back
201              
202             =cut
203              
204             # subroutines for private functions
205             sub balance
206             {
207             my ($self) = @_;
208             return $o->unflatten( $self->_json_post( $cexio{urls}{api}{balance} ) );
209             }
210              
211             sub open_orders
212             {
213             my ($self) = @_;
214             return $self->_json_post( $cexio{urls}{api}{open_orders} );
215             }
216              
217             sub cancel_order
218             {
219             my ($self, $id) = @_;
220             $self->{post_message}{id} = $id;
221             return $o->unflatten( $self->_json_post( $cexio{urls}{api}{cancel_order} ) );
222             }
223              
224             sub place_order
225             {
226             my ($self, $primary, $secondary, $type, $amount, $price) = @_;
227              
228             croak("order type must be either 'buy' or 'sell'") unless ($type =~ m/^buy$|^sell$/);
229              
230             $self->{post_message}{type} = $type;
231             $self->{post_message}{amount} = $amount;
232             $self->{post_message}{price} = $price;
233              
234             return $o->unflatten( $self->_json_post( $cexio{urls}{api}{place_order}.$primary."/".$secondary."/" ) );
235             }
236              
237             =pod
238              
239             =head2 GHash.io-specific private functions
240              
241             =over 4
242              
243             =item my $hashrate = $cexio_object->hashrate();
244              
245             Takes no options. Returns an associative array of general mining hashrate statistics for the past day in MH/s.
246              
247             =item my $workers = $cexio_object->workers();
248              
249             Takes no options. Returns an associative array of mining hashrate statistics broken down by workers in MH/s.
250              
251             =back
252              
253             =cut
254              
255             # subroutines for private ghash.io functions
256             sub hashrate
257             {
258             my ($self) = @_;
259             return $o->unflatten( $self->_json_post( $cexio{urls}{api}{ghash}{hashrate} ) );
260             }
261              
262             sub workers
263             {
264             my ($self) = @_;
265             return $o->unflatten( $self->_json_post( $cexio{urls}{api}{ghash}{workers} ) );
266             }
267              
268              
269             # private module functions
270              
271             # This gets called only when a POST operation gets used
272             sub _generate_signature
273             {
274             my ($self) = @_;
275              
276             my $nonce = _generate_nonce();
277              
278             croak("username not defined for private function") if ( !defined($self->{username}) );
279             croak("key not defined for private function") if ( !defined($self->{key}) );
280             croak("secret not defined for private function") if ( !defined($self->{secret}) );
281              
282             # We now have enough information to create a signature
283             $self->{signature} = hmac_sha256_hex($nonce . $self->{username} . $self->{key}, $self->{secret});
284              
285             # fix length of signature to mod4
286             while (length($self->{signature}) % 4) {
287             $self->{signature} .= '=';
288             }
289              
290             # Now let's build a post_message
291             $self->{post_message}{key} = $self->{key};
292             $self->{post_message}{signature} = $self->{signature};
293             $self->{post_message}{nonce} = $nonce;
294              
295             return $self;
296             }
297              
298             sub _generate_nonce
299             {
300             # $opcount is used in case we want to
301             # use the same instance of this module
302             # for multiple POST operations, potentially
303             # within the same second of each other.
304             $opcount++;
305              
306             # we are using epoch time + $opcount to
307             # act as nonce
308             return time . $opcount;
309             }
310              
311             sub _json_get
312             {
313             my ($self, $url) = @_;
314             return decode_json $ua->get( $url )->decoded_content();
315             }
316              
317             sub _json_post
318             {
319             my ($self, $url) = @_;
320             return decode_json $ua->post( $url, $self->_generate_signature()->{post_message} )->decoded_content();
321             }
322              
323             sub TRACE {}
324              
325             1;
326              
327             =pod
328              
329             =head1 CHANGELOG
330              
331             =over 4
332              
333             =item * Changes to POD to fix formatting
334              
335             =item * Fixed _generate_signature private function to merge $self->{post_message} instead of overwriting it
336              
337             =back
338              
339              
340             =head1 TODO
341              
342             =over 4
343              
344             =item * Add comprehensive unit tests to module distribution
345             =item * Add server-side error handling
346             =item * Fix any bugs that anybody reports
347             =item * Write better documentation. Always write better documentation
348              
349             =back
350              
351              
352             =head1 SEE ALSO
353              
354             See https://cex.io/api for the most updated API docs and more details on each of the functions listed here.
355              
356              
357             =head1 VERSION
358              
359             $Id: cexio.pm,v 0.2.3 2014/06/08 09:08:00 CRYPTOGRA Exp $
360              
361              
362             =head1 AUTHOR
363              
364             Michael W. Renz, C<< >>
365              
366              
367             =head1 BUGS
368              
369             Please report any bugs or feature requests to C, or through
370             the web interface at L. I will be notified, and then you'll
371             automatically be notified of progress on your bug as I make changes.
372              
373              
374             =head1 SUPPORT
375              
376             You can find documentation for this module with the perldoc command.
377              
378             perldoc cexio
379              
380              
381             You can also look for information at:
382              
383             =over 4
384              
385             =item * RT: CPAN's request tracker (report bugs here)
386              
387             L
388              
389             =item * AnnoCPAN: Annotated CPAN documentation
390              
391             L
392              
393             =item * CPAN Ratings
394              
395             L
396              
397             =item * Search CPAN
398              
399             L
400              
401             =back
402              
403              
404             =head1 LICENSE AND COPYRIGHT
405              
406             Copyright 2014 Michael W. Renz.
407              
408             This program is free software; you can redistribute it and/or modify it
409             under the terms of the the Artistic License (2.0). You may obtain a
410             copy of the full license at:
411              
412             L
413              
414             Any use, modification, and distribution of the Standard or Modified
415             Versions is governed by this Artistic License. By using, modifying or
416             distributing the Package, you accept this license. Do not use, modify,
417             or distribute the Package, if you do not accept this license.
418              
419             If your Modified Version has been derived from a Modified Version made
420             by someone other than you, you are nevertheless required to ensure that
421             your Modified Version complies with the requirements of this license.
422              
423             This license does not grant you the right to use any trademark, service
424             mark, tradename, or logo of the Copyright Holder.
425              
426             This license includes the non-exclusive, worldwide, free-of-charge
427             patent license to make, have made, use, offer to sell, sell, import and
428             otherwise transfer the Package with respect to any patent claims
429             licensable by the Copyright Holder that are necessarily infringed by the
430             Package. If you institute patent litigation (including a cross-claim or
431             counterclaim) against any party alleging that the Package constitutes
432             direct or contributory patent infringement, then this Artistic License
433             to you shall terminate on the date that such litigation is filed.
434              
435             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
436             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
437             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
438             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
439             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
440             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
441             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
442             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
443              
444             =cut