├── .coveragerc ├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE.TXT ├── README.md ├── btceapi ├── __init__.py ├── common.py ├── keyhandler.py ├── public.py ├── scraping.py └── trade.py ├── samples ├── cancel-orders.py ├── compute-account-value.py ├── place-order.py ├── print-account-info.py ├── print-trans-history.py ├── show-api-info.py ├── show-chat.py ├── show-depth.py ├── show-history.py ├── show-tickers.py └── watch.py ├── setup.py └── test ├── test_common.py ├── test_keyhandler.py ├── test_public.py ├── test_scraping.py └── test_trade.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | */python?.?/* 4 | */site-packages/nose/* 5 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | repo_token: OX6m7Vkhv9G1bi7HC7ZdP4j7jTKw0r3WH -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | *~ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | - "3.5-dev" 9 | - "3.6" 10 | - "3.6-dev" 11 | - "3.7-dev" 12 | - "nightly" 13 | - "pypy" 14 | - "pypy3" 15 | install: 16 | pip install nose coveralls coverage 17 | script: 18 | nosetests --with-coverage --cover-package=btceapi 19 | after_success: 20 | coveralls 21 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2017 CodeReclaimers, LLC 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | btce-api 2 | ======== 3 | 4 | [![Build Status](https://travis-ci.org/CodeReclaimers/btce-api.svg)](https://travis-ci.org/CodeReclaimers/btce-api) 5 | [![Coverage Status](https://coveralls.io/repos/github/CodeReclaimers/btce-api/badge.svg)](https://coveralls.io/github/CodeReclaimers/btce-api) 6 | 7 | NOTE: Due to the closure of BTC-e, this repository is no longer being maintained. 8 | I am leaving it here in case some of the code proves useful for the development of 9 | similar tools for other exchanges. 10 | 11 | This library provides a wrapper (hopefully a convenient one) around the public 12 | and trading APIs of the BTC-e.com exchange site. So that you don't have to 13 | spend your time chasing down wacky dependencies, it depends only on the Python 14 | standard library. 15 | 16 | NOTE: Some of the samples use matplotlib and NumPy, so you may need to install 17 | additional packages to run all the samples. 18 | 19 | NOTE: BTC-e is not affiliated with this project; this is a completely 20 | independent implementation based on the API description. Use at your own risk. 21 | 22 | If you find the library useful and would like to donate (and many thanks to 23 | those that have donated!), please send some coins here: 24 | 25 | LTC LatrKXtfw66LQUURrxBzCE7cxFc9Sv8FWf 26 | BTC 16vnh6gwFYLGneBa8JUk7NaXpEt3Qojqs1 27 | DOGE D5jNqRjwxhDZT4hkG8yoGkseP576smjyNx 28 | 29 | The following functions in the btceapi module access the public API and/or 30 | scrape content from the main page, and do not require any user account 31 | information: 32 | 33 | getDepth(pair) - Retrieve the depth for the given pair. Returns a tuple 34 | (asks, bids); each of these is a list of (price, volume) tuples. See the 35 | example usage in samples/show_depth.py. 36 | 37 | getTicker(pair) - Retrieve the ticker information (high, low, avg, etc.) 38 | for the given pair. Returns a Ticker instance, which has members high, low, 39 | avg, vol, vol_cur, last, buy, sell, updated, and server_time. 40 | 41 | getTradeFee(pair) - Retrieve the fee (in percent) associated with trades 42 | for a given pair. 43 | 44 | getTradeHistory(pair) - Retrieve the trade history for the given pair. 45 | Returns a list of Trade instances. Each Trade instance has members 46 | trade_type (either "bid" or "ask"), price, tid (transaction ID?), amount, 47 | and date (a datetime object). 48 | 49 | scrapeMainPage() - Collect information from the main page and return it in 50 | a ScraperResults object. This object has members 'messages' (a list of 51 | (message ID, user, time, text) tuples representing the chat messages 52 | currently visible on the main page, 'bitInstantReserves' (an integer value 53 | representing the current BitInstant reserves), and 'aurumXchangeReserves' 54 | (an integer value representing the current AurumXchange reserves). 55 | 56 | All of the functions above also take an optional 'connection' argument, which 57 | should be an instance of BTCEConnection. This will speed up multiple function 58 | calls, as a new connection will not have to be created for every call. 59 | 60 | The TradeAPI class in the btceapi module accesses the trading API, and requires 61 | a KeyHandler object. The KeyHandler manages your API key and secret values 62 | (found under "API Keys" on the Profile page), stored in a text file. For 63 | instructions on creating this text file, please see step 9 here: 64 | 65 | https://github.com/alanmcintyre/btce-bot/wiki/Getting-started 66 | 67 | The following methods are available on a TradeAPI instance: 68 | 69 | getInfo - Retrieves basic account information via the server getInfo 70 | method, and returns a TradeAccountInfo object with the following members: 71 | balance_[currency] - Current available balance in the given currency. 72 | open_orders - Number of open orders. 73 | server_time - Server time in a datetime object. 74 | transaction_count - Number of transactions. (?) 75 | info_rights - True if the API key has info rights. 76 | withdraw_rights - True if the API key has withdrawal rights. 77 | trade_rights - True if the API key has trading rights. 78 | 79 | transHistory - Retrieves transaction history via the server TransHistory 80 | method, and returns a list of TransactionHistoryItem objects, which have 81 | the following members: type, amount, currency, desc, status, and timestamp 82 | (a datetime object). 83 | 84 | tradeHistory - Retrieves trading history via the server TradeHistory 85 | method, and returns a list of TradeHistoryItem objects, which have the 86 | following members: pair (such as "btc_usd"), type ("buy" or "sell"), 87 | amount, rate, order_id, is_your_order, timestamp (a datetime object). 88 | 89 | activeOrders - Retrieves a list of orders via the server ActiveOrders 90 | method, and returns a list of OrderItem objects, which have the following 91 | members: pair (such as "btc_usd"), type ("buy" or "sell"), amount, rate, 92 | timestamp_created (a datetime object) and status. 93 | 94 | trade - Place a trade order via the server Trade method, and return a 95 | TradeResult object, which has the following members: 96 | received - Immediate proceeds from the order. 97 | remains - Portion of the order that remains unfilled. 98 | order_id 99 | balance_[currency] - Current available balance in the given currency. 100 | 101 | cancelOrder - Cancel the specified order via the server CancelOrder method, 102 | and return a CancelOrderResult object, which has the following members: 103 | order_id 104 | balance_[currency] - Current available balance in the given currency. 105 | 106 | See the API documentation (https://btc-e.com/api/documentation) for more 107 | details on arguments to these methods. 108 | -------------------------------------------------------------------------------- /btceapi/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017 CodeReclaimers, LLC 2 | 3 | from .common import formatCurrencyDigits, truncateAmountDigits, BTCEConnection, \ 4 | InvalidTradePairException, InvalidTradeTypeException, \ 5 | InvalidTradeAmountException, InvalidTradePriceException, APIResponseError 6 | 7 | from .keyhandler import AbstractKeyHandler, KeyHandler 8 | from .public import APIInfo, getDepth, getTicker, getTradeHistory 9 | from .trade import TradeAPI 10 | 11 | __version__ = "0.9.0" 12 | -------------------------------------------------------------------------------- /btceapi/common.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017 CodeReclaimers, LLC 2 | 3 | import decimal 4 | import json 5 | import os 6 | import re 7 | 8 | try: 9 | import httplib 10 | except ImportError: 11 | import http.client as httplib 12 | 13 | 14 | class InvalidTradePairException(Exception): 15 | """ Raised when an invalid pair is passed. """ 16 | pass 17 | 18 | 19 | class InvalidTradeTypeException(Exception): 20 | """ Raised when invalid trade type is passed. """ 21 | pass 22 | 23 | 24 | class InvalidTradeAmountException(Exception): 25 | """ Raised if trade amount is too much or too little. """ 26 | pass 27 | 28 | 29 | class InvalidTradePriceException(Exception): 30 | """ Raised if trade price is too much or too little. """ 31 | pass 32 | 33 | 34 | class APIResponseError(Exception): 35 | """ Raised if the API replies with an HTTP code 36 | not in the 2xx range. """ 37 | pass 38 | 39 | 40 | decimal.getcontext().rounding = decimal.ROUND_DOWN 41 | quanta = [decimal.Decimal("1e-%d" % i) for i in range(16)] 42 | 43 | btce_domain = "btc-e.com" 44 | 45 | 46 | def parseJSONResponse(response): 47 | def parse_decimal(var): 48 | return decimal.Decimal(var) 49 | 50 | try: 51 | if type(response) is not str: 52 | response = response.decode('utf-8') 53 | r = json.loads(response, parse_float=parse_decimal, 54 | parse_int=parse_decimal) 55 | except Exception as e: 56 | msg = "Error while attempting to parse JSON response:" \ 57 | " %s\nResponse:\n%r" % (e, response) 58 | raise Exception(msg) 59 | 60 | return r 61 | 62 | 63 | HEADER_COOKIE_RE = re.compile(r'__cfduid=([a-f0-9]{46})') 64 | BODY_COOKIE_RE = re.compile(r'document\.cookie="a=([a-f0-9]{32});path=/;";') 65 | 66 | 67 | class BTCEConnection(object): 68 | def __init__(self, timeout=30): 69 | self.conn = None 70 | self.cookie = None 71 | self._timeout = timeout 72 | self.setup_connection() 73 | 74 | def __del__(self): 75 | self.close() 76 | 77 | def __enter__(self): 78 | return self 79 | 80 | def __exit__(self, *_args): 81 | self.close() 82 | 83 | def setup_connection(self): 84 | if "HTTPS_PROXY" in os.environ: 85 | match = re.search(r'http://([\w.]+):(\d+)', os.environ['HTTPS_PROXY']) 86 | if match: 87 | self.conn = httplib.HTTPSConnection(match.group(1), 88 | port=match.group(2), 89 | timeout=self._timeout) 90 | self.conn.set_tunnel(btce_domain) 91 | else: 92 | self.conn = httplib.HTTPSConnection(btce_domain, timeout=self._timeout) 93 | self.cookie = None 94 | 95 | def close(self): 96 | if self.conn is not None: 97 | self.conn.close() 98 | self.conn = None 99 | 100 | def getCookie(self): 101 | if self.conn is None: 102 | raise Exception("Attempted to use a closed connection.") 103 | 104 | self.cookie = "" 105 | 106 | try: 107 | self.conn.request("GET", '/') 108 | response = self.conn.getresponse() 109 | except Exception: 110 | # reset connection so it doesn't stay in a weird state if we catch 111 | # the error in some other place 112 | self.conn.close() 113 | self.setup_connection() 114 | raise 115 | 116 | set_cookie_header = response.getheader("Set-Cookie") 117 | match = HEADER_COOKIE_RE.search(set_cookie_header) 118 | if match: 119 | self.cookie = "__cfduid=" + match.group(1) 120 | 121 | cookie_body = response.read() 122 | if type(cookie_body) is not str: 123 | cookie_body = cookie_body.decode('utf-8') 124 | 125 | match = BODY_COOKIE_RE.search(cookie_body) 126 | if match: 127 | if self.cookie != "": 128 | self.cookie += '; ' 129 | self.cookie += "a=" + match.group(1) 130 | 131 | def makeRequest(self, url, extra_headers=None, params="", with_cookie=False): 132 | if self.conn is None: 133 | raise Exception("Attempted to use a closed connection.") 134 | 135 | headers = {"Content-type": "application/x-www-form-urlencoded"} 136 | if extra_headers is not None: 137 | headers.update(extra_headers) 138 | 139 | if with_cookie: 140 | if self.cookie is None: 141 | self.getCookie() 142 | 143 | headers.update({"Cookie": self.cookie}) 144 | 145 | try: 146 | self.conn.request("POST", url, params, headers) 147 | response = self.conn.getresponse() 148 | except Exception: 149 | # reset connection so it doesn't stay in a weird state if we catch 150 | # the error in some other place 151 | self.conn.close() 152 | self.setup_connection() 153 | raise 154 | 155 | if response.status != 200: 156 | raise httplib.HTTPException 157 | else: 158 | return response.read() 159 | 160 | def makeJSONRequest(self, url, extra_headers=None, params=""): 161 | response = self.makeRequest(url, extra_headers, params) 162 | return parseJSONResponse(response) 163 | 164 | 165 | def truncateAmountDigits(value, digits): 166 | if type(value) is float: 167 | value = str(value) 168 | 169 | if type(value) is str: 170 | value = decimal.Decimal(value) 171 | 172 | quantum = quanta[int(digits)] 173 | return value.quantize(quantum) 174 | 175 | 176 | def formatCurrencyDigits(value, digits): 177 | s = str(truncateAmountDigits(value, digits)) 178 | s = s.rstrip("0") 179 | if s[-1] == ".": 180 | s = "%s0" % s 181 | 182 | return s 183 | -------------------------------------------------------------------------------- /btceapi/keyhandler.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017 CodeReclaimers, LLC 2 | 3 | 4 | class InvalidNonceException(Exception): 5 | """Raised when an invalid nonce is set on a key.""" 6 | pass 7 | 8 | 9 | class KeyData(object): 10 | def __init__(self, secret, nonce): 11 | self.secret = secret 12 | self.nonce = nonce 13 | 14 | # BTC-e's API caps nonces' values 15 | MAX_NONCE_VALUE = 4294967294 16 | 17 | def setNonce(self, newNonce): 18 | if newNonce <= 0: 19 | raise InvalidNonceException('Nonces must be positive') 20 | if newNonce <= self.nonce: 21 | raise InvalidNonceException('Nonces must be strictly increasing') 22 | if newNonce > self.MAX_NONCE_VALUE: 23 | raise InvalidNonceException('Nonces cannot be greater than %d' % 24 | self.MAX_NONCE_VALUE) 25 | 26 | self.nonce = newNonce 27 | 28 | return self.nonce 29 | 30 | def incrementNonce(self): 31 | if self.nonce >= self.MAX_NONCE_VALUE: 32 | raise InvalidNonceException('Cannot increment nonce, already at' 33 | ' maximum value') 34 | 35 | self.nonce += 1 36 | 37 | return self.nonce 38 | 39 | 40 | class AbstractKeyHandler(object): 41 | """AbstractKeyHandler handles the tedious task of managing nonces 42 | associated with BTC-e API key/secret pairs. 43 | The getNextNonce method is threadsafe, all others need not be.""" 44 | def __init__(self): 45 | self._keys = {} 46 | self._loadKeys() 47 | 48 | @property 49 | def keys(self): 50 | if self._keys is None: 51 | raise Exception("Attempted to use a closed key handler.") 52 | 53 | return self._keys.keys() 54 | 55 | def _loadKeys(self): 56 | """ 57 | Load the keys with their secrets and nonces from the datastore. 58 | """ 59 | raise NotImplementedError 60 | 61 | def _updateDatastore(self): 62 | """ 63 | Should update the datastore with the latest data (newest nonces, and any 64 | keys that might have been added) 65 | """ 66 | raise NotImplementedError 67 | 68 | def __del__(self): 69 | self.close() 70 | 71 | def close(self): 72 | self._updateDatastore() 73 | 74 | # By convention, if _keys is None the object is considered 75 | # closed and should not attempt to touch the underlying storage. 76 | self._keys = None 77 | 78 | def __enter__(self): 79 | return self 80 | 81 | def __exit__(self, *_args): 82 | self.close() 83 | 84 | def addKey(self, key, secret, next_nonce): 85 | if self._keys is None: 86 | raise Exception("Attempted to use a closed key handler.") 87 | 88 | self._keys[key] = KeyData(secret, next_nonce) 89 | return self 90 | 91 | def getNextNonce(self, key): 92 | return self.getKey(key).incrementNonce() 93 | 94 | def getSecret(self, key): 95 | return self.getKey(key).secret 96 | 97 | def setNextNonce(self, key, nextNonce): 98 | return self.getKey(key).setNonce(nextNonce) 99 | 100 | def getKey(self, key): 101 | if self._keys is None: 102 | raise Exception("Attempted to use a closed key handler.") 103 | 104 | data = self._keys.get(key) 105 | if data is None: 106 | raise KeyError("Key not found: %r" % key) 107 | 108 | return data 109 | 110 | 111 | class KeyHandler(AbstractKeyHandler): 112 | """An implementation of AbstractKeyHandler using local files to store the 113 | data.""" 114 | def __init__(self, filename=None, resaveOnDeletion=True): 115 | """The given file is assumed to be a text file with three lines 116 | (key, secret, nonce) per entry.""" 117 | self.filename = filename 118 | self.resaveOnDeletion = resaveOnDeletion 119 | super(KeyHandler, self).__init__() 120 | 121 | def _loadKeys(self): 122 | if self.filename is not None: 123 | self._load() 124 | 125 | def _updateDatastore(self): 126 | if self.resaveOnDeletion and self.filename is not None: 127 | self._save() 128 | 129 | def _save(self): 130 | # Do nothing if the object has been closed. 131 | if self._keys is None: 132 | return 133 | 134 | with open(self.filename, 'wt') as f: 135 | for k, data in self._keys.items(): 136 | f.write("%s\n%s\n%d\n" % (k, data.secret, data.nonce)) 137 | 138 | def _load(self): 139 | with open(self.filename, 'rt') as input_file: 140 | while True: 141 | key = input_file.readline().strip() 142 | if not key: 143 | break 144 | secret = input_file.readline().strip() 145 | nonce = int(input_file.readline().strip()) 146 | self.addKey(key, secret, nonce) 147 | -------------------------------------------------------------------------------- /btceapi/public.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017 CodeReclaimers, LLC 2 | 3 | # Public API v3 description: https://btc-e.com/api/3/documentation 4 | 5 | from collections import namedtuple 6 | 7 | from . import common, scraping 8 | 9 | PairInfoBase = namedtuple("PairInfoBase", 10 | ["decimal_places", "min_price", "max_price", "min_amount", "hidden", "fee"]) 11 | 12 | 13 | class PairInfo(PairInfoBase): 14 | def format_currency(self, value): 15 | return common.formatCurrencyDigits(value, self.decimal_places) 16 | 17 | def truncate_amount(self, value): 18 | return common.truncateAmountDigits(value, self.decimal_places) 19 | 20 | def validate_order(self, trade_type, rate, amount): 21 | if trade_type not in ("buy", "sell"): 22 | raise common.InvalidTradeTypeException("Unrecognized trade type: %r" % trade_type) 23 | 24 | if rate < self.min_price or rate > self.max_price: 25 | raise common.InvalidTradePriceException( 26 | "Allowed price range is from %f to %f" % (self.min_price, self.max_price)) 27 | 28 | formatted_min_amount = self.format_currency(self.min_amount) 29 | if amount < self.min_amount: 30 | msg = "Trade amount %r too small; should be >= %s" % \ 31 | (amount, formatted_min_amount) 32 | raise common.InvalidTradeAmountException(msg) 33 | 34 | 35 | class APIInfo(object): 36 | def __init__(self, connection): 37 | self.connection = connection 38 | self.currencies = None 39 | self.pair_names = None 40 | self.pairs = None 41 | self.server_time = None 42 | 43 | self._scrape_pair_index = 0 44 | 45 | self.update() 46 | 47 | def update(self): 48 | info = self.connection.makeJSONRequest("/api/3/info") 49 | if type(info) is not dict: 50 | raise TypeError("The response is not a dict.") 51 | 52 | self.server_time = info.get(u"server_time") 53 | 54 | pairs = info.get(u"pairs") 55 | if type(pairs) is not dict: 56 | raise TypeError("The pairs item is not a dict.") 57 | 58 | self.pairs = {} 59 | currencies = set() 60 | for name, data in pairs.items(): 61 | self.pairs[name] = PairInfo(**data) 62 | a, b = name.split(u"_") 63 | currencies.add(a) 64 | currencies.add(b) 65 | 66 | self.currencies = list(currencies) 67 | self.currencies.sort() 68 | 69 | self.pair_names = list(self.pairs.keys()) 70 | self.pair_names.sort() 71 | 72 | def validate_pair(self, pair): 73 | if pair not in self.pair_names: 74 | if "_" in pair: 75 | a, b = pair.split("_", 1) 76 | swapped_pair = "%s_%s" % (b, a) 77 | if swapped_pair in self.pair_names: 78 | msg = "Unrecognized pair: %r (did you mean %s?)" 79 | msg = msg % (pair, swapped_pair) 80 | raise common.InvalidTradePairException(msg) 81 | raise common.InvalidTradePairException("Unrecognized pair: %r" % pair) 82 | 83 | def get_pair_info(self, pair): 84 | self.validate_pair(pair) 85 | return self.pairs[pair] 86 | 87 | def validate_order(self, pair, trade_type, rate, amount): 88 | self.validate_pair(pair) 89 | 90 | pair_info = self.pairs[pair] 91 | pair_info.validate_order(trade_type, rate, amount) 92 | 93 | def format_currency(self, pair, amount): 94 | self.validate_pair(pair) 95 | 96 | pair_info = self.pairs[pair] 97 | return pair_info.format_currency(amount) 98 | 99 | def scrapeMainPage(self): 100 | parser = scraping.BTCEScraper() 101 | 102 | # Rotate through the currency pairs between chat requests so that the 103 | # chat pane contents will update more often than every few minutes. 104 | self._scrape_pair_index = (self._scrape_pair_index + 1) % len(self.pair_names) 105 | current_pair = self.pair_names[self._scrape_pair_index] 106 | 107 | response = self.connection.makeRequest('/exchange/%s' % current_pair, with_cookie=True) 108 | 109 | parser.feed(parser.unescape(response.decode('utf-8'))) 110 | parser.close() 111 | 112 | r = scraping.ScraperResults() 113 | r.messages = parser.messages 114 | r.devOnline = parser.devOnline 115 | r.supportOnline = parser.supportOnline 116 | r.adminOnline = parser.adminOnline 117 | 118 | return r 119 | 120 | Ticker = namedtuple("Ticker", 121 | ["high", "low", "avg", "vol", "vol_cur", "last", "buy", "sell", "updated"]) 122 | 123 | 124 | def getTicker(pair, connection=None, info=None): 125 | """Retrieve the ticker for the given pair. Returns a Ticker instance.""" 126 | 127 | if info is not None: 128 | info.validate_pair(pair) 129 | 130 | if connection is None: 131 | connection = common.BTCEConnection() 132 | 133 | response = connection.makeJSONRequest("/api/3/ticker/%s" % pair) 134 | 135 | if type(response) is not dict: 136 | raise TypeError("The response is a %r, not a dict." % type(response)) 137 | elif u'error' in response: 138 | print("There is a error \"%s\" while obtaining ticker %s" % (response['error'], pair)) 139 | ticker = None 140 | else: 141 | ticker = Ticker(**response[pair]) 142 | 143 | return ticker 144 | 145 | 146 | def getDepth(pair, connection=None, info=None): 147 | """Retrieve the depth for the given pair. Returns a tuple (asks, bids); 148 | each of these is a list of (price, volume) tuples.""" 149 | 150 | if info is not None: 151 | info.validate_pair(pair) 152 | 153 | if connection is None: 154 | connection = common.BTCEConnection() 155 | 156 | response = connection.makeJSONRequest("/api/3/depth/%s" % pair) 157 | if type(response) is not dict: 158 | raise TypeError("The response is not a dict.") 159 | 160 | depth = response.get(pair) 161 | if type(depth) is not dict: 162 | raise TypeError("The pair depth is not a dict.") 163 | 164 | asks = depth.get(u'asks') 165 | if type(asks) is not list: 166 | raise TypeError("The response does not contain an asks list.") 167 | 168 | bids = depth.get(u'bids') 169 | if type(bids) is not list: 170 | raise TypeError("The response does not contain a bids list.") 171 | 172 | return asks, bids 173 | 174 | 175 | Trade = namedtuple("Trade", ['pair', 'type', 'price', 'tid', 'amount', 'timestamp']) 176 | 177 | 178 | def getTradeHistory(pair, connection=None, info=None, count=None): 179 | """Retrieve the trade history for the given pair. Returns a list of 180 | Trade instances. If count is not None, it should be an integer, and 181 | specifies the number of items from the trade history that will be 182 | processed and returned.""" 183 | 184 | if info is not None: 185 | info.validate_pair(pair) 186 | 187 | if connection is None: 188 | connection = common.BTCEConnection() 189 | 190 | response = connection.makeJSONRequest("/api/3/trades/%s" % pair) 191 | if type(response) is not dict: 192 | raise TypeError("The response is not a dict.") 193 | 194 | history = response.get(pair) 195 | if type(history) is not list: 196 | raise TypeError("The response is a %r, not a list." % type(history)) 197 | 198 | result = [] 199 | 200 | # Limit the number of items returned if requested. 201 | if count is not None: 202 | history = history[:count] 203 | 204 | for h in history: 205 | h["pair"] = pair 206 | t = Trade(**h) 207 | result.append(t) 208 | return result 209 | -------------------------------------------------------------------------------- /btceapi/scraping.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017 CodeReclaimers, LLC 2 | 3 | import datetime 4 | import warnings 5 | try: 6 | from HTMLParser import HTMLParser 7 | except ImportError: 8 | from html.parser import HTMLParser 9 | 10 | 11 | class BTCEScraper(HTMLParser): 12 | def __init__(self): 13 | HTMLParser.__init__(self) 14 | self.messageId = None 15 | self.messageTime = None 16 | self.messageUser = None 17 | self.messageText = None 18 | self.messages = [] 19 | 20 | self.inMessageA = False 21 | self.inMessageSpan = False 22 | 23 | self.devOnline = False 24 | self.supportOnline = False 25 | self.adminOnline = False 26 | 27 | def handle_data(self, data): 28 | # Capture contents of and tags, which contain 29 | # the user ID and the message text, respectively. 30 | if self.inMessageA: 31 | self.messageUser = data.strip() 32 | elif self.inMessageSpan: 33 | self.messageText = data.strip() 34 | 35 | def handle_starttag(self, tag, attrs): 36 | if tag == 'p': 37 | # Check whether this

tag has id="msgXXXXXXXX" and 38 | # class="chatmessage *"; if not, it doesn't contain a message. 39 | messageId = None 40 | for k, v in attrs: 41 | if k == 'id': 42 | if v[:3] != 'msg': 43 | return 44 | messageId = v 45 | if k == 'class' and 'chatmessage' not in v: 46 | return 47 | 48 | # This appears to be a message

tag, so set the message ID. 49 | # Other code in this class assumes that if self.messageId is None, 50 | # the tags being processed are not relevant. 51 | if messageId is not None: 52 | self.messageId = messageId 53 | elif tag == 'a': 54 | if self.messageId is not None: 55 | # Check whether this tag has class="chatmessage" and a 56 | # time string in the title attribute; if not, it's not part 57 | # of a message. 58 | messageTime = None 59 | for k, v in attrs: 60 | if k == 'title': 61 | messageTime = v 62 | if k == 'class' and v != 'chatmessage': 63 | return 64 | 65 | if messageTime is None: 66 | return 67 | 68 | # This appears to be a message tag, so remember the message 69 | # time and set the inMessageA flag so the tag's data can be 70 | # captured in the handle_data method. 71 | self.inMessageA = True 72 | self.messageTime = messageTime 73 | else: 74 | for k, v in attrs: 75 | if k != 'href': 76 | continue 77 | 78 | # If the tag for dev/support/admin is present, then 79 | # they are online (otherwise nothing appears on the 80 | # page for them). 81 | if v == 'https://btc-e.com/profile/1': 82 | self.devOnline = True 83 | elif v == 'https://btc-e.com/profile/2': 84 | self.supportOnline = True 85 | elif v == 'https://btc-e.com/profile/3': 86 | self.adminOnline = True 87 | elif tag == 'span': 88 | if self.messageId is not None: 89 | self.inMessageSpan = True 90 | 91 | def handle_endtag(self, tag): 92 | if tag == 'p' and self.messageId is not None: 93 | # exiting from the message

tag 94 | 95 | # check for invalid message contents 96 | if self.messageId is None: 97 | warnings.warn("Missing message ID") 98 | if self.messageUser is None: 99 | warnings.warn("Missing message user") 100 | if self.messageTime is None: 101 | warnings.warn("Missing message time") 102 | 103 | if self.messageText is None: 104 | # messageText will be None if the message consists entirely 105 | # of emoticons. 106 | self.messageText = '' 107 | 108 | # parse message time 109 | t = datetime.datetime.now() 110 | messageTime = t.strptime(self.messageTime, '%d.%m.%y %H:%M:%S') 111 | 112 | self.messages.append((self.messageId, self.messageUser, 113 | messageTime, self.messageText)) 114 | self.messageId = None 115 | self.messageUser = None 116 | self.messageTime = None 117 | self.messageText = None 118 | elif tag == 'a' and self.messageId is not None: 119 | self.inMessageA = False 120 | elif tag == 'span': 121 | self.inMessageSpan = False 122 | 123 | 124 | class ScraperResults(object): 125 | __slots__ = ('messages', 'devOnline', 'supportOnline', 'adminOnline') 126 | 127 | def __init__(self): 128 | self.messages = None 129 | self.devOnline = False 130 | self.supportOnline = False 131 | self.adminOnline = False 132 | 133 | def __getstate__(self): 134 | return dict((k, getattr(self, k)) for k in ScraperResults.__slots__) 135 | 136 | def __setstate__(self, state): 137 | for k, v in state.items(): 138 | setattr(self, k, v) 139 | 140 | 141 | -------------------------------------------------------------------------------- /btceapi/trade.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2017 CodeReclaimers, LLC 2 | 3 | # Trade API description: https://btc-e.com/api/documentation 4 | 5 | from collections import namedtuple 6 | import hashlib 7 | import hmac 8 | import warnings 9 | 10 | try: 11 | from urllib import urlencode 12 | except ImportError: 13 | from urllib.parse import urlencode 14 | 15 | from . import keyhandler 16 | from . import public 17 | 18 | 19 | class InvalidNonceException(Exception): 20 | def __init__(self, method, expectedNonce, actualNonce): 21 | Exception.__init__(self) 22 | self.method = method 23 | self.expectedNonce = expectedNonce 24 | self.actualNonce = actualNonce 25 | 26 | def __str__(self): 27 | return "Expected a nonce greater than %d" % self.expectedNonce 28 | 29 | 30 | class InvalidSortOrderException(Exception): 31 | """ Exception thrown when an invalid sort order is passed """ 32 | pass 33 | 34 | 35 | class TradeAccountInfo(object): 36 | """An instance of this class will be returned by 37 | a successful call to TradeAPI.getInfo.""" 38 | 39 | def __init__(self, info): 40 | self.funds = info.get(u'funds') 41 | self.open_orders = info.get(u'open_orders') 42 | self.server_time = info.get(u'server_time') 43 | self.transaction_count = info.get(u'transaction_count') 44 | rights = info.get(u'rights') 45 | self.info_rights = (rights.get(u'info') == 1) 46 | self.withdraw_rights = (rights.get(u'withdraw') == 1) 47 | self.trade_rights = (rights.get(u'trade') == 1) 48 | 49 | 50 | TransactionHistoryItem = namedtuple("TransactionHistoryItem", 51 | ["transaction_id", "type", "amount", "currency", "desc", "status", "timestamp"]) 52 | 53 | 54 | TradeHistoryItem = namedtuple("TradeHistoryItem", 55 | ["transaction_id", "pair", "type", "amount", "rate", "order_id", "is_your_order", "timestamp"]) 56 | 57 | 58 | OrderItem = namedtuple("OrderItem", 59 | ["order_id", "pair", "type", "amount", "rate", "timestamp_created", "status"]) 60 | 61 | 62 | TradeResult = namedtuple("TradeResult", 63 | ["received", "remains", "order_id", "funds"]) 64 | 65 | 66 | CancelOrderResult = namedtuple("CancelOrderResult", 67 | ["order_id", "funds"]) 68 | 69 | 70 | def setHistoryParams(params, from_number, count_number, from_id, end_id, 71 | order, since, end): 72 | if from_number is not None: 73 | params["from"] = "%d" % from_number 74 | if count_number is not None: 75 | params["count"] = "%d" % count_number 76 | if from_id is not None: 77 | params["from_id"] = "%d" % from_id 78 | if end_id is not None: 79 | params["end_id"] = "%d" % end_id 80 | if order is not None: 81 | if order not in ("ASC", "DESC"): 82 | raise InvalidSortOrderException("Unexpected order parameter: %r" % order) 83 | params["order"] = order 84 | if since is not None: 85 | params["since"] = "%d" % since 86 | if end is not None: 87 | params["end"] = "%d" % end 88 | 89 | 90 | class TradeAPI(object): 91 | def __init__(self, key, handler, connection): 92 | self.key = key 93 | self.handler = handler 94 | self.connection = connection 95 | self.apiInfo = public.APIInfo(self.connection) 96 | self.raiseIfInvalidNonce = True 97 | 98 | if not isinstance(self.handler, keyhandler.AbstractKeyHandler): 99 | raise TypeError("The handler argument must be a" 100 | " keyhandler.AbstractKeyHandler, such as" 101 | " keyhandler.KeyHandler") 102 | 103 | # We depend on the key handler for the secret 104 | self.secret = handler.getSecret(key) 105 | 106 | def _post(self, params, allowNonceRetry=False): 107 | params["nonce"] = self.handler.getNextNonce(self.key) 108 | encoded_params = urlencode(params) 109 | 110 | # Hash the params string to produce the Sign header value 111 | H = hmac.new(self.secret.encode('utf-8'), digestmod=hashlib.sha512) 112 | H.update(encoded_params.encode('utf-8')) 113 | sign = H.hexdigest() 114 | 115 | headers = {"Key": self.key, "Sign": sign} 116 | result = self.connection.makeJSONRequest("/tapi", headers, encoded_params) 117 | 118 | success = result.get(u'success') 119 | if not success: 120 | err_message = result.get(u'error') 121 | method = params.get("method", "[uknown method]") 122 | 123 | if "invalid nonce" in err_message: 124 | # If the nonce is out of sync, make one attempt to update to 125 | # the correct nonce. This sometimes happens if a bot crashes 126 | # and the nonce file doesn't get saved, so it's reasonable to 127 | # attempt a correction. If multiple threads/processes are 128 | # attempting to use the same key, this mechanism will 129 | # eventually fail and the InvalidNonce will be emitted so that 130 | # you'll end up here reading this comment. :) 131 | 132 | # The assumption is that the invalid nonce message looks like 133 | # "invalid nonce parameter; on key:4, you sent:3" 134 | s = err_message.split(",") 135 | expected = int(s[-2].split(":")[1].strip("'")) 136 | actual = int(s[-1].split(":")[1].strip("'")) 137 | if self.raiseIfInvalidNonce and not allowNonceRetry: 138 | raise InvalidNonceException(method, expected, actual) 139 | 140 | warnings.warn("The nonce in the key file is out of date;" 141 | " attempting to correct.") 142 | self.handler.setNextNonce(self.key, expected + 1000) 143 | return self._post(params, True) 144 | elif "no orders" in err_message and method == "ActiveOrders": 145 | # ActiveOrders returns failure if there are no orders; 146 | # intercept this and return an empty dict. 147 | return {} 148 | elif "no trades" in err_message and method == "TradeHistory": 149 | # TradeHistory returns failure if there are no trades; 150 | # intercept this and return an empty dict. 151 | return {} 152 | 153 | raise Exception("%s call failed with error: %s" 154 | % (method, err_message)) 155 | 156 | if u'return' not in result: 157 | raise Exception("Response does not contain a 'return' item.") 158 | 159 | return result.get(u'return') 160 | 161 | def getInfo(self): 162 | params = {"method": "getInfo"} 163 | return TradeAccountInfo(self._post(params)) 164 | 165 | def transHistory(self, from_number=None, count_number=None, 166 | from_id=None, end_id=None, order="DESC", 167 | since=None, end=None): 168 | 169 | params = {"method": "TransHistory"} 170 | 171 | setHistoryParams(params, from_number, count_number, from_id, end_id, 172 | order, since, end) 173 | 174 | orders = self._post(params) 175 | result = [] 176 | for k, v in orders.items(): 177 | result.append(TransactionHistoryItem(int(k), **v)) 178 | 179 | # We have to sort items here because the API returns a dict 180 | if "ASC" == order: 181 | result.sort(key=lambda a: a.transaction_id, reverse=False) 182 | elif "DESC" == order: 183 | result.sort(key=lambda a: a.transaction_id, reverse=True) 184 | 185 | return result 186 | 187 | def tradeHistory(self, from_number=None, count_number=None, 188 | from_id=None, end_id=None, order=None, 189 | since=None, end=None, pair=None): 190 | 191 | params = {"method": "TradeHistory"} 192 | 193 | setHistoryParams(params, from_number, count_number, from_id, end_id, 194 | order, since, end) 195 | 196 | if pair is not None: 197 | self.apiInfo.validate_pair(pair) 198 | params["pair"] = pair 199 | 200 | orders = list(self._post(params).items()) 201 | orders.sort(reverse=order != "ASC") 202 | result = [] 203 | for k, v in orders: 204 | result.append(TradeHistoryItem(int(k), **v)) 205 | 206 | return result 207 | 208 | def activeOrders(self, pair=None): 209 | 210 | params = {"method": "ActiveOrders"} 211 | 212 | if pair is not None: 213 | pair_info = self.apiInfo.validate_pair(pair) 214 | params["pair"] = pair 215 | 216 | orders = self._post(params) 217 | result = [] 218 | for k, v in orders.items(): 219 | result.append(OrderItem(int(k), **v)) 220 | 221 | return result 222 | 223 | def trade(self, pair, trade_type, rate, amount): 224 | pair_info = self.apiInfo.get_pair_info(pair) 225 | pair_info.validate_order(trade_type, rate, amount) 226 | params = {"method": "Trade", 227 | "pair": pair, 228 | "type": trade_type, 229 | "rate": pair_info.format_currency(rate), 230 | "amount": pair_info.format_currency(amount)} 231 | 232 | return TradeResult(**self._post(params)) 233 | 234 | def cancelOrder(self, order_id): 235 | params = {"method": "CancelOrder", 236 | "order_id": order_id} 237 | return CancelOrderResult(**self._post(params)) 238 | -------------------------------------------------------------------------------- /samples/cancel-orders.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import btceapi 4 | 5 | # This sample shows use of a KeyHandler. For each API key in the file 6 | # passed in as the first argument, all pending orders for the specified 7 | # pair and type will be canceled. 8 | 9 | if len(sys.argv) < 4: 10 | print("Usage: cancel-orders.py ") 11 | print(" key file - Path to a file containing key/secret/nonce data") 12 | print(" pair - A currency pair, such as btc_usd") 13 | print(" order type - Type of orders to process, either 'buy' or 'sell'") 14 | sys.exit(1) 15 | 16 | key_file = sys.argv[1] 17 | pair = sys.argv[2] 18 | order_type = sys.argv[3] 19 | 20 | with btceapi.KeyHandler(key_file) as handler: 21 | if not handler.keys: 22 | print("No keys in key file.") 23 | else: 24 | for key in handler.keys: 25 | print("Canceling orders for key {}".format(key)) 26 | 27 | with btceapi.BTCEConnection() as connection: 28 | t = btceapi.TradeAPI(key, handler, connection) 29 | 30 | try: 31 | # Get a list of orders for the given pair, and cancel the ones 32 | # with the correct order type. 33 | orders = t.activeOrders(pair=pair) 34 | for o in orders: 35 | if o.type == order_type: 36 | print(" Canceling {} {} order for {:f} @ {:f}".format( 37 | pair, order_type, o.amount, o.rate)) 38 | t.cancelOrder(o.order_id) 39 | 40 | if not orders: 41 | print(" There are no {} {} orders".format(pair, order_type)) 42 | except Exception as e: 43 | print(" An error occurred: {}".format(e)) 44 | -------------------------------------------------------------------------------- /samples/compute-account-value.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import btceapi 4 | 5 | if len(sys.argv) < 2: 6 | print("Usage: compute-account-value.py ") 7 | print(" key file - Path to a file containing key/secret/nonce data") 8 | sys.exit(1) 9 | 10 | key_file = sys.argv[1] 11 | with btceapi.KeyHandler(key_file) as handler: 12 | if not handler.keys: 13 | print("No keys in key file.") 14 | else: 15 | for key in handler.keys: 16 | print("Computing value for key %s" % key) 17 | with btceapi.BTCEConnection() as connection: 18 | t = btceapi.TradeAPI(key, handler, connection) 19 | 20 | try: 21 | r = t.getInfo() 22 | 23 | exchange_rates = {} 24 | for pair in t.apiInfo.pair_names: 25 | asks, bids = btceapi.getDepth(pair) 26 | exchange_rates[pair] = bids[0][0] 27 | 28 | btc_total = 0 29 | for currency in t.apiInfo.currencies: 30 | balance = r.funds[currency] 31 | if currency == "btc": 32 | print("\t%s balance: %s" % (currency.upper(), balance)) 33 | btc_total += balance 34 | else: 35 | pair = "%s_btc" % currency 36 | if pair in t.apiInfo.pair_names: 37 | btc_equiv = balance * exchange_rates[pair] 38 | else: 39 | pair = "btc_%s" % currency 40 | btc_equiv = balance / exchange_rates[pair] 41 | 42 | bal_str = t.apiInfo.format_currency(pair, balance) 43 | btc_str = t.apiInfo.format_currency("btc_usd", btc_equiv) 44 | print("\t%s balance: %s (~%s BTC)" % (currency.upper(), 45 | bal_str, btc_str)) 46 | btc_total += btc_equiv 47 | 48 | print("\tCurrent value of open orders:") 49 | orders = t.activeOrders() 50 | if orders: 51 | for o in orders: 52 | c1, c2 = o.pair.split("_") 53 | c2_equiv = o.amount * exchange_rates[o.pair] 54 | if c2 == "btc": 55 | btc_equiv = c2_equiv 56 | else: 57 | btc_equiv = c2_equiv / exchange_rates["btc_%s" % c2] 58 | 59 | btc_str = t.apiInfo.format_currency(o.pair, btc_equiv) 60 | print("\t\t%s %s %s @ %s (~%s BTC)" % (o.type, o.amount, 61 | o.pair, o.rate, 62 | btc_str)) 63 | btc_total += btc_equiv 64 | else: 65 | print("\t\tThere are no open orders.") 66 | 67 | btc_str = t.apiInfo.format_currency("btc_usd", btc_total) 68 | print("\n\tTotal estimated value: %s BTC" % btc_str) 69 | for fiat in ("usd", "eur", "rur"): 70 | fiat_pair = "btc_%s" % fiat 71 | fiat_total = btc_total * exchange_rates[fiat_pair] 72 | fiat_str = btceapi.formatCurrencyDigits(fiat_total, 2) 73 | print("\t %s %s" % (fiat_str, 74 | fiat.upper())) 75 | 76 | except Exception as e: 77 | print(" An error occurred: %s" % e) 78 | raise e 79 | 80 | -------------------------------------------------------------------------------- /samples/place-order.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import decimal 3 | import sys 4 | import btceapi 5 | 6 | # This sample shows how to place a single trade order for each key in the provided file. 7 | 8 | if len(sys.argv) < 4: 9 | print("Usage: place-order.py ") 10 | print(" key file - Path to a file containing key/secret/nonce data") 11 | print(" pair - A currency pair, such as btc_usd") 12 | print(" order type - Type of orders to process, either 'buy' or 'sell'") 13 | print(" amount - Amount of currency in order") 14 | print(" price - Order price") 15 | sys.exit(1) 16 | 17 | key_file = sys.argv[1] 18 | pair = sys.argv[2] 19 | order_type = sys.argv[3] 20 | amount = decimal.Decimal(sys.argv[4]) 21 | price = decimal.Decimal(sys.argv[5]) 22 | 23 | with btceapi.KeyHandler(key_file) as handler: 24 | if not handler.keys: 25 | print("No keys in key file.") 26 | else: 27 | for key in handler.keys: 28 | print("Placing order for key {}".format(key)) 29 | 30 | with btceapi.BTCEConnection() as connection: 31 | t = btceapi.TradeAPI(key, handler, connection) 32 | 33 | try: 34 | result = t.trade(pair, order_type, price, amount) 35 | 36 | print("Trade result:") 37 | print(" received: {0}".format(result.received)) 38 | print(" remains: {0}".format(result.remains)) 39 | print(" order_id: {0}".format(result.order_id)) 40 | print(" funds:") 41 | for c, v in result.funds.items(): 42 | print(" {} {}".format(c, v)) 43 | 44 | except Exception as e: 45 | print(" An error occurred: {}".format(e)) 46 | -------------------------------------------------------------------------------- /samples/print-account-info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import btceapi 4 | 5 | if len(sys.argv) < 2: 6 | print("Usage: print-account-info.py ") 7 | print(" key file - Path to a file containing key/secret/nonce data") 8 | sys.exit(1) 9 | 10 | key_file = sys.argv[1] 11 | with btceapi.KeyHandler(key_file) as handler: 12 | if not handler.keys: 13 | print("No keys in key file.") 14 | else: 15 | for key in handler.keys: 16 | print("Printing info for key {}".format(key)) 17 | 18 | with btceapi.BTCEConnection() as connection: 19 | t = btceapi.TradeAPI(key, handler, connection) 20 | 21 | try: 22 | r = t.getInfo() 23 | currencies = list(r.funds.keys()) 24 | currencies.sort() 25 | for currency in currencies: 26 | balance = r.funds[currency] 27 | print("\t{} balance: {}".format(currency.upper(), balance)) 28 | print("\tInformation rights: {}".format(r.info_rights)) 29 | print("\tTrading rights: {}".format(r.trade_rights)) 30 | print("\tWithrawal rights: {}".format(r.withdraw_rights)) 31 | print("\tServer time: {}".format(r.server_time)) 32 | print("\tItems in transaction history: {}".format(r.transaction_count)) 33 | print("\tNumber of open orders: {}".format(r.open_orders)) 34 | print("\topen orders:") 35 | orders = t.activeOrders() 36 | if orders: 37 | for o in orders: 38 | print("\t\torder id: {}".format(o.order_id)) 39 | print("\t\t type: {}".format(o.type)) 40 | print("\t\t pair: {}".format(o.pair)) 41 | print("\t\t rate: {}".format(o.rate)) 42 | print("\t\t amount: {}".format(o.amount)) 43 | print("\t\t created: {}".format(o.timestamp_created)) 44 | print("\t\t status: {}".format(o.status)) 45 | print() 46 | else: 47 | print("\t\tno orders") 48 | 49 | print("\tTrade history:") 50 | trade_history = t.tradeHistory() 51 | if trade_history: 52 | for th in trade_history: 53 | print("\t\ttransaction_id: {}".format(th.transaction_id)) 54 | print("\t\t pair: {}".format(th.pair)) 55 | print("\t\t type: {}".format(th.type)) 56 | print("\t\t amount: {}".format(th.amount)) 57 | print("\t\t rate: {}".format(th.rate)) 58 | print("\t\t order_id: {}".format(th.order_id)) 59 | print("\t\t is_your_order: {}".format(th.is_your_order)) 60 | print("\t\t timestamp: {}".format(th.timestamp)) 61 | print() 62 | else: 63 | print("\t\tno items in trade history") 64 | 65 | except Exception as e: 66 | print(" An error occurred: {}".format(e)) 67 | raise e 68 | -------------------------------------------------------------------------------- /samples/print-trans-history.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | import btceapi 5 | 6 | if len(sys.argv) < 2: 7 | print("Usage: print-trans-history.py ") 8 | print(" key file - Path to a file containing key/secret/nonce data") 9 | sys.exit(1) 10 | 11 | key_file = sys.argv[1] 12 | # NOTE: In future versions, resaveOnDeletion will default to True. 13 | with btceapi.KeyHandler(key_file) as handler: 14 | if not handler.keys: 15 | print("No keys in key file.") 16 | else: 17 | for key in handler.keys: 18 | print("Printing info for key %s" % key) 19 | 20 | with btceapi.BTCEConnection() as connection: 21 | t = btceapi.TradeAPI(key, handler, connection) 22 | 23 | try: 24 | th = t.transHistory() 25 | for h in th: 26 | print("\t\t id: %r" % h.transaction_id) 27 | print("\t\t type: %r" % h.type) 28 | print("\t\t amount: %r" % h.amount) 29 | print("\t\t currency: %r" % h.currency) 30 | print("\t\t desc: %s" % h.desc) 31 | print("\t\t status: %r" % h.status) 32 | print("\t\t timestamp: %r" % h.timestamp) 33 | print() 34 | except Exception as e: 35 | print(" An error occurred: %s" % e) 36 | -------------------------------------------------------------------------------- /samples/show-api-info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import btceapi 3 | 4 | with btceapi.BTCEConnection() as connection: 5 | info = btceapi.APIInfo(connection) 6 | 7 | print("Server time: %s" % info.server_time) 8 | 9 | print("Active currencies:") 10 | for curr in info.currencies: 11 | print(" %s" % curr) 12 | 13 | print("Active trading pairs:") 14 | for name in info.pair_names: 15 | data = info.pairs[name] 16 | print(" %s" % name) 17 | print(" decimal places: %s" % data.decimal_places) 18 | print(" min price: %s" % data.min_price) 19 | print(" max price: %s" % data.max_price) 20 | print(" min amount: %s" % data.min_amount) 21 | print(" hidden: %s" % data.hidden) 22 | print(" fee: %s" % data.fee) 23 | -------------------------------------------------------------------------------- /samples/show-chat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import btceapi 3 | 4 | with btceapi.BTCEConnection() as connection: 5 | info = btceapi.APIInfo(connection) 6 | 7 | mainPage = info.scrapeMainPage() 8 | for message in mainPage.messages: 9 | msgId, user, time, text = message 10 | print("%s %s: %s" % (time, user, text)) 11 | 12 | print() 13 | 14 | print("dev online: %s" % ('yes' if mainPage.devOnline else 'no')) 15 | print("support online: %s" % ('yes' if mainPage.supportOnline else 'no')) 16 | print("admin online: %s" % ('yes' if mainPage.adminOnline else 'no')) 17 | -------------------------------------------------------------------------------- /samples/show-depth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import pylab 4 | import numpy as np 5 | 6 | import btceapi 7 | 8 | # If an argument is provided to this script, it will be interpreted 9 | # as a currency pair for which depth should be displayed. Otherwise 10 | # the BTC/USD depth will be displayed. 11 | 12 | if len(sys.argv) >= 2: 13 | pair = sys.argv[1] 14 | print("Showing depth for %s" % pair) 15 | else: 16 | print("No currency pair provided, defaulting to btc_usd") 17 | pair = "btc_usd" 18 | 19 | asks, bids = btceapi.getDepth(pair) 20 | 21 | print(len(asks), len(bids)) 22 | 23 | ask_prices, ask_volumes = zip(*asks) 24 | bid_prices, bid_volumes = zip(*bids) 25 | 26 | pylab.plot(ask_prices, np.cumsum(ask_volumes), 'r-') 27 | pylab.plot(bid_prices, np.cumsum(bid_volumes), 'g-') 28 | pylab.grid() 29 | pylab.title("%s depth" % pair) 30 | pylab.show() 31 | -------------------------------------------------------------------------------- /samples/show-history.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import pylab 4 | import btceapi 5 | 6 | # If an argument is provided to this script, it will be interpreted 7 | # as a currency pair for which history should be displayed. Otherwise 8 | # the BTC/USD history will be displayed. 9 | 10 | if len(sys.argv) >= 2: 11 | pair = sys.argv[1] 12 | print("Showing history for %s" % pair) 13 | else: 14 | print("No currency pair provided, defaulting to btc_usd") 15 | pair = "btc_usd" 16 | 17 | history = btceapi.getTradeHistory(pair) 18 | 19 | print(len(history)) 20 | 21 | pylab.plot([t.timestamp for t in history if t.type == u'ask'], 22 | [t.price for t in history if t.type == u'ask'], 'ro') 23 | 24 | pylab.plot([t.timestamp for t in history if t.type == u'bid'], 25 | [t.price for t in history if t.type == u'bid'], 'go') 26 | 27 | pylab.grid() 28 | pylab.show() 29 | -------------------------------------------------------------------------------- /samples/show-tickers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import btceapi 3 | 4 | attrs = ('high', 'low', 'avg', 'vol', 'vol_cur', 'last', 5 | 'buy', 'sell', 'updated') 6 | 7 | print("Tickers:") 8 | connection = btceapi.BTCEConnection() 9 | info = btceapi.APIInfo(connection) 10 | for pair in info.pair_names: 11 | ticker = btceapi.getTicker(pair, connection) 12 | print(pair) 13 | for a in attrs: 14 | print("\t%s %s" % (a, getattr(ticker, a))) 15 | -------------------------------------------------------------------------------- /samples/watch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | import matplotlib 5 | import wx 6 | 7 | matplotlib.use("WXAgg") 8 | matplotlib.rcParams['toolbar'] = 'None' 9 | 10 | import matplotlib.pyplot as plt 11 | import pylab 12 | 13 | import btceapi 14 | 15 | 16 | class Chart(object): 17 | def __init__(self, symbol): 18 | self.symbol = symbol 19 | self.base = symbol.split("_")[0].upper() 20 | self.alt = symbol.split("_")[1].upper() 21 | 22 | self.ticks = btceapi.getTradeHistory(self.symbol) 23 | self.last_tid = max([t.tid for t in self.ticks]) 24 | 25 | self.fig = plt.figure() 26 | self.axes = self.fig.add_subplot(111) 27 | self.bid_line, = self.axes.plot(*zip(*self.bid), 28 | linestyle='None', marker='o', color='red') 29 | self.ask_line, = self.axes.plot(*zip(*self.ask), 30 | linestyle='None', marker='o', color='green') 31 | self.axes.grid() 32 | 33 | self.fig.canvas.draw() 34 | 35 | self.timer_id = wx.NewId() 36 | self.actor = self.fig.canvas.manager.frame 37 | self.timer = wx.Timer(self.actor, id=self.timer_id) 38 | self.timer.Start(10000) # update every 10 seconds 39 | wx.EVT_TIMER(self.actor, self.timer_id, self.update) 40 | 41 | pylab.show() 42 | 43 | @property 44 | def bid(self): 45 | return [(t.timestamp, t.price) for t in self.ticks if t.type == u'bid'] 46 | 47 | @property 48 | def ask(self): 49 | return [(t.timestamp, t.price) for t in self.ticks if t.type == u'ask'] 50 | 51 | def update(self, event): 52 | ticks = btceapi.getTradeHistory(self.symbol) 53 | self.ticks += [t for t in ticks if t.tid > self.last_tid] 54 | 55 | for t in ticks: 56 | if t.tid > self.last_tid: 57 | print("%s: %s %f at %s %f" % 58 | (t.type, self.base, t.amount, self.alt, t.price)) 59 | 60 | self.last_tid = max([t.tid for t in ticks]) 61 | 62 | x, y = zip(*self.bid) 63 | self.bid_line.set_xdata(x) 64 | self.bid_line.set_ydata(y) 65 | 66 | x, y = zip(*self.ask) 67 | self.ask_line.set_xdata(x) 68 | self.ask_line.set_ydata(y) 69 | 70 | pylab.gca().relim() 71 | pylab.gca().autoscale_view() 72 | 73 | self.fig.canvas.draw() 74 | 75 | 76 | if __name__ == "__main__": 77 | symbol = "btc_usd" 78 | try: 79 | symbol = sys.argv[1] 80 | except IndexError: 81 | pass 82 | 83 | chart = Chart(symbol) 84 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | 4 | setup(name='btce-api', 5 | version='0.9', 6 | author='CodeReclaimers, LLC', 7 | author_email='alan@codereclaimers.com', 8 | url='https://github.com/CodeReclaimers/btce-api', 9 | license="MIT", 10 | description='A library for the public and private APIs of the digital currency trading site BTC-e.com.', 11 | packages=['btceapi'], 12 | classifiers=[ 13 | 'Development Status :: 2 - Pre-Alpha', 14 | 'Intended Audience :: Developers', 15 | 'Operating System :: OS Independent', 16 | 'Programming Language :: Python :: 2.6', 17 | 'Programming Language :: Python :: 2.7', 18 | 'Programming Language :: Python :: 3.3', 19 | 'Programming Language :: Python :: 3.4', 20 | 'Programming Language :: Python :: 3.5', 21 | 'Programming Language :: Python :: 3.6', 22 | 'Programming Language :: Python :: Implementation :: PyPy', 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /test/test_common.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | import unittest 3 | 4 | import btceapi 5 | from btceapi.common import parseJSONResponse 6 | 7 | # TODO: Add test for using BTCEConnection with a proxy. 8 | 9 | 10 | class TestCommon(unittest.TestCase): 11 | def setUp(self): 12 | self.connection = btceapi.BTCEConnection() 13 | 14 | def tearDown(self): 15 | self.connection.close() 16 | self.connection = None 17 | 18 | def test_formatCurrency(self): 19 | self.assertEqual(btceapi.formatCurrencyDigits(1.123456789, 1), "1.1") 20 | self.assertEqual(btceapi.formatCurrencyDigits(1.123456789, 2), "1.12") 21 | self.assertEqual(btceapi.formatCurrencyDigits(1.123456789, 3), "1.123") 22 | self.assertEqual(btceapi.formatCurrencyDigits(1.123456789, 4), "1.1234") 23 | self.assertEqual(btceapi.formatCurrencyDigits(1.123456789, 5), "1.12345") 24 | self.assertEqual(btceapi.formatCurrencyDigits(1.123456789, 6), "1.123456") 25 | self.assertEqual(btceapi.formatCurrencyDigits(1.123456789, 7), "1.1234567") 26 | 27 | for i in range(2, 8): 28 | print(i) 29 | self.assertEqual(btceapi.formatCurrencyDigits(1.12, i), "1.12") 30 | self.assertEqual(btceapi.formatCurrencyDigits(44.0, i), "44.0") 31 | 32 | def test_formatCurrencyByPair(self): 33 | info = btceapi.APIInfo(self.connection) 34 | for i in info.pairs.values(): 35 | d = i.decimal_places 36 | self.assertEqual(i.format_currency(1.12), 37 | btceapi.formatCurrencyDigits(1.12, d)) 38 | self.assertEqual(i.format_currency(44.0), 39 | btceapi.formatCurrencyDigits(44.0, d)) 40 | self.assertEqual(i.truncate_amount(1.12), 41 | btceapi.truncateAmountDigits(1.12, d)) 42 | self.assertEqual(i.truncate_amount(44.0), 43 | btceapi.truncateAmountDigits(44.0, d)) 44 | 45 | def test_truncateAmount(self): 46 | info = btceapi.APIInfo(self.connection) 47 | for i in info.pairs.values(): 48 | d = i.decimal_places 49 | self.assertEqual(i.truncate_amount(1.12), 50 | btceapi.truncateAmountDigits(1.12, d)) 51 | self.assertEqual(i.truncate_amount(44.0), 52 | btceapi.truncateAmountDigits(44.0, d)) 53 | 54 | def test_validatePair(self): 55 | info = btceapi.APIInfo(self.connection) 56 | for pair in info.pair_names: 57 | info.validate_pair(pair) 58 | self.assertRaises(btceapi.InvalidTradePairException, 59 | info.validate_pair, "not_a_real_pair") 60 | 61 | def test_validate_pair_suggest(self): 62 | info = btceapi.APIInfo(self.connection) 63 | self.assertRaises(btceapi.InvalidTradePairException, 64 | info.validate_pair, "usd_btc") 65 | 66 | def test_validateOrder(self): 67 | info = btceapi.APIInfo(self.connection) 68 | for pair_name, pair_data in info.pairs.items(): 69 | for t in ("buy", "sell"): 70 | info.validate_order(pair_name, t, pair_data.min_price, pair_data.min_amount) 71 | info.validate_order(pair_name, t, pair_data.max_price, pair_data.min_amount) 72 | 73 | self.assertRaises(btceapi.InvalidTradeAmountException, 74 | info.validate_order, pair_name, t, pair_data.min_price, 75 | pair_data.min_amount - decimal.Decimal("0.0001")) 76 | self.assertRaises(btceapi.InvalidTradePriceException, 77 | info.validate_order, pair_name, t, 78 | pair_data.min_price - decimal.Decimal("0.0001"), 79 | pair_data.min_amount) 80 | self.assertRaises(btceapi.InvalidTradePriceException, 81 | info.validate_order, pair_name, t, 82 | pair_data.max_price + decimal.Decimal("0.0001"), 83 | pair_data.min_amount) 84 | 85 | self.assertRaises(btceapi.InvalidTradePairException, 86 | info.validate_order, "foo_bar", "buy", 87 | decimal.Decimal("1.0"), decimal.Decimal("1.0")) 88 | self.assertRaises(btceapi.InvalidTradeTypeException, 89 | info.validate_order, "btc_usd", "foo", 90 | decimal.Decimal("1.0"), decimal.Decimal("1.0")) 91 | 92 | def test_parseJSONResponse(self): 93 | json1 = """ 94 | {"asks":[[3.29551,0.5],[3.29584,5]], 95 | "bids":[[3.29518,15.51461],[3,27.5]]} 96 | """ 97 | parsed = parseJSONResponse(json1) 98 | asks = parsed.get("asks") 99 | self.assertEqual(asks[0], [decimal.Decimal("3.29551"), 100 | decimal.Decimal("0.5")]) 101 | self.assertEqual(asks[1], [decimal.Decimal("3.29584"), 102 | decimal.Decimal("5")]) 103 | bids = parsed.get("bids") 104 | self.assertEqual(bids[0], [decimal.Decimal("3.29518"), 105 | decimal.Decimal("15.51461")]) 106 | self.assertEqual(bids[1], [decimal.Decimal("3"), 107 | decimal.Decimal("27.5")]) 108 | 109 | def test_parseJSONResponse_fail(self): 110 | json1 = """ \most /definitely *not ^json""" 111 | self.assertRaises(Exception, parseJSONResponse, json1) 112 | 113 | def test_pair_identity(self): 114 | info = btceapi.APIInfo(self.connection) 115 | self.assertEqual(len(info.pair_names), len(set(info.pair_names))) 116 | self.assertEqual(set(info.pairs.keys()), set(info.pair_names)) 117 | 118 | def test_currency_sets(self): 119 | currencies_from_pairs = set() 120 | info = btceapi.APIInfo(self.connection) 121 | for pair in info.pair_names: 122 | c1, c2 = pair.split("_") 123 | currencies_from_pairs.add(c1) 124 | currencies_from_pairs.add(c2) 125 | 126 | self.assertEqual(len(info.currencies), len(set(info.currencies))) 127 | self.assertEqual(currencies_from_pairs, set(info.currencies)) 128 | 129 | 130 | if __name__ == '__main__': 131 | unittest.main() 132 | -------------------------------------------------------------------------------- /test/test_keyhandler.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import tempfile 3 | from btceapi.keyhandler import KeyData, AbstractKeyHandler, KeyHandler, InvalidNonceException 4 | 5 | 6 | class TestKeyData(unittest.TestCase): 7 | def test_setNonce(self): 8 | data = KeyData('secret', 1) 9 | 10 | # happy path 11 | newNonce = 28 12 | self.assertEqual(data.setNonce(newNonce), newNonce) 13 | 14 | # negative nonce 15 | self.assertRaises(InvalidNonceException, data.setNonce, 0) 16 | 17 | # non-increasing nonce 18 | self.assertRaises(InvalidNonceException, data.setNonce, newNonce) 19 | 20 | # over the max value 21 | self.assertRaises(InvalidNonceException, data.setNonce, 4294967295) 22 | 23 | def test_incrementNonce(self): 24 | # happy path 25 | self.assertEqual(KeyData('secret', 1).incrementNonce(), 2) 26 | 27 | # over the max value 28 | data = KeyData('secret', 4294967294) 29 | self.assertRaises(InvalidNonceException, data.incrementNonce) 30 | 31 | 32 | # we need a dummy key handler class to test on 33 | class DummyKeyHandler(AbstractKeyHandler): 34 | def __init__(self): 35 | self.keysLoaded = False 36 | self.datastoreUpdated = False 37 | super(DummyKeyHandler, self).__init__() 38 | 39 | def _loadKeys(self): 40 | self.keysLoaded = True 41 | 42 | def _updateDatastore(self): 43 | self.datastoreUpdated = True 44 | 45 | 46 | class TestAbstractKeyHandler(unittest.TestCase): 47 | def test_init(self): 48 | # test it loads the keys from the datastore 49 | handler = DummyKeyHandler() 50 | assert handler.keysLoaded 51 | 52 | def test_keys(self): 53 | # incidentally tests addKey, too 54 | self.assertEqual(set(self._handler_with_keys().keys), set(('k2', 'k1'))) 55 | 56 | def test___del__(self): 57 | handler = DummyKeyHandler() 58 | handler.__del__() 59 | assert handler.datastoreUpdated 60 | 61 | def test___exit__(self): 62 | handler = DummyKeyHandler() 63 | handler.__exit__('type', 'value', 'traceback') 64 | assert handler.datastoreUpdated 65 | 66 | def test_close(self): 67 | handler = DummyKeyHandler() 68 | handler.close() 69 | assert handler.datastoreUpdated 70 | 71 | def test___enter__(self): 72 | handler = DummyKeyHandler() 73 | self.assertEqual(handler.__enter__(), handler) 74 | 75 | def test_getKey(self): 76 | handler = self._handler_with_keys() 77 | 78 | # happy path 79 | self.assertEqual(handler.getKey('k1').nonce, 3) 80 | 81 | # unknown key 82 | self.assertRaises(KeyError, handler.getNextNonce, 'i_dont_exist') 83 | 84 | def test_getNextNonce(self): 85 | self.assertEqual(self._handler_with_keys().getNextNonce('k2'), 29) 86 | 87 | def test_setNextNonce(self): 88 | self.assertEqual(self._handler_with_keys().setNextNonce('k1', 82), 82) 89 | 90 | def _handler_with_keys(self): 91 | handler = DummyKeyHandler() 92 | return handler.addKey('k1', 'secret1', 3).addKey('k2', 'secret2', 28) 93 | 94 | 95 | class TestKeyHandler(unittest.TestCase): 96 | def test_save(self): 97 | _, filename = tempfile.mkstemp() 98 | 99 | handler = KeyHandler(filename=filename) 100 | handler.addKey('k1', 'secret1', 3).addKey('k2', 'secret2', 28) 101 | 102 | handler.close() 103 | 104 | allowed_content = ('k2\nsecret2\n28\nk1\nsecret1\n3\n', 105 | 'k1\nsecret1\n3\nk2\nsecret2\n28\n') 106 | with open(filename) as saved_file: 107 | self.assertTrue(saved_file.read() in allowed_content) 108 | 109 | def test_parse(self): 110 | _, filename = tempfile.mkstemp() 111 | 112 | with open(filename, 'w') as input_file: 113 | input_file.write('k2\nsecret2\n28\nk1\nsecret1\n3\n') 114 | 115 | handler = KeyHandler(filename=filename) 116 | 117 | self.assertEqual(set(handler.keys), set(['k2', 'k1'])) 118 | 119 | 120 | if __name__ == '__main__': 121 | unittest.main() 122 | -------------------------------------------------------------------------------- /test/test_public.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import btceapi 4 | 5 | 6 | class TestPublic(unittest.TestCase): 7 | def test_getTicker(self): 8 | connection = btceapi.BTCEConnection() 9 | info = btceapi.APIInfo(connection) 10 | for pair in info.pair_names: 11 | btceapi.getTicker(pair, connection, info) 12 | btceapi.getTicker(pair, connection) 13 | btceapi.getTicker(pair, info=info) 14 | btceapi.getTicker(pair) 15 | 16 | def test_getHistory(self): 17 | connection = btceapi.BTCEConnection() 18 | info = btceapi.APIInfo(connection) 19 | for pair in info.pair_names: 20 | btceapi.getTradeHistory(pair, connection, info) 21 | btceapi.getTradeHistory(pair, connection) 22 | btceapi.getTradeHistory(pair, info=info) 23 | btceapi.getTradeHistory(pair) 24 | 25 | def test_getDepth(self): 26 | connection = btceapi.BTCEConnection() 27 | info = btceapi.APIInfo(connection) 28 | for pair in info.pair_names: 29 | btceapi.getDepth(pair, connection, info) 30 | btceapi.getDepth(pair, connection) 31 | btceapi.getDepth(pair, info=info) 32 | btceapi.getDepth(pair) 33 | 34 | 35 | if __name__ == '__main__': 36 | unittest.main() 37 | -------------------------------------------------------------------------------- /test/test_scraping.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import sys 3 | import unittest 4 | 5 | import btceapi 6 | 7 | 8 | class TestScraping(unittest.TestCase): 9 | 10 | def test_scrape_main_page(self): 11 | with btceapi.BTCEConnection() as connection: 12 | info = btceapi.APIInfo(connection) 13 | mainPage = info.scrapeMainPage() 14 | 15 | for message in mainPage.messages: 16 | msgId, user, time, text = message 17 | assert type(time) is datetime 18 | if sys.version_info[0] == 2: 19 | # python2.x 20 | assert type(msgId) in (str, unicode) 21 | assert type(user) in (str, unicode) 22 | assert type(text) in (str, unicode) 23 | else: 24 | # python3.x 25 | self.assertIs(type(msgId), str) 26 | self.assertIs(type(user), str) 27 | self.assertIs(type(text), str) 28 | 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /test/test_trade.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | import btceapi 5 | 6 | # TODO: Add tests that run against a local server. 7 | 8 | TEST_KEY_FILE = os.path.join(os.path.dirname(__file__), "test-keys.txt") 9 | 10 | if os.path.isfile(TEST_KEY_FILE): 11 | class TestTrade(unittest.TestCase): 12 | def setUp(self): 13 | self.key_handler = btceapi.KeyHandler(TEST_KEY_FILE) 14 | self.connection = btceapi.BTCEConnection() 15 | 16 | def tearDown(self): 17 | self.connection.close() 18 | self.connection = None 19 | self.key_handler.close() 20 | 21 | def test_construction(self): 22 | keys = list(self.key_handler.keys) 23 | t = btceapi.TradeAPI(keys[0], self.key_handler, self.connection) 24 | 25 | def test_key_info(self): 26 | for key in self.key_handler.keys: 27 | t = btceapi.TradeAPI(key, self.key_handler, self.connection) 28 | r = t.getInfo() 29 | 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | --------------------------------------------------------------------------------