bits_dealer 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop +45 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +87 -0
- data/LICENSE.txt +21 -0
- data/README.md +56 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bits_dealer.gemspec +34 -0
- data/exe/bits_dealer +5 -0
- data/lib/bits_dealer/cli.rb +38 -0
- data/lib/bits_dealer/commands/balance.rb +10 -0
- data/lib/bits_dealer/commands/books.rb +29 -0
- data/lib/bits_dealer/commands/cancel_order.rb +18 -0
- data/lib/bits_dealer/commands/configure.rb +51 -0
- data/lib/bits_dealer/commands/help.rb +19 -0
- data/lib/bits_dealer/commands/monitor.rb +41 -0
- data/lib/bits_dealer/commands/new_order.rb +67 -0
- data/lib/bits_dealer/commands/open_orders.rb +21 -0
- data/lib/bits_dealer/commands/tickers.rb +19 -0
- data/lib/bits_dealer/config.rb +76 -0
- data/lib/bits_dealer/helper.rb +81 -0
- data/lib/bits_dealer/repl.rb +102 -0
- data/lib/bits_dealer/templates/database.sqlite3 +0 -0
- data/lib/bits_dealer/version.rb +3 -0
- data/lib/bits_dealer.rb +23 -0
- data/lib/ext/hash.rb +5 -0
- data/lib/ext/string.rb +19 -0
- data/spec/bits_dealer_spec.rb +9 -0
- data/spec/spec_helper.rb +14 -0
- metadata +233 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e5a45167f151b6e940c8527c97bebdbd629a3f81
|
4
|
+
data.tar.gz: 96edd3070bd6894c23c947bc7ea5ca2e365e27b6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 222aa629859c829a8c44a038d9a6c988ec0a81f59661262e050ff4d24109898cc95cee879cbdeb23cb5de7753a84401b4dd1bdfc5a64ba41fb7fcc5e7c3ff14e
|
7
|
+
data.tar.gz: ebb0af4234c7a744967c95a7f3c2da8af8666f7f55aecba23946b76efb84fe2ee261a7e5a4a06de3b87b588dab59aa05ee17ceb1b51edc7a7bf4520aba791c34
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.4
|
3
|
+
Style/Documentation:
|
4
|
+
Enabled: false
|
5
|
+
Style/TrailingCommaInLiteral:
|
6
|
+
EnforcedStyleForMultiline: comma
|
7
|
+
Style/AlignParameters:
|
8
|
+
EnforcedStyle: with_fixed_indentation
|
9
|
+
Style/PercentLiteralDelimiters:
|
10
|
+
PreferredDelimiters:
|
11
|
+
default: []
|
12
|
+
'%i': []
|
13
|
+
'%w': []
|
14
|
+
Style/FrozenStringLiteralComment:
|
15
|
+
EnforcedStyle: always
|
16
|
+
Style/TrailingBlankLines:
|
17
|
+
EnforcedStyle: final_blank_line
|
18
|
+
Style/LambdaCall:
|
19
|
+
Enabled: false
|
20
|
+
Style/IndentHash:
|
21
|
+
Enabled: false
|
22
|
+
Style/AlignHash:
|
23
|
+
Enabled: false
|
24
|
+
Metrics/ClassLength:
|
25
|
+
Enabled: false
|
26
|
+
Metrics/MethodLength:
|
27
|
+
Enabled: false
|
28
|
+
Metrics/AbcSize:
|
29
|
+
Enabled: false
|
30
|
+
Metrics/LineLength:
|
31
|
+
Max: 120
|
32
|
+
# To make it possible to copy or click on URIs in the code, we allow lines
|
33
|
+
# containing a URI to be longer than Max.
|
34
|
+
AllowHeredoc: true
|
35
|
+
AllowURI: true
|
36
|
+
URISchemes:
|
37
|
+
- http
|
38
|
+
- https
|
39
|
+
# The IgnoreCopDirectives option causes the LineLength rule to ignore cop
|
40
|
+
# directives like '# rubocop: enable ...' when calculating a line's length.
|
41
|
+
IgnoreCopDirectives: false
|
42
|
+
Metrics/BlockLength:
|
43
|
+
Enabled: false
|
44
|
+
Style/NumericLiterals:
|
45
|
+
Enabled: false
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.2
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
bits_dealer (0.1.0)
|
5
|
+
bitsor (~> 0.1.2)
|
6
|
+
money
|
7
|
+
parallel
|
8
|
+
retries
|
9
|
+
sequel
|
10
|
+
sqlite3
|
11
|
+
terminal-table
|
12
|
+
tty-prompt
|
13
|
+
|
14
|
+
GEM
|
15
|
+
remote: https://rubygems.org/
|
16
|
+
specs:
|
17
|
+
bitsor (0.1.2)
|
18
|
+
typhoeus (~> 1.3)
|
19
|
+
coderay (1.1.2)
|
20
|
+
concurrent-ruby (1.0.5)
|
21
|
+
diff-lcs (1.3)
|
22
|
+
equatable (0.5.0)
|
23
|
+
ethon (0.11.0)
|
24
|
+
ffi (>= 1.3.0)
|
25
|
+
ffi (1.9.18)
|
26
|
+
hitimes (1.2.6)
|
27
|
+
i18n (0.9.1)
|
28
|
+
concurrent-ruby (~> 1.0)
|
29
|
+
method_source (0.9.0)
|
30
|
+
money (6.10.1)
|
31
|
+
i18n (>= 0.6.4, < 1.0)
|
32
|
+
necromancer (0.4.0)
|
33
|
+
parallel (1.12.1)
|
34
|
+
pastel (0.7.2)
|
35
|
+
equatable (~> 0.5.0)
|
36
|
+
tty-color (~> 0.4.0)
|
37
|
+
pry (0.11.3)
|
38
|
+
coderay (~> 1.1.0)
|
39
|
+
method_source (~> 0.9.0)
|
40
|
+
rake (10.5.0)
|
41
|
+
retries (0.0.5)
|
42
|
+
rspec (3.7.0)
|
43
|
+
rspec-core (~> 3.7.0)
|
44
|
+
rspec-expectations (~> 3.7.0)
|
45
|
+
rspec-mocks (~> 3.7.0)
|
46
|
+
rspec-core (3.7.0)
|
47
|
+
rspec-support (~> 3.7.0)
|
48
|
+
rspec-expectations (3.7.0)
|
49
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
50
|
+
rspec-support (~> 3.7.0)
|
51
|
+
rspec-mocks (3.7.0)
|
52
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
53
|
+
rspec-support (~> 3.7.0)
|
54
|
+
rspec-support (3.7.0)
|
55
|
+
sequel (5.3.0)
|
56
|
+
sqlite3 (1.3.13)
|
57
|
+
terminal-table (1.8.0)
|
58
|
+
unicode-display_width (~> 1.1, >= 1.1.1)
|
59
|
+
timers (4.1.2)
|
60
|
+
hitimes
|
61
|
+
tty-color (0.4.2)
|
62
|
+
tty-cursor (0.5.0)
|
63
|
+
tty-prompt (0.13.2)
|
64
|
+
necromancer (~> 0.4.0)
|
65
|
+
pastel (~> 0.7.0)
|
66
|
+
timers (~> 4.1.2)
|
67
|
+
tty-cursor (~> 0.5.0)
|
68
|
+
tty-reader (~> 0.1.0)
|
69
|
+
tty-reader (0.1.0)
|
70
|
+
wisper (~> 2.0.0)
|
71
|
+
typhoeus (1.3.0)
|
72
|
+
ethon (>= 0.9.0)
|
73
|
+
unicode-display_width (1.3.0)
|
74
|
+
wisper (2.0.0)
|
75
|
+
|
76
|
+
PLATFORMS
|
77
|
+
ruby
|
78
|
+
|
79
|
+
DEPENDENCIES
|
80
|
+
bits_dealer!
|
81
|
+
bundler (~> 1.16)
|
82
|
+
pry
|
83
|
+
rake (~> 10.0)
|
84
|
+
rspec (~> 3.0)
|
85
|
+
|
86
|
+
BUNDLED WITH
|
87
|
+
1.16.0
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Adrian Castillo
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# BitsDealer
|
2
|
+
|
3
|
+
BitsDealer is a command line interface that helps you to manage your bitso account, like buy coins, sell them, check ticks or your orders,
|
4
|
+
it makes use of [Bitsor](https://github.com/rodacato/bitsor) gem, a Bitso V3 API.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Simply run the following command to install it on your current ruby version (2.4.2 is recommended)
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem install bits_dealer
|
12
|
+
```
|
13
|
+
|
14
|
+
Once you install the gem, run the command `bits_dealer` to setup you account, you will need your client_id aswell as the API keys.
|
15
|
+
|
16
|
+
The configuration steps will ask you for a password to encrypt all you information on your home folder, and ask you again each time you open
|
17
|
+
the terminal, all this for security reasons.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
BitsDealer support the following actions:
|
22
|
+
|
23
|
+
```
|
24
|
+
balance
|
25
|
+
books
|
26
|
+
cancel_order
|
27
|
+
new_order
|
28
|
+
open_order
|
29
|
+
tickers
|
30
|
+
help / ?
|
31
|
+
exit / quit
|
32
|
+
```
|
33
|
+
|
34
|
+
You can call them manually from the console after execute `bits_dealer` or through the help menu.
|
35
|
+
|
36
|
+
### Tips
|
37
|
+
|
38
|
+
Type `?` to access the quick menu
|
39
|
+
|
40
|
+
Type `quit` or `exit` to close the session
|
41
|
+
|
42
|
+
If you dont want to perform the command you can cancel it with `CTRL + c`.
|
43
|
+
|
44
|
+
## Development
|
45
|
+
|
46
|
+
After checking out the repo, run rake install to intall this gem locally,
|
47
|
+
|
48
|
+
then open it from the executables folder `exe/bits_dealer`
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rodacato/bits_dealer.
|
53
|
+
|
54
|
+
## License
|
55
|
+
|
56
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "bits_dealer"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/bits_dealer.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "bits_dealer/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "bits_dealer"
|
8
|
+
spec.version = BitsDealer::VERSION
|
9
|
+
spec.authors = ["Adrian Castillo"]
|
10
|
+
spec.email = ["rodacato@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Another gem REPL to manage your bitso account}
|
13
|
+
spec.description = %q{Another gem REPL to manage your bitso account}
|
14
|
+
spec.homepage = "https://github.com/rodacato/bits_dealer"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($\)
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = ["bits_dealer"]
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency 'bitsor', "~> 0.1.2"
|
23
|
+
spec.add_dependency 'money'
|
24
|
+
spec.add_dependency 'parallel'
|
25
|
+
spec.add_dependency 'retries'
|
26
|
+
spec.add_dependency 'sequel'
|
27
|
+
spec.add_dependency 'sqlite3'
|
28
|
+
spec.add_dependency 'terminal-table'
|
29
|
+
spec.add_dependency 'tty-prompt'
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
34
|
+
end
|
data/exe/bits_dealer
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'bitsor'
|
2
|
+
|
3
|
+
module BitsDealer
|
4
|
+
class CLI
|
5
|
+
attr_accessor :options
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
self.options = parse_options(args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
BitsDealer::REPL.new(options).start
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# @private
|
18
|
+
def parse_options(args)
|
19
|
+
{}.tap do |options|
|
20
|
+
|
21
|
+
OptionParser.new do |opts|
|
22
|
+
opts.banner = "Usage: bits_dealer [options]"
|
23
|
+
|
24
|
+
opts.on('--reset', 'Remove your configuration files') do
|
25
|
+
options[:reset] = true
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
29
|
+
puts opts
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
end.parse!(args)
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module BitsDealer
|
4
|
+
module Books
|
5
|
+
ALL_BOOKS = [
|
6
|
+
OpenStruct.new({ id: "btc_mxn", name: :bitcoin, place_order: true, exchange_order: false, base_price_difference: 0.01 }),
|
7
|
+
OpenStruct.new({ id: "eth_mxn", name: :etherium, place_order: true, exchange_order: false, base_price_difference: 0.01 }),
|
8
|
+
OpenStruct.new({ id: "eth_btc", name: :etherium_bitcoin, place_order: false, exchange_order: true, base_price_difference: 0.00000001 }),
|
9
|
+
OpenStruct.new({ id: "xrp_mxn", name: :ripple, place_order: true, exchange_order: false, base_price_difference: 0.01 }),
|
10
|
+
OpenStruct.new({ id: "xrp_btc", name: :ripple_bitcoin, place_order: false, exchange_order: true, base_price_difference: 0.00000001 }),
|
11
|
+
]
|
12
|
+
|
13
|
+
PLACE_ORDER_BOOKS = ALL_BOOKS.select(&:place_order)
|
14
|
+
EXCHANGE_ORDER_BOOKS = ALL_BOOKS.select(&:exchange_order)
|
15
|
+
|
16
|
+
def books
|
17
|
+
table = Terminal::Table.new(
|
18
|
+
:headings => [:name, :book],
|
19
|
+
:rows => ALL_BOOKS.map{|book| [book.name, book.id] }
|
20
|
+
)
|
21
|
+
|
22
|
+
prompt.say table
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module BitsDealer
|
2
|
+
module CancelOrder
|
3
|
+
def cancel_order(oid: nil)
|
4
|
+
unless oid
|
5
|
+
book = helper.ask_book(books: BitsDealer::Books::ALL_BOOKS)
|
6
|
+
orders = with_retries(:max_tries => 3) { Bitsor.open_orders(book: book.id, limit: 100) }
|
7
|
+
order = helper.ask_order(orders: orders)
|
8
|
+
oid = order[:oid]
|
9
|
+
end
|
10
|
+
|
11
|
+
cancelled_order = with_retries(:max_tries => 3) { Bitsor.cancel_order(oid: oid) }
|
12
|
+
prompt.ok "Order ##{cancelled_order.first} cancelled."
|
13
|
+
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module BitsDealer
|
2
|
+
module Configure
|
3
|
+
def configure
|
4
|
+
if !BitsDealer::Config.needs_configuration?
|
5
|
+
prompt.say "BitsDealer is already configured"
|
6
|
+
end
|
7
|
+
|
8
|
+
client_id = prompt.ask("What is your cliend id?")
|
9
|
+
api_key = prompt.ask("What is your api key?")
|
10
|
+
api_secret = prompt.ask("What is your api secret?")
|
11
|
+
password = nil
|
12
|
+
password_input = 0
|
13
|
+
|
14
|
+
loop do
|
15
|
+
password = prompt.mask("\nSet a password for your configuration: ")
|
16
|
+
password_confirmation = prompt.mask("confirm your password: ")
|
17
|
+
|
18
|
+
if password == password_confirmation
|
19
|
+
break
|
20
|
+
else
|
21
|
+
password_input += 1
|
22
|
+
prompt.error "Your password didnt match, please try it again"
|
23
|
+
end
|
24
|
+
|
25
|
+
if password_input == 3
|
26
|
+
prompt.warn "Configuration failed, try again!!!"
|
27
|
+
break
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
@config = BitsDealer::Config.create({ client_id: client_id, api_key: api_key, api_secret: api_secret, password: password })
|
32
|
+
|
33
|
+
prompt.ok 'You have configured BitsDealer.'
|
34
|
+
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def reset
|
39
|
+
confirm = prompt.yes?("Are you sure you want to delete your configuration?") do |q|
|
40
|
+
q.default false
|
41
|
+
end
|
42
|
+
|
43
|
+
if confirm
|
44
|
+
BitsDealer::Config.reset
|
45
|
+
prompt.ok "Configuration removed."
|
46
|
+
end
|
47
|
+
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module BitsDealer
|
2
|
+
module Help
|
3
|
+
def help
|
4
|
+
option = prompt.select("Choose what you want to do?") do |menu|
|
5
|
+
menu.enum '.'
|
6
|
+
menu.choice 'Nevermind', 'nothing'
|
7
|
+
|
8
|
+
menu.choice 'Cancel order', 'cancel_order'
|
9
|
+
menu.choice 'Buy order', 'buy_order'
|
10
|
+
menu.choice 'Sell order', 'sell_order'
|
11
|
+
menu.choice 'Exchange order', 'exchange_order'
|
12
|
+
end
|
13
|
+
|
14
|
+
process(option)
|
15
|
+
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
require 'retries'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module BitsDealer
|
6
|
+
module Monitor
|
7
|
+
def start_monitors
|
8
|
+
@monitor_data = OpenStruct.new({
|
9
|
+
tickers: [],
|
10
|
+
stats: {}
|
11
|
+
})
|
12
|
+
|
13
|
+
_monit_tickers
|
14
|
+
_process_stats
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :monitor
|
20
|
+
|
21
|
+
def monitor_tickers
|
22
|
+
Thread.kill(@tickers_thread) if @tickers_thread
|
23
|
+
Thread.abort_on_exception = true
|
24
|
+
@tickers_thread = Thread.new do
|
25
|
+
loop do
|
26
|
+
books = BitsDealer::Books::ALL_BOOKS
|
27
|
+
tickers = Parallel.map(books) do |book|
|
28
|
+
with_retries(:max_tries => 3) { Bitsor.ticker(book: book.id) }
|
29
|
+
end
|
30
|
+
@monitor_data.tickers = [{ created_at: Time.now.to_i, data: tickers }] + @monitor_data.tickers[0..99]
|
31
|
+
sleep 15
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def process_stats
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module BitsDealer
|
2
|
+
module NewOrder
|
3
|
+
DEFAULT_ORDER_AMOUNT = 50.0
|
4
|
+
|
5
|
+
def buy_order
|
6
|
+
book = helper.ask_book
|
7
|
+
minor = prompt.ask("How much MXN invest?", convert: :float, default: DEFAULT_ORDER_AMOUNT, help_color: :green)
|
8
|
+
|
9
|
+
ticker = with_retries(:max_tries => 3) { Bitsor.ticker(book: book.id) }
|
10
|
+
ticker_price = ticker[:bid] + book.base_price_difference
|
11
|
+
helper.print_tickers_table(tickers: [ticker])
|
12
|
+
price = prompt.ask("What price?", convert: :float, default: ticker_price, help_color: :green)
|
13
|
+
|
14
|
+
begin
|
15
|
+
order = helper.place_order(book, :buy, minor, price)
|
16
|
+
prompt.ok("Order ##{order[:oid]} placed.")
|
17
|
+
rescue => error
|
18
|
+
prompt.error("Failed to place the order with: #{error.message}")
|
19
|
+
end
|
20
|
+
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def sell_order
|
25
|
+
book = helper.ask_book
|
26
|
+
|
27
|
+
balance(filter: book.id.split('_'))
|
28
|
+
minor = prompt.ask("How much MXN collect?", convert: :float, default: DEFAULT_ORDER_AMOUNT, help_color: :green)
|
29
|
+
|
30
|
+
ticker = with_retries(:max_tries => 3) { Bitsor.ticker(book: book.id) }
|
31
|
+
ticker_price = ticker[:ask] - book.base_price_difference
|
32
|
+
helper.print_tickers_table(tickers: [ticker])
|
33
|
+
price = prompt.ask("What price?", convert: :float, default: ticker_price, help_color: :green)
|
34
|
+
|
35
|
+
begin
|
36
|
+
order = helper.place_order(book, :sell, minor, price)
|
37
|
+
prompt.ok("Order ##{order[:oid]} placed.")
|
38
|
+
rescue => error
|
39
|
+
prompt.error("Failed to place the order with: #{error.message}")
|
40
|
+
end
|
41
|
+
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def exchange_order
|
46
|
+
book = helper.ask_book(books: BitsDealer::Books::EXCHANGE_ORDER_BOOKS)
|
47
|
+
book_names = book.id.split('_')
|
48
|
+
|
49
|
+
balance(filter: book_names)
|
50
|
+
minor = prompt.ask("How much #{book_names.first} convert?", convert: :float)
|
51
|
+
|
52
|
+
ticker = with_retries(:max_tries => 3) { Bitsor.ticker(book: book.id) }
|
53
|
+
ticker_price = ticker[:ask] - book.base_price_difference
|
54
|
+
helper.print_tickers_table(tickers: [ticker])
|
55
|
+
price = prompt.ask("What price?", convert: :float, default: '%.8f' % ticker_price, help_color: :green)
|
56
|
+
|
57
|
+
begin
|
58
|
+
order = helper.exchange_order(book, minor, price)
|
59
|
+
prompt.ok("Exchange order ##{order[:oid]} placed.")
|
60
|
+
rescue => error
|
61
|
+
prompt.error("Failed to place the order with: #{error.message}")
|
62
|
+
end
|
63
|
+
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module BitsDealer
|
2
|
+
module OpenOrders
|
3
|
+
def open_orders
|
4
|
+
book = helper.ask_book
|
5
|
+
orders = with_retries(:max_tries => 3) { Bitsor.open_orders(book: book.id, limit: 100) }
|
6
|
+
|
7
|
+
table = Terminal::Table.new(
|
8
|
+
:headings => [:book, :oid, :side, :amount, :total_price, :unit_price, :unfilled],
|
9
|
+
:rows => orders.map do |order|
|
10
|
+
side_formatted = order[:side] == 'buy' ? formatter.green(order[:side]) : formatter.red(order[:side])
|
11
|
+
[order[:book], order[:oid], side_formatted, order[:original_amount], order[:original_value], order[:price], order[:unfilled_amount], ]
|
12
|
+
end
|
13
|
+
)
|
14
|
+
|
15
|
+
prompt.say table
|
16
|
+
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
require 'retries'
|
3
|
+
|
4
|
+
module BitsDealer
|
5
|
+
module Tickers
|
6
|
+
def tickers
|
7
|
+
books = BitsDealer::Books::ALL_BOOKS
|
8
|
+
|
9
|
+
tickers = Parallel.map(books) do |book|
|
10
|
+
with_retries(:max_tries => 3) { Bitsor.ticker(book: book.id) }
|
11
|
+
end
|
12
|
+
|
13
|
+
helper.print_tickers_table(tickers: tickers)
|
14
|
+
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'sequel'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module BitsDealer
|
6
|
+
class Config
|
7
|
+
attr_reader :credentials, :database
|
8
|
+
|
9
|
+
def initialize(password)
|
10
|
+
load_configurations(password)
|
11
|
+
connect_to_bitso
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create(options)
|
15
|
+
ensure_config_folder
|
16
|
+
create_secrets_file(options)
|
17
|
+
new(options[:password])
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.reset
|
21
|
+
FileUtils.remove_dir("#{Dir.home}/.bits_dealer")
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.needs_configuration?
|
25
|
+
!File.exist?("#{Dir.home}/.bits_dealer/secrets.yml")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def connect_to_bitso
|
31
|
+
Bitsor.configure do |c|
|
32
|
+
c.client_id = credentials[:client_id]
|
33
|
+
c.api_key = credentials[:api_key]
|
34
|
+
c.api_secret = credentials[:api_secret]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_configurations(password)
|
39
|
+
if File.exist?("#{Dir.home}/.bits_dealer/secrets.yml")
|
40
|
+
secrets = YAML.load_file("#{Dir.home}/.bits_dealer/secrets.yml")
|
41
|
+
|
42
|
+
@credentials = {
|
43
|
+
client_id: secrets[:client_id].decrypt(password),
|
44
|
+
api_key: secrets[:api_key].decrypt(password),
|
45
|
+
api_secret: secrets[:api_secret].decrypt(password),
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
if File.exist?("#{Dir.home}/.bits_dealer/database.sqlite3")
|
50
|
+
@database = Sequel.sqlite("#{Dir.home}/.bits_dealer/database.sqlite3")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.create_secrets_file(values)
|
55
|
+
File.open("#{Dir.home}/.bits_dealer/secrets.yml", "w") do |file|
|
56
|
+
file.write(
|
57
|
+
{
|
58
|
+
client_id: values[:client_id].encrypt(values[:password]),
|
59
|
+
api_key: values[:api_key].encrypt(values[:password]),
|
60
|
+
api_secret: values[:api_secret].encrypt(values[:password]),
|
61
|
+
}.to_yaml
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.create_database
|
67
|
+
FileUtils.cp('lib/bits_dealer/templates/database.sqlite3', "#{Dir.home}/.bits_dealer/database.sqlite3")
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.ensure_config_folder
|
71
|
+
unless File.directory?("#{Dir.home}/.bits_dealer")
|
72
|
+
FileUtils.mkdir_p("#{Dir.home}/.bits_dealer")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module BitsDealer
|
2
|
+
class Helper
|
3
|
+
attr_reader :prompt, :formatter
|
4
|
+
|
5
|
+
def initialize(prompt:, formatter:)
|
6
|
+
@prompt = prompt
|
7
|
+
@formatter = formatter
|
8
|
+
end
|
9
|
+
|
10
|
+
def ask_book(books: nil)
|
11
|
+
book_options = books || BitsDealer::Books::PLACE_ORDER_BOOKS
|
12
|
+
|
13
|
+
book = prompt.select("Choose the book?") do |menu|
|
14
|
+
menu.enum '.'
|
15
|
+
|
16
|
+
book_options.each do |book|
|
17
|
+
menu.choice book.name, book
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def ask_order(orders:)
|
23
|
+
book = prompt.select("Choose the order?") do |menu|
|
24
|
+
menu.enum '.'
|
25
|
+
|
26
|
+
orders.each do |order|
|
27
|
+
side_formatted = order[:side] == 'buy' ? formatter.green(order[:side]) : formatter.red(order[:side])
|
28
|
+
help_text = formatter.magenta("(#{order[:original_value]})")
|
29
|
+
menu.choice "#{side_formatted} #{order[:original_amount]} at #{order[:price]} #{help_text}", order
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_tickers_table(tickers:)
|
35
|
+
tickers_formatted = tickers.sort{|a, b| a[:book] <=> b[:book] }.each_with_object({}){ |element, hsh| hsh[element[:book]] = element; hsh }
|
36
|
+
|
37
|
+
table = Terminal::Table.new(
|
38
|
+
:headings => [:book, :last, :last_mxn, :bid, :ask, 'high/low'],
|
39
|
+
:rows => tickers.map do |ticker|
|
40
|
+
if ['xrp_btc', 'eth_btc'].include?(ticker[:book]) && tickers_formatted['btc_mxn']
|
41
|
+
last_mxn = ticker[:last] * tickers_formatted['btc_mxn'][:last]
|
42
|
+
end
|
43
|
+
|
44
|
+
if ticker[:book] == 'xrp_btc'
|
45
|
+
next [ticker[:book], '%.8f' % ticker[:last], last_mxn, formatter.green('%.8f' % ticker[:bid]), formatter.red('%.8f' % ticker[:ask]), "#{'%.8f' % ticker[:high]} / #{'%.8f' % ticker[:low]}"]
|
46
|
+
end
|
47
|
+
|
48
|
+
[ticker[:book], ticker[:last], last_mxn, formatter.green(ticker[:bid]), formatter.red(ticker[:ask]), "#{ticker[:high]} / #{ticker[:low]}"]
|
49
|
+
end
|
50
|
+
)
|
51
|
+
|
52
|
+
prompt.say table
|
53
|
+
end
|
54
|
+
|
55
|
+
def print_account_balance(balance:, filter: nil)
|
56
|
+
balances = balance[:balances].map{|currency| currency.values }
|
57
|
+
balances = balances.select{|balance| filter.include?(balance.first) } if filter
|
58
|
+
|
59
|
+
table = Terminal::Table.new(
|
60
|
+
:headings => [:currency, :available, :locked, :total, :pending_deposit, :pending_withdrawal],
|
61
|
+
:rows => balances
|
62
|
+
)
|
63
|
+
|
64
|
+
prompt.say table
|
65
|
+
end
|
66
|
+
|
67
|
+
def place_order(book, side, minor, price)
|
68
|
+
major = (minor/price).round(6)
|
69
|
+
|
70
|
+
with_retries(:max_tries => 3) {
|
71
|
+
Bitsor.place_order(book: book.id, side: side, type: :limit, major: major.to_s, price: price.to_s)
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def exchange_order(book, minor, price)
|
76
|
+
with_retries(:max_tries => 3) {
|
77
|
+
Bitsor.place_order(book: book.id, side: :sell, type: :limit, major: minor.to_s, price: price.to_s)
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require "bits_dealer/commands/balance"
|
2
|
+
require "bits_dealer/commands/books"
|
3
|
+
require "bits_dealer/commands/cancel_order"
|
4
|
+
require "bits_dealer/commands/configure"
|
5
|
+
require "bits_dealer/commands/help"
|
6
|
+
require "bits_dealer/commands/new_order"
|
7
|
+
require "bits_dealer/commands/open_orders"
|
8
|
+
require "bits_dealer/commands/tickers"
|
9
|
+
require "bits_dealer/commands/monitor"
|
10
|
+
|
11
|
+
require "bits_dealer/helper"
|
12
|
+
|
13
|
+
require 'readline'
|
14
|
+
|
15
|
+
module BitsDealer
|
16
|
+
class REPL
|
17
|
+
include BitsDealer::Balance
|
18
|
+
include BitsDealer::Books
|
19
|
+
include BitsDealer::CancelOrder
|
20
|
+
include BitsDealer::Configure
|
21
|
+
include BitsDealer::Help
|
22
|
+
include BitsDealer::NewOrder
|
23
|
+
include BitsDealer::OpenOrders
|
24
|
+
include BitsDealer::Tickers
|
25
|
+
|
26
|
+
include BitsDealer::Monitor
|
27
|
+
|
28
|
+
def initialize(options={})
|
29
|
+
@options = options
|
30
|
+
end
|
31
|
+
|
32
|
+
def start
|
33
|
+
ensure_configuration
|
34
|
+
|
35
|
+
prompt.say "Hello there! ready to rock in bitso."
|
36
|
+
|
37
|
+
while command = Readline.readline("> ")
|
38
|
+
case command
|
39
|
+
when "help", '?'
|
40
|
+
help
|
41
|
+
when "exit", "quit"
|
42
|
+
break
|
43
|
+
else
|
44
|
+
process(command)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
prompt.say "Goodbye, I hope you made money!\n"
|
49
|
+
end
|
50
|
+
|
51
|
+
def process(command)
|
52
|
+
begin
|
53
|
+
result = eval(command)
|
54
|
+
prompt.say "=> #{result}\n" if result
|
55
|
+
rescue TTY::Reader::InputInterrupt => e
|
56
|
+
prompt.warn "\nGot it, lets do something else."
|
57
|
+
rescue SyntaxError => e
|
58
|
+
@last_error = e
|
59
|
+
prompt.warn "Oops, seems to have been some error."
|
60
|
+
rescue NameError => e
|
61
|
+
@last_error = e
|
62
|
+
prompt.warn "Oops, you tried to use a method or variable that doesn't exist."
|
63
|
+
rescue ArgumentError => e
|
64
|
+
@last_error = e
|
65
|
+
prompt.warn "Oops, you tried to use a method without the right parameters."
|
66
|
+
rescue => e
|
67
|
+
@last_error = e
|
68
|
+
prompt.warn "Opps, didnt worked, something bad happened."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def prompt
|
75
|
+
@prompt ||= ::TTY::Prompt.new(enable_color: true, prefix: '> ', track_history: false)
|
76
|
+
end
|
77
|
+
|
78
|
+
def formatter
|
79
|
+
@formatter ||= Pastel.new
|
80
|
+
end
|
81
|
+
|
82
|
+
def helper
|
83
|
+
@helper ||= BitsDealer::Helper.new(prompt: prompt, formatter: formatter)
|
84
|
+
end
|
85
|
+
|
86
|
+
def nothing
|
87
|
+
prompt.say 'Alright!'
|
88
|
+
end
|
89
|
+
|
90
|
+
def ensure_configuration
|
91
|
+
if BitsDealer::Config.needs_configuration?
|
92
|
+
prompt.error("\nIMPORTANT!!!\nBitsDealer is not configured, to start using it type 'configure'\n")
|
93
|
+
else
|
94
|
+
prompt.warn("Hey, we found some configuration files")
|
95
|
+
password = prompt.mask("Please input your password to load them: ")
|
96
|
+
@config = Config.new(password)
|
97
|
+
prompt.ok("Configuration loaded.\n")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
Binary file
|
data/lib/bits_dealer.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require "json"
|
3
|
+
require "pry"
|
4
|
+
|
5
|
+
require 'bitsor'
|
6
|
+
require "tty-prompt"
|
7
|
+
require 'terminal-table'
|
8
|
+
require 'parallel'
|
9
|
+
require 'retries'
|
10
|
+
require 'sqlite3'
|
11
|
+
require 'sequel'
|
12
|
+
|
13
|
+
require 'ext/string'
|
14
|
+
require 'ext/hash'
|
15
|
+
|
16
|
+
require "bits_dealer/cli"
|
17
|
+
require "bits_dealer/config"
|
18
|
+
require "bits_dealer/repl"
|
19
|
+
require "bits_dealer/version"
|
20
|
+
|
21
|
+
module BitsDealer
|
22
|
+
# Your code goes here...
|
23
|
+
end
|
data/lib/ext/hash.rb
ADDED
data/lib/ext/string.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
class String
|
4
|
+
def encrypt(key)
|
5
|
+
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').encrypt
|
6
|
+
cipher.key = Digest::SHA1.hexdigest(key)[0..23]
|
7
|
+
s = cipher.update(self) + cipher.final
|
8
|
+
|
9
|
+
s.unpack('H*')[0].upcase
|
10
|
+
end
|
11
|
+
|
12
|
+
def decrypt(key)
|
13
|
+
cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
|
14
|
+
cipher.key = Digest::SHA1.hexdigest(key)[0..23]
|
15
|
+
s = [self].pack("H*").unpack("C*").pack("c*")
|
16
|
+
|
17
|
+
cipher.update(s) + cipher.final
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "bits_dealer"
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
# Enable flags like --only-failures and --next-failure
|
6
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
7
|
+
|
8
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
9
|
+
config.disable_monkey_patching!
|
10
|
+
|
11
|
+
config.expect_with :rspec do |c|
|
12
|
+
c.syntax = :expect
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bits_dealer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adrian Castillo
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bitsor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: money
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: parallel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: retries
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sequel
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: terminal-table
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: tty-prompt
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: bundler
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1.16'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.16'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rake
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '10.0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '10.0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rspec
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '3.0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '3.0'
|
167
|
+
description: Another gem REPL to manage your bitso account
|
168
|
+
email:
|
169
|
+
- rodacato@gmail.com
|
170
|
+
executables:
|
171
|
+
- bits_dealer
|
172
|
+
extensions: []
|
173
|
+
extra_rdoc_files: []
|
174
|
+
files:
|
175
|
+
- ".gitignore"
|
176
|
+
- ".rspec"
|
177
|
+
- ".rubocop"
|
178
|
+
- ".ruby-version"
|
179
|
+
- ".travis.yml"
|
180
|
+
- Gemfile
|
181
|
+
- Gemfile.lock
|
182
|
+
- LICENSE.txt
|
183
|
+
- README.md
|
184
|
+
- Rakefile
|
185
|
+
- bin/console
|
186
|
+
- bin/setup
|
187
|
+
- bits_dealer.gemspec
|
188
|
+
- exe/bits_dealer
|
189
|
+
- lib/bits_dealer.rb
|
190
|
+
- lib/bits_dealer/cli.rb
|
191
|
+
- lib/bits_dealer/commands/balance.rb
|
192
|
+
- lib/bits_dealer/commands/books.rb
|
193
|
+
- lib/bits_dealer/commands/cancel_order.rb
|
194
|
+
- lib/bits_dealer/commands/configure.rb
|
195
|
+
- lib/bits_dealer/commands/help.rb
|
196
|
+
- lib/bits_dealer/commands/monitor.rb
|
197
|
+
- lib/bits_dealer/commands/new_order.rb
|
198
|
+
- lib/bits_dealer/commands/open_orders.rb
|
199
|
+
- lib/bits_dealer/commands/tickers.rb
|
200
|
+
- lib/bits_dealer/config.rb
|
201
|
+
- lib/bits_dealer/helper.rb
|
202
|
+
- lib/bits_dealer/repl.rb
|
203
|
+
- lib/bits_dealer/templates/database.sqlite3
|
204
|
+
- lib/bits_dealer/version.rb
|
205
|
+
- lib/ext/hash.rb
|
206
|
+
- lib/ext/string.rb
|
207
|
+
- spec/bits_dealer_spec.rb
|
208
|
+
- spec/spec_helper.rb
|
209
|
+
homepage: https://github.com/rodacato/bits_dealer
|
210
|
+
licenses:
|
211
|
+
- MIT
|
212
|
+
metadata: {}
|
213
|
+
post_install_message:
|
214
|
+
rdoc_options: []
|
215
|
+
require_paths:
|
216
|
+
- lib
|
217
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - ">="
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
223
|
+
requirements:
|
224
|
+
- - ">="
|
225
|
+
- !ruby/object:Gem::Version
|
226
|
+
version: '0'
|
227
|
+
requirements: []
|
228
|
+
rubyforge_project:
|
229
|
+
rubygems_version: 2.6.13
|
230
|
+
signing_key:
|
231
|
+
specification_version: 4
|
232
|
+
summary: Another gem REPL to manage your bitso account
|
233
|
+
test_files: []
|