├── predictionprice ├── __init__.py ├── derivedpoloniex │ ├── __init__.py │ ├── exchangetrade.py │ └── margintrade.py └── predictionprice.py ├── .gitattributes ├── setup.py ├── .gitignore ├── examples ├── exchangetrade │ ├── exchangetradebot.py │ └── getexchangebalance.py └── margintrade │ ├── margintradebot.py │ └── getmarginbalance.py └── README.md /predictionprice/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.0.1" 2 | from .predictionprice import PredictionPrice -------------------------------------------------------------------------------- /predictionprice/derivedpoloniex/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .exchangetrade import ExchangeTradePoloniex 3 | from .margintrade import MarginTradePoloniex -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name = "predictionprice", 5 | version = "2.0.1", 6 | description = "Prediction price for Python 2.7.x", 7 | license = "MIT", 8 | author = "darden1", 9 | author_email = "darden066@gmail.com", 10 | url = "https://github.com/darden1/python-predictionprice/", 11 | keywords = "", 12 | packages = find_packages(), 13 | install_requires = ["numpy","pandas","matplotlib","scikit-learn","apscheduler","poloniex==0.2.2"], 14 | dependency_links = ["git+https://git@github.com/darden1/python-poloniex.git@master#egg=poloniex-0.2.2"], 15 | zip_safe=False 16 | ) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask instance folder 57 | instance/ 58 | 59 | # Scrapy stuff: 60 | .scrapy 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyBuilder 66 | target/ 67 | 68 | # IPython Notebook 69 | .ipynb_checkpoints 70 | 71 | # pyenv 72 | .python-version 73 | 74 | # celery beat schedule file 75 | celerybeat-schedule 76 | 77 | # dotenv 78 | .env 79 | 80 | # virtualenv 81 | venv/ 82 | ENV/ 83 | 84 | # Spyder project settings 85 | .spyderproject 86 | 87 | # Rope project settings 88 | .ropeproject 89 | 90 | # ========================= 91 | # Operating System Files 92 | # ========================= 93 | 94 | # OSX 95 | # ========================= 96 | 97 | .DS_Store 98 | .AppleDouble 99 | .LSOverride 100 | 101 | # Thumbnails 102 | ._* 103 | 104 | # Files that might appear in the root of a volume 105 | .DocumentRevisions-V100 106 | .fseventsd 107 | .Spotlight-V100 108 | .TemporaryItems 109 | .Trashes 110 | .VolumeIcon.icns 111 | 112 | # Directories potentially created on remote AFP share 113 | .AppleDB 114 | .AppleDesktop 115 | Network Trash Folder 116 | Temporary Items 117 | .apdisk 118 | 119 | # Windows 120 | # ========================= 121 | 122 | # Windows image file caches 123 | Thumbs.db 124 | ehthumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | # Recycle Bin used on file shares 130 | $RECYCLE.BIN/ 131 | 132 | # Windows Installer files 133 | *.cab 134 | *.msi 135 | *.msm 136 | *.msp 137 | 138 | # Windows shortcuts 139 | *.lnk 140 | -------------------------------------------------------------------------------- /examples/exchangetrade/exchangetradebot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from apscheduler.schedulers.blocking import BlockingScheduler 4 | from predictionprice.derivedpoloniex import ExchangeTradePoloniex 5 | from predictionprice import PredictionPrice 6 | 7 | myGmailAddress = "********@gmail.com" 8 | myGmailAddressPassword = "************" 9 | myAPIKey = "************************" 10 | mySecret = "************************************************" 11 | 12 | coins = ["ETH", "XMR", "XRP", "FCT", "DASH"] 13 | backTestOptParams = [ 14 | [20, 40, 20, 40], 15 | [20, 40, 20, 40], 16 | [20, 40, 20, 40], 17 | [20, 40, 20, 40], 18 | [20, 40, 20, 40]] 19 | 20 | basicCoin = "BTC" 21 | workingDirPath = os.path.dirname(os.path.abspath(__file__)) 22 | 23 | 24 | def botRoutine(): 25 | 26 | ppList = [] 27 | tomorrwPricePrediction = [] 28 | 29 | # --- Prediction price and back test 30 | for coinIndex in range(len(coins)): 31 | pp = PredictionPrice(currentPair=basicCoin + "_" + coins[coinIndex], workingDirPath=workingDirPath, 32 | gmailAddress=myGmailAddress, gmailAddressPassword=myGmailAddressPassword, 33 | backTestOptNumFeatureMin=backTestOptParams[coinIndex][0], 34 | backTestOptNumFeatureMax=backTestOptParams[coinIndex][1], 35 | backTestOptNumTrainSampleMin=backTestOptParams[coinIndex][2], 36 | backTestOptNumTrainSampleMax=backTestOptParams[coinIndex][3]) 37 | 38 | pp.fit(pp.appreciationRate_, pp.quantizer(pp.appreciationRate_)) 39 | pp.sendMail(pp.getSummary()) 40 | ppList.append(pp) 41 | if pp.backTestResult_["AccuracyRateUp"].values > 0.5: 42 | tomorrwPricePrediction.append(pp.tomorrowPriceFlag_) 43 | else: 44 | tomorrwPricePrediction.append(False) 45 | 46 | # --- Fit balance 47 | try: 48 | polo = ExchangeTradePoloniex(APIKey=myAPIKey, Secret=mySecret, workingDirPath=workingDirPath, 49 | gmailAddress=myGmailAddress, gmailAddressPassword=myGmailAddressPassword, 50 | coins=coins, buySigns=tomorrwPricePrediction) 51 | polo.savePoloniexBalanceToCsv() 52 | polo.fitBalance() 53 | polo.sendMailBalance(polo.getSummary()) 54 | polo.savePoloniexBalanceToCsv() 55 | except: 56 | pass 57 | 58 | # --- Write log 59 | for coinIndex in range(len(coins)): 60 | pp = ppList[coinIndex] 61 | writeBotLog(pp.getSummary()) 62 | writeBotLog(polo.getSummary()) 63 | 64 | # --- Back test optimization 65 | for coinIndex in range(len(coins)): 66 | pp = ppList[coinIndex] 67 | pp.backTestOptimization(pp.appreciationRate_, pp.quantizer(pp.appreciationRate_)) 68 | 69 | 70 | def writeBotLog(logStr): 71 | fileName = __file__.split(".py")[0] + ".log" 72 | f = open(fileName, "a") 73 | f.write(logStr) 74 | f.close() 75 | 76 | 77 | if __name__ == "__main__": 78 | sc = BlockingScheduler(timezone="UTC") 79 | sc.add_job(botRoutine, "cron", hour=0, minute=1) 80 | sc.start() 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **A module for a bot trading the virtual currency** 2 | ##### _Tested on Python 2.7.12_ 3 | ##### Very thanks to [s4w3d0ff](https://github.com/s4w3d0ff) and [python-poloniex](https://github.com/s4w3d0ff/python-poloniex) 4 | 5 | ### Features: 6 | - predictionprice module is a set of tools for making a bot that trade virtual currency automatically according to the prediction with the machine learning. 7 | - You can do mainly the followings by using this module. 8 | - To get the price of virtual currency from Poloniex.com 9 | - Prediction if the tomorrow's virtual currency price rise or fall by machine learning decision tree algorithm using the scikit-learn. 10 | - Implementation of the back test. 11 | - Optimization of the number of the features and training samples. 12 | - The features are represented with the sequencial past prices. 13 | - The training sample is a set of the features. 14 | - Implementation of the market trade with accessing to Poloniex.com in accordance with the predicted buying and selling signs. 15 | - Function to inform the results of the prediction and the trading by e-mail. 16 | 17 | ### Prerequired: 18 | - [poloniex(An API wrapper for Poloniex.com written in Python)](https://github.com/s4w3d0ff/python-poloniex)==0.2.2 19 | - poloniex-0.2.2 will be installed automatically when you install predictionprice module. 20 | - An account of Poloniex.com 21 | - APIKey and Secret to access to Poloniex.com with API. 22 | - A gmail account. 23 | 24 | ### Install: 25 | ``` 26 | git clone https://github.com/darden1/python-predictionprice.git 27 | cd python-predictionprice 28 | python setup.py install 29 | ``` 30 | 31 | ### Uninstall: 32 | ``` 33 | pip uninstall predictionprice 34 | ``` 35 | 36 | ### Usage: 37 | - Examples of auto trading bot have already prepared. 38 | - The bot executes followings one time a day. 39 | - Back test. 40 | - Prediction the virtual currency tomorrow price will rise or fall. 41 | - Trading with market price in accordance with the prediction. 42 | - Optimization of the number of the features and training samples.(Optimized learning parameters are used for next day prediction) 43 | - Sending e-mail to inform the results of the execution. 44 | - Bots are in examples folder. 45 | - examples/exchangetrade/exchangetradebot.py is for exchange trade. 46 | - examples/margintrade/margintradebot.py is for margin trade. 47 | - Remark to transfer fund to margin account when you use margintradebot.py. 48 | - Open exchangetradebot.py or margintradebot.py and set your gmail address, password, poloniex.com APIKey and Secret. 49 | - Run this script with nohup as following. 50 | ``` 51 | nohup python exchangetradebot.py > out.log 2> err.log & 52 | or 53 | nohup python margintradebot.py > out.log 2> err.log & 54 | ``` 55 | - You can check the process ID as following when you want to kill the process. 56 | ``` 57 | ps auxw | grep python 58 | ``` 59 | - You can change the combination of the currency pair you want to trade with `coins` and `basicCoin`. 60 | 61 | ### Caution: 62 | - It is **not absolutely guaranteed** to increase your assets by using this bot. 63 | - I hope if this could help you to create a much better one than this bot. (I would be very happy if you would tell me the new algorithm of the bot when you could achieve it.) 64 | -------------------------------------------------------------------------------- /examples/margintrade/margintradebot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from apscheduler.schedulers.blocking import BlockingScheduler 4 | from predictionprice.derivedpoloniex import MarginTradePoloniex 5 | from predictionprice import PredictionPrice 6 | 7 | myGmailAddress = "********@gmail.com" 8 | myGmailAddressPassword = "************" 9 | myAPIKey = "************************" 10 | mySecret = "************************************************" 11 | 12 | coins = ["ETH", "XMR", "XRP", "FCT", "DASH"] 13 | backTestOptParams = [ 14 | [20, 40, 20, 40], 15 | [20, 40, 20, 40], 16 | [20, 40, 20, 40], 17 | [20, 40, 20, 40], 18 | [20, 40, 20, 40]] 19 | 20 | basicCoin = "BTC" 21 | workingDirPath = os.path.dirname(os.path.abspath(__file__)) 22 | 23 | def botRoutine(): 24 | 25 | ppList = [] 26 | tradeSigns = [] 27 | 28 | # --- Prediction price and back test 29 | for coinIndex in range(len(coins)): 30 | pp = PredictionPrice(currentPair=basicCoin + "_" + coins[coinIndex], workingDirPath=workingDirPath, 31 | gmailAddress=myGmailAddress, gmailAddressPassword=myGmailAddressPassword, 32 | backTestOptNumFeatureMin=backTestOptParams[coinIndex][0], 33 | backTestOptNumFeatureMax=backTestOptParams[coinIndex][1], 34 | backTestOptNumTrainSampleMin=backTestOptParams[coinIndex][2], 35 | backTestOptNumTrainSampleMax=backTestOptParams[coinIndex][3], marginTrade=True) 36 | 37 | 38 | pp.fit(pp.appreciationRate_, pp.quantizer(pp.appreciationRate_)) 39 | pp.sendMail(pp.getSummary()) 40 | ppList.append(pp) 41 | if pp.tomorrowPriceFlag_: # Buy sign 42 | if pp.backTestResult_["AccuracyRateUp"].values > 0.5: 43 | tradeSigns.append("long") 44 | else: 45 | tradeSigns.append("hold") 46 | else: 47 | if pp.backTestResult_["AccuracyRateDown"].values > 0.5: 48 | tradeSigns.append("short") 49 | else: 50 | tradeSigns.append("hold") 51 | 52 | # --- Fit balance 53 | try: 54 | polo = MarginTradePoloniex(Key=myAPIKey, Secret=mySecret, workingDirPath=workingDirPath, 55 | gmailAddress=myGmailAddress, gmailAddressPassword=myGmailAddressPassword, 56 | coins=coins, tradeSigns=tradeSigns) 57 | polo.savePoloniexMarginAccountBalanceToCsv() 58 | polo.fitBalance() 59 | polo.sendMailBalance(polo.getSummary()) 60 | polo.savePoloniexMarginAccountBalanceToCsv() 61 | except: 62 | pass 63 | 64 | # --- Write log 65 | for coinIndex in range(len(coins)): 66 | pp = ppList[coinIndex] 67 | writeBotLog(pp.getSummary()) 68 | writeBotLog(polo.getSummary()) 69 | 70 | # --- Back test optimization 71 | for coinIndex in range(len(coins)): 72 | pp = ppList[coinIndex] 73 | pp.backTestOptimization(pp.appreciationRate_, pp.quantizer(pp.appreciationRate_)) 74 | 75 | def writeBotLog(logStr): 76 | fileName = __file__.split(".py")[0] + ".log" 77 | f = open(fileName, "a") 78 | f.write(logStr) 79 | f.close() 80 | 81 | if __name__ == "__main__": 82 | sc = BlockingScheduler(timezone="UTC") 83 | sc.add_job(botRoutine, "cron", hour=0, minute=1) 84 | sc.start() 85 | -------------------------------------------------------------------------------- /predictionprice/derivedpoloniex/exchangetrade.py: -------------------------------------------------------------------------------- 1 | import os 2 | import smtplib 3 | import email 4 | import csv 5 | import pytz 6 | import time 7 | import datetime 8 | import logging 9 | import numpy as np 10 | import pandas as pd 11 | import poloniex 12 | 13 | 14 | class ExchangeTradePoloniex(poloniex.Poloniex): 15 | def __init__(self, APIKey=False, Secret=False,timeout=10, coach=True, loglevel=logging.WARNING, extend=True, basicCoin="BTC", 16 | workingDirPath=".", gmailAddress="", gmailAddressPassword="", 17 | coins=[], buySigns=[] ): 18 | super(ExchangeTradePoloniex, self).__init__(APIKey, Secret, timeout, coach, loglevel, extend) 19 | self.basicCoin = basicCoin 20 | self.workingDirPath = workingDirPath 21 | self.gmailAddress = gmailAddress 22 | self.gmailAddressPassword = gmailAddressPassword 23 | self.coins = coins 24 | self.buySigns = buySigns 25 | self.todayStr = str(datetime.datetime.now(pytz.timezone("UTC")))[0:10] 26 | 27 | def myAvailableCompleteBalances(self): 28 | """Return AvailableCompleteBalances as pandas.DataFrame.""" 29 | balance = pd.DataFrame.from_dict(self.myCompleteBalances(account="exchange")).T 30 | return balance.iloc[np.where(balance["btcValue"] != "0.00000000")] 31 | 32 | def myEstimatedValueOfHoldings(self): 33 | """Return EstimatedValueOfHoldings.""" 34 | balance = self.myAvailableCompleteBalances() 35 | estimatedValueOfHoldingsAsBTC = np.sum(np.float_(balance.iloc[:, 1])) 36 | lastValueUSDT_BTC = pd.DataFrame.from_dict(self.marketTicker()).T.loc["USDT_BTC"]["last"] 37 | estimatedValueOfHoldingsAsUSD = np.float_(lastValueUSDT_BTC) * estimatedValueOfHoldingsAsBTC 38 | return estimatedValueOfHoldingsAsBTC, estimatedValueOfHoldingsAsUSD 39 | 40 | def cancelOnOrder(self,coin): 41 | """Cancel on Exchange Order""" 42 | onOrders = pd.DataFrame.from_dict(self.myOrders(pair=self.basicCoin + "_" + coin)) 43 | if len(onOrders) == 0: return 44 | onExchangeOrders = onOrders.loc[np.where(onOrders["margin"]==0)] 45 | if len(onExchangeOrders) == 0: return 46 | 47 | while len(onExchangeOrders) != 0: 48 | orderId = onExchangeOrders["orderNumber"].tolist()[0] 49 | self.cancelOrder(orderId) 50 | onOrders = pd.DataFrame.from_dict(self.myOrders(pair=self.basicCoin + "_" + coin)) 51 | if len(onOrders) == 0: 52 | return 53 | else: 54 | onExchangeOrders = onOrders.loc[np.where(onOrders["margin"]==0)] 55 | 56 | def marketSell(self, coin, btcValue): 57 | """Sell coin with market price as estimated btcValue.""" 58 | self.cancelOnOrder(coin) 59 | balance = self.myAvailableCompleteBalances() 60 | if len(np.where(balance.index==coin)[0])==0: return 61 | if float(btcValue)>float(balance.loc[coin]["btcValue"]): 62 | return self.marketSellAll(coin) 63 | bids = pd.Series(pd.DataFrame.from_dict(self.marketOrders(pair=self.basicCoin + "_" + coin, depth=1000)["bids"]).values.tolist()) 64 | sumBtcValue = 0.0 65 | sumAmount = 0.0 66 | for rate, amount in zip(np.array(bids.tolist())[:,0],np.array(bids.tolist())[:,1]): 67 | sumAmount += float(amount) 68 | sumBtcValue += float(rate)*float(amount) 69 | if float(btcValue) < sumBtcValue: 70 | break 71 | coinAmount = np.floor((sumAmount - (float(sumBtcValue) - float(btcValue))/float(rate)) * 1e7) * 1e-7 72 | return self.sell(self.basicCoin + "_" + coin, rate, coinAmount) 73 | 74 | def marketSellAll(self, coin): 75 | """Sell all coin with market price.""" 76 | self.cancelOnOrder(coin) 77 | balance = self.myAvailableCompleteBalances() 78 | if len(np.where(balance.index==coin)[0])==0: return 79 | bids = pd.Series(pd.DataFrame.from_dict(self.marketOrders(pair=self.basicCoin + "_" + coin, depth=1000)["bids"]).values.tolist()) 80 | sumBtcValue = 0.0 81 | for rate, amount in zip(np.array(bids.tolist())[:,0],np.array(bids.tolist())[:,1]): 82 | sumBtcValue += float(rate)*float(amount) 83 | if float(balance.loc[coin]["btcValue"]) < sumBtcValue: 84 | break 85 | return self.sell(self.basicCoin + "_" + coin, rate, balance.loc[coin]["available"]) 86 | 87 | def marketBuy(self, coin, btcValue): 88 | """Buy coin with market price as estimated btcValue.""" 89 | self.cancelOnOrder(coin) 90 | balance = self.myAvailableCompleteBalances() 91 | if len(np.where(balance.index==self.basicCoin)[0])==0: return 92 | if float(btcValue)>float(balance.loc[self.basicCoin]["btcValue"]): 93 | return self.marketBuyAll(coin) 94 | asks = pd.Series(pd.DataFrame.from_dict(self.marketOrders(pair=self.basicCoin + "_" + coin, depth=1000)["asks"]).values.tolist()) 95 | sumBtcValue = 0.0 96 | sumAmount = 0.0 97 | for rate, amount in zip(np.array(asks.tolist())[:,0],np.array(asks.tolist())[:,1]): 98 | sumAmount += float(amount) 99 | sumBtcValue += float(rate)*float(amount) 100 | if float(btcValue) < sumBtcValue: 101 | break 102 | coinAmount = np.floor((sumAmount - (float(sumBtcValue) - float(btcValue)) / float(rate)) * 1e7) * 1e-7 103 | return self.buy(self.basicCoin + "_" + coin, rate, coinAmount) 104 | 105 | def marketBuyAll(self, coin): 106 | """Buy coin with market price as much as possible.""" 107 | self.cancelOnOrder(coin) 108 | balance = self.myAvailableCompleteBalances() 109 | if len(np.where(balance.index == self.basicCoin)[0]) == 0: return 110 | asks = pd.Series(pd.DataFrame.from_dict(self.marketOrders(pair=self.basicCoin + "_" + coin, depth=1000)["asks"]).values.tolist()) 111 | sumBtcValue = 0.0 112 | sumAmount = 0.0 113 | for rate, amount in zip(np.array(asks.tolist())[:, 0], np.array(asks.tolist())[:, 1]): 114 | sumAmount += float(amount) 115 | sumBtcValue += float(rate) * float(amount) 116 | if float(balance.loc[self.basicCoin]["btcValue"]) < sumBtcValue: 117 | break 118 | coinAmount = np.floor(( 119 | sumAmount - (float(sumBtcValue) - float(balance.loc[self.basicCoin]["btcValue"])) / float( 120 | rate)) * 1e7) * 1e-7 121 | if float(rate) * coinAmount < 0.0001: 122 | return 123 | return self.buy(self.basicCoin + "_" + coin, rate, coinAmount) 124 | 125 | def fitSell(self): 126 | """Sell coins in accordance with buySigns.""" 127 | balance = self.myAvailableCompleteBalances() 128 | for coinIndex in range(len(self.coins)): 129 | if not self.buySigns[coinIndex]: #Sign is Sell? 130 | if len(np.where(balance.index == self.coins[coinIndex])[0]) != 0: # Holding the coin? 131 | self.marketSellAll(self.coins[coinIndex]) 132 | 133 | def fitBuy(self): 134 | """Buy coins in accordance with buySigns.""" 135 | balance = self.myAvailableCompleteBalances() 136 | if np.sum(self.buySigns)==0: # All signs are sell? 137 | return 138 | else: 139 | myBTC,myUSD = self.myEstimatedValueOfHoldings() 140 | distributionBTCValue = myBTC*1.0/np.sum(self.buySigns) 141 | # --- Sell extra coins 142 | numSelledExtraCoins = 0 143 | for coinIndex in range(len(self.coins)): 144 | if self.buySigns[coinIndex]: #Sign is Buy? 145 | if len(np.where(balance.index == self.coins[coinIndex])[0]) != 0: # Holding the coin? 146 | extraBTCValue = float(balance.loc[self.coins[coinIndex]]["btcValue"]) - float(distributionBTCValue) 147 | if extraBTCValue>0: 148 | self.marketSell(self.coins[coinIndex],extraBTCValue) 149 | numSelledExtraCoins += 1 150 | 151 | # --- Buy coins by distlibuted btcValue 152 | balance = self.myAvailableCompleteBalances() 153 | myBTC,myUSD = self.myEstimatedValueOfHoldings() 154 | distributionBTCValue = myBTC*1.0/np.sum(self.buySigns) 155 | for coinIndex in range(len(self.coins)): 156 | if self.buySigns[coinIndex]: # Sign is Buy? 157 | if len(np.where(balance.index == self.coins[coinIndex])[0]) != 0: # Holding the coin? 158 | extraBTCValue = float(balance.loc[self.coins[coinIndex]]["btcValue"]) - float(distributionBTCValue) 159 | if extraBTCValue < 0: 160 | self.marketBuy(self.coins[coinIndex], np.abs(extraBTCValue)) 161 | else: 162 | self.marketBuy(self.coins[coinIndex], distributionBTCValue) 163 | 164 | def fitBalance(self): 165 | """Call fitSell and fitBuy.""" 166 | self.fitSell() 167 | self.fitBuy() 168 | 169 | def getSummary(self): 170 | myBTC, myUSD = self.myEstimatedValueOfHoldings() 171 | balance = self.myAvailableCompleteBalances() 172 | summaryStr = "" 173 | summaryStr += "-----------------------------------------\n" 174 | summaryStr += "Poloniex Balance.\n" 175 | summaryStr += "-----------------------------------------\n" 176 | summaryStr += "Today: " + self.todayStr + "\n" 177 | summaryStr += "Coins: " + str(self.coins) + "\n" 178 | summaryStr += "BuySigns: " + np.str(self.buySigns) + "\n" 179 | summaryStr += "\n" 180 | summaryStr += "Your total fund in exchange account:\n" 181 | summaryStr += str(myBTC) + " BTC\n" 182 | summaryStr += str(myUSD) + " USD\n" 183 | summaryStr += "\n" 184 | summaryStr += "Breakdown:\n" 185 | summaryStr += str(balance) 186 | return summaryStr 187 | 188 | def sendMailBalance(self, body): 189 | """Send the balance by e-mail.""" 190 | if self.gmailAddress == "" or self.gmailAddressPassword == "": 191 | return "Set your gmail address and password." 192 | # ---Create message 193 | msg = email.MIMEMultipart.MIMEMultipart() 194 | msg["From"] = self.gmailAddress 195 | msg["To"] = self.gmailAddress 196 | msg["Date"] = email.Utils.formatdate() 197 | msg["Subject"] = "Poloniex Balance" 198 | msg.attach(email.MIMEText.MIMEText(body)) 199 | # ---SendMail 200 | smtpobj = smtplib.SMTP("smtp.gmail.com", 587) 201 | smtpobj.ehlo() 202 | smtpobj.starttls() 203 | smtpobj.login(self.gmailAddress, self.gmailAddressPassword) 204 | smtpobj.sendmail(self.gmailAddress, self.gmailAddress, msg.as_string()) 205 | smtpobj.close() 206 | 207 | def savePoloniexBalanceToCsv(self): 208 | """Save EstimatedValueOfHoldings to csv file.""" 209 | fileName = self.workingDirPath + "/PoloniexBalance.csv" 210 | date = str(datetime.datetime.today())[0:19] 211 | myBTC, myUSD = self.myEstimatedValueOfHoldings() 212 | if os.path.exists(fileName): 213 | f = open(fileName, "a") 214 | writer = csv.writer(f, lineterminator="\n") 215 | else: 216 | f = open(fileName, "w") 217 | writer = csv.writer(f, lineterminator="\n") 218 | writer.writerow(["Date","BTC","USD"]) # Write header 219 | writer.writerow([date,str(myBTC),str(myUSD)]) 220 | f.close() 221 | -------------------------------------------------------------------------------- /predictionprice/derivedpoloniex/margintrade.py: -------------------------------------------------------------------------------- 1 | import os 2 | import smtplib 3 | import email 4 | import csv 5 | import pytz 6 | import time 7 | import datetime 8 | import logging 9 | import numpy as np 10 | import pandas as pd 11 | import poloniex 12 | 13 | 14 | class MarginTradePoloniex(poloniex.Poloniex): 15 | def __init__(self, Key=False, Secret=False,timeout=10, coach=True, loglevel=logging.WARNING, extend=True, basicCoin="BTC", 16 | workingDirPath=".", gmailAddress="", gmailAddressPassword="", 17 | coins=[], tradeSigns=[] ): 18 | super(MarginTradePoloniex, self).__init__(Key, Secret, timeout, coach, loglevel, extend) 19 | self.basicCoin = basicCoin 20 | self.workingDirPath = workingDirPath 21 | self.gmailAddress = gmailAddress 22 | self.gmailAddressPassword = gmailAddressPassword 23 | self.coins = coins 24 | self.tradeSigns = tradeSigns 25 | self.todayStr = str(datetime.datetime.now(pytz.timezone("UTC")))[0:10] 26 | self.leverage = 2.5 27 | 28 | 29 | def floatToEighthDigit(self, numFloat): 30 | """Change float number to string of eighth digit number.""" 31 | return "{0:.9f}".format(float(numFloat)).split(".")[0] + "." + "{0:.9f}".format(float(numFloat)).split(".")[1][0:8] 32 | 33 | def returnSummary(self): 34 | """Return margin account balance summary as pandas data frame.""" 35 | return pd.DataFrame.from_dict({"summary": self.returnMarginAccountSummary()}) 36 | 37 | def returnTradableBalance(self): 38 | """Return tradable balance.""" 39 | summary = self.returnSummary() 40 | return self.floatToEighthDigit(np.float(summary.loc["netValue"]) * self.leverage - np.float(summary.loc["totalBorrowedValue"])) 41 | 42 | def getOpeningMarginPosition(self): 43 | """Return AvailableCompleteBalances as pandas.DataFrame.""" 44 | position = pd.DataFrame.from_dict(self.getMarginPosition()).T 45 | if len(np.where(position["amount"] != "0.00000000")[0]) == 0: 46 | return False 47 | else: 48 | return position.iloc[np.where(position["amount"] != "0.00000000")] 49 | 50 | def cancelOnMarginOrder(self, coin): 51 | """Cancel on Margin Order""" 52 | onOrders = pd.DataFrame.from_dict(self.returnOpenOrders(pair=self.basicCoin + "_" + coin)) 53 | if len(onOrders) == 0: 54 | return 55 | onMarginOrders = onOrders.loc[np.where(onOrders["margin"] == 1)] 56 | if len(onMarginOrders) == 0: 57 | return 58 | while len(onMarginOrders) != 0: 59 | orderId = onMarginOrders["orderNumber"].tolist()[0] 60 | self.cancelOrder(orderId) 61 | onOrders = pd.DataFrame.from_dict(self.returnOpenOrders(pair=self.basicCoin + "_" + coin)) 62 | if len(onOrders) == 0: 63 | return 64 | else: 65 | onMarginOrders = onOrders.loc[np.where(onOrders["margin"] == 1)] 66 | 67 | def returnRateAndAmount(self, orderStr, coin, btcValue): 68 | """Return BTC rate and coin amount to trade some coin.""" 69 | order = pd.Series(pd.DataFrame.from_dict( 70 | self.marketOrders(pair=self.basicCoin + "_" + coin, depth=1000)[orderStr]).values.tolist()) 71 | sumBtcValue = 0.0 72 | sumAmount = 0.0 73 | for rate, amount in zip(np.array(order.tolist())[:, 0], np.array(order.tolist())[:, 1]): 74 | sumAmount += float(amount) 75 | sumBtcValue += float(rate) * float(amount) 76 | if float(btcValue) < sumBtcValue: 77 | break 78 | rate = self.floatToEighthDigit(rate) 79 | coinAmount = self.floatToEighthDigit(sumAmount - (float(sumBtcValue) - float(btcValue)) / float(rate)) 80 | return rate, coinAmount 81 | 82 | def marketMarginBuy(self, coin, btcValue): 83 | """Buy coin with market price as much as possible.""" 84 | self.cancelOnMarginOrder(coin) 85 | btcValue = self.floatToEighthDigit(btcValue) 86 | tradableBalance = self.returnTradableBalance() 87 | if float(btcValue) > float(tradableBalance): 88 | btcValue = tradableBalance 89 | rate, coinAmount = self.returnRateAndAmount("asks", coin, btcValue) 90 | if float(rate) * float(coinAmount) < 0.0001: 91 | return 92 | ret = self.marginBuy(self.basicCoin + "_" + coin, rate, coinAmount, lendingRate=0.02) 93 | while ret["success"] == 0: 94 | rate, coinAmount = self.returnRateAndAmount("asks", coin, btcValue) 95 | ret = self.marginBuy(self.basicCoin + "_" + coin, rate, coinAmount, lendingRate=0.02) 96 | time.sleep(1) 97 | btcValue = self.floatToEighthDigit(0.999 * float(btcValue)) 98 | return ret 99 | 100 | def marketMarginSell(self, coin, btcValue): 101 | """Buy coin with market price as much as possible.""" 102 | self.cancelOnMarginOrder(coin) 103 | btcValue = self.floatToEighthDigit(btcValue) 104 | tradableBalance = self.returnTradableBalance() 105 | if float(btcValue) > float(tradableBalance): 106 | btcValue = tradableBalance 107 | rate, coinAmount = self.returnRateAndAmount("bids", coin, btcValue) 108 | if float(rate) * float(coinAmount) < 0.0001: 109 | return 110 | ret = self.marginSell(self.basicCoin + "_" + coin, rate, coinAmount, lendingRate=0.02) 111 | while ret["success"] == 0: 112 | rate, coinAmount = self.returnRateAndAmount("bids", coin, btcValue) 113 | ret = self.marginSell(self.basicCoin + "_" + coin, rate, coinAmount, lendingRate=0.02) 114 | time.sleep(1) 115 | btcValue = self.floatToEighthDigit(0.999 * float(btcValue)) 116 | return ret 117 | 118 | def distributedBtcValue(self): 119 | """Return BTC value that is whole you can trade divide tby the number of coin you want to trade.""" 120 | summary = self.returnSummary() 121 | return self.floatToEighthDigit(float(summary.loc["netValue"]) * self.leverage / len(self.coins)) 122 | 123 | def fitBalance(self): 124 | """Re-take your positions based on the trading sign.""" 125 | position = self.getOpeningMarginPosition() 126 | for coinIndex in range(len(self.coins)): 127 | if type(position)!=pd.core.frame.DataFrame: # Hold nothing today? 128 | if self.tradeSigns[coinIndex] != "hold": # Trade sign is not "hold" ? 129 | if self.tradeSigns[coinIndex] == "long": # Trade sign is "long" ? 130 | self.marketMarginBuy(self.coins[coinIndex], self.distributedBtcValue()) 131 | else: 132 | self.marketMarginSell(self.coins[coinIndex], self.distributedBtcValue()) 133 | else: 134 | if self.basicCoin + "_" + self.coins[coinIndex] in position.index: # Opening the position? 135 | if position.loc[self.basicCoin + "_" + self.coins[coinIndex]]["type"] != self.tradeSigns[coinIndex]: # Position type is not the same with trade sign? 136 | self.closeMarginPosition(self.basicCoin + "_" + self.coins[coinIndex]) 137 | if self.tradeSigns[coinIndex] != "hold": # Trade sign is not "hold" ? 138 | if self.tradeSigns[coinIndex] == "long": # Trade sign is "long" ? 139 | self.marketMarginBuy(self.coins[coinIndex], self.distributedBtcValue()) 140 | else: 141 | self.marketMarginSell(self.coins[coinIndex], self.distributedBtcValue()) 142 | else: 143 | if self.tradeSigns[coinIndex] != "hold": # Trade sign is not "hold" ? 144 | if self.tradeSigns[coinIndex] == "long": # Trade sign is "long" ? 145 | self.marketMarginBuy(self.coins[coinIndex], self.distributedBtcValue()) 146 | else: 147 | self.marketMarginSell(self.coins[coinIndex], self.distributedBtcValue()) 148 | 149 | def closeAllOpeningMarginPosition(self): 150 | """Close all your positions.""" 151 | position = self.getOpeningMarginPosition() 152 | if type(position) == pd.core.frame.DataFrame: 153 | for coinIndex in range(len(position.index)): 154 | self.closeMarginPosition(position.index[coinIndex]) 155 | 156 | def returnEstimatedValueOfHoldings(self): 157 | """Return EstimatedValueOfHoldings.""" 158 | summary = self.returnSummary() 159 | estimatedValueOfHoldingsAsBTC = float(summary.loc["netValue"]) 160 | lastValueUSDT_BTC = pd.DataFrame.from_dict(self.returnTicker()).T.loc["USDT_BTC"]["last"] 161 | estimatedValueOfHoldingsAsUSD = float(lastValueUSDT_BTC) * estimatedValueOfHoldingsAsBTC 162 | return estimatedValueOfHoldingsAsBTC, estimatedValueOfHoldingsAsUSD 163 | 164 | def getSummary(self): 165 | """Get your balance and return it as string.""" 166 | myBTC, myUSD = self.returnEstimatedValueOfHoldings() 167 | summary = self.returnSummary() 168 | positions = self.getOpeningMarginPosition() 169 | onOrders = pd.DataFrame.from_dict(self.returnOpenOrders(pair="all")) 170 | if len(onOrders) == 0: 171 | onMarginOrders ="Nothing" 172 | else: 173 | onMarginOrders = onOrders.loc[np.where(onOrders["margin"] == 1)] 174 | if len(onMarginOrders) == 0: 175 | onMarginOrders = "Nothing" 176 | 177 | summaryStr = "" 178 | summaryStr += "-----------------------------------------\n" 179 | summaryStr += "Poloniex Margin Account Balance.\n" 180 | summaryStr += "-----------------------------------------\n" 181 | summaryStr += "Today: " + self.todayStr + "\n" 182 | summaryStr += "Coins: " + str(self.coins) + "\n" 183 | summaryStr += "TradeSigns: " + np.str(self.tradeSigns) + "\n" 184 | summaryStr += "\n" 185 | summaryStr += "Your total fund in margin account:\n" 186 | summaryStr += str(myBTC) + " BTC\n" 187 | summaryStr += str(myUSD) + " USD\n" 188 | summaryStr += "\n" 189 | summaryStr += "Summary:\n" 190 | summaryStr += str(summary) 191 | summaryStr += "\n" 192 | summaryStr += "\n" 193 | summaryStr += "Breakdown:\n" 194 | summaryStr += str(positions) 195 | summaryStr += "\n" 196 | summaryStr += "\n" 197 | summaryStr += "On order:\n" 198 | summaryStr += str(onMarginOrders) 199 | return summaryStr 200 | 201 | 202 | def sendMailBalance(self, body): 203 | """Send the balance by e-mail.""" 204 | if self.gmailAddress == "" or self.gmailAddressPassword == "": 205 | return "Set your gmail address and password." 206 | # ---Create message 207 | msg = email.MIMEMultipart.MIMEMultipart() 208 | msg["From"] = self.gmailAddress 209 | msg["To"] = self.gmailAddress 210 | msg["Date"] = email.Utils.formatdate() 211 | msg["Subject"] = "Poloniex Balance" 212 | msg.attach(email.MIMEText.MIMEText(body)) 213 | # ---SendMail 214 | smtpobj = smtplib.SMTP("smtp.gmail.com", 587) 215 | smtpobj.ehlo() 216 | smtpobj.starttls() 217 | smtpobj.login(self.gmailAddress, self.gmailAddressPassword) 218 | smtpobj.sendmail(self.gmailAddress, self.gmailAddress, msg.as_string()) 219 | smtpobj.close() 220 | 221 | 222 | def savePoloniexMarginAccountBalanceToCsv(self): 223 | """Save EstimatedValueOfHoldings to csv file.""" 224 | fileName = self.workingDirPath + "/PoloniexMarginAccountBalance.csv" 225 | date = str(datetime.datetime.today())[0:19] 226 | myBTC, myUSD = self.returnEstimatedValueOfHoldings() 227 | if os.path.exists(fileName): 228 | f = open(fileName, "a") 229 | writer = csv.writer(f, lineterminator="\n") 230 | else: 231 | f = open(fileName, "w") 232 | writer = csv.writer(f, lineterminator="\n") 233 | writer.writerow(["Date", "BTC", "USD"]) # Write header 234 | writer.writerow([date, str(myBTC), str(myUSD)]) 235 | f.close() 236 | -------------------------------------------------------------------------------- /predictionprice/predictionprice.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Copyright (c) 2016 Tylor Darden 4 | Released under the MIT license 5 | http://opensource.org/licenses/mit-license.php 6 | """ 7 | import sys 8 | import os 9 | import pytz 10 | import time 11 | import datetime 12 | import smtplib 13 | import email 14 | import pickle 15 | import csv 16 | import numpy as np 17 | import pandas as pd 18 | import matplotlib 19 | matplotlib.use('Agg') 20 | import matplotlib.pyplot as plt 21 | from mpl_toolkits.mplot3d import Axes3D 22 | from sklearn import tree 23 | from sklearn.preprocessing import StandardScaler 24 | import poloniex 25 | import logging 26 | 27 | 28 | class PredictionPrice(object): 29 | def __init__(self, currentPair="BTC_ETH", workingDirPath=".", 30 | gmailAddress="", gmailAddressPassword="", 31 | waitGettingTodaysChart=True, waitGettingTodaysChartTime=60, 32 | numFeature=30, numTrainSample=30, standardizationFeatureFlag=True, numStudyTrial=50, 33 | useBackTestOptResult=True, backTestInitialFund=1000, backTestSpread=0, backTestDays=60, 34 | backTestOptNumFeatureMin=20, backTestOptNumFeatureMax=40, backTestOptNumTrainSampleMin=20, backTestOptNumTrainSampleMax=40, 35 | marginTrade=False): 36 | 37 | self.marginTrade = marginTrade 38 | self.currentPair = currentPair 39 | self.workingDirPath = workingDirPath 40 | self.useBackTestOptResult=useBackTestOptResult 41 | if self.useBackTestOptResult and os.path.exists(self.workingDirPath + "/backTestOptResult_" + self.currentPair + ".pickle"): 42 | with open(self.workingDirPath + "/backTestOptResult_" + self.currentPair + ".pickle", mode='rb') as f: 43 | self.backTestOptResult_ = pickle.load(f) 44 | self.numFeature = self.backTestOptResult_["numFeatureOpt"] 45 | self.numTrainSample = self.backTestOptResult_["numTrainSampleOpt"] 46 | else: 47 | self.useBackTestOptResult = False 48 | self.numFeature = numFeature 49 | self.numTrainSample = numTrainSample 50 | self.standardizationFeatureFlag = standardizationFeatureFlag 51 | 52 | self.numStudyTrial = numStudyTrial 53 | self.gmailAddress = gmailAddress 54 | self.gmailAddressPassword = gmailAddressPassword 55 | 56 | self.waitGettingTodaysChart = waitGettingTodaysChart 57 | self.waitGettingTodaysChartTime = waitGettingTodaysChartTime 58 | 59 | self.backTestInitialFund = backTestInitialFund 60 | self.backTestSpread = backTestSpread 61 | self.backTestDays = backTestDays 62 | 63 | self.backTestOptNumFeatureMin = backTestOptNumFeatureMin 64 | self.backTestOptNumFeatureMax = backTestOptNumFeatureMax 65 | self.backTestOptNumTrainSampleMin = backTestOptNumTrainSampleMin 66 | self.backTestOptNumTrainSampleMax = backTestOptNumTrainSampleMax 67 | 68 | self.todayStr = str(datetime.datetime.now(pytz.timezone("UTC")))[0:10] 69 | self.chartData_ = self.getChartData() 70 | #---self.saveChartData(self.chartData_) 71 | #---self.chartData_ = self.loadChartData() 72 | self.appreciationRate_ = self.getAppreciationRate(self.chartData_.open) 73 | self.chartDataLatestDayStr = str(self.chartData_.date[0])[0:10] 74 | 75 | if self.waitGettingTodaysChart: 76 | for tmpIndex in range(int(self.waitGettingTodaysChartTime*60.0/20.0)): 77 | if not (self.todayStr == self.chartDataLatestDayStr): 78 | time.sleep(20) 79 | else: 80 | break 81 | self.chartData_ = self.getChartData() 82 | self.appreciationRate_ = self.getAppreciationRate(self.chartData_.open) 83 | self.chartDataLatestDayStr = str(self.chartData_.date[0])[0:10] 84 | 85 | def reverseDataFrame(self,dataFrame): 86 | """Reverse the index of chart data as last data comes first.""" 87 | dataFrame = dataFrame[::-1] 88 | dataFrame.index = dataFrame.index[::-1] 89 | return dataFrame 90 | 91 | def getChartData(self): 92 | """Get chart data.""" 93 | polo = poloniex.Poloniex(timeout = 10, coach = True, extend=True) 94 | chartData = pd.DataFrame(polo.marketChart(self.currentPair, period=polo.DAY, start=time.time() - polo.DAY * 500,end=time.time())).astype(float) 95 | chartData.date = pd.DataFrame([datetime.datetime.fromtimestamp(chartData.date[i]).date() for i in range(len(chartData.date))]) 96 | return self.reverseDataFrame(chartData) 97 | 98 | def saveChartData(self,chartData): 99 | """Save chart data to a pickle file. You can load it with loadChartData() for debug.""" 100 | with open("chartData_"+ self.currentPair + ".pickle", mode="wb") as f: 101 | pickle.dump(chartData, f) 102 | return 103 | 104 | def loadChartData(self): 105 | """You can load chart data from a pickle file. You have to save chart data with saveChartData() before calling.""" 106 | with open("chartData_"+ self.currentPair + ".pickle", mode="rb") as f: 107 | chartData = pickle.load(f) 108 | return chartData 109 | 110 | def getAppreciationRate(self,price): 111 | """Transrate chart price to appreciation rate.""" 112 | return np.append(-np.diff(price) / price[1:].values,0) 113 | 114 | def quantizer(self, y): 115 | """Transrate appreciation rate to -1 or 1 for preparing teacher data.""" 116 | return np.where(np.array(y) >= 0.0, 1, -1) 117 | 118 | def standardizationFeature(self, train_X, test_X): 119 | """Standarize feature data.""" 120 | sc = StandardScaler() 121 | train_X_std = sc.fit_transform(train_X) 122 | test_X_std = sc.transform(test_X) 123 | return train_X_std, test_X_std 124 | 125 | def preparationTrainSample(self,sampleData,classData,trainStartIndex, numFeature, numTrainSample): 126 | """Prepare training sample.""" 127 | train_X = [] 128 | train_y = [] 129 | for i in range(numTrainSample): 130 | train_X.append(sampleData[trainStartIndex + i + 1:trainStartIndex + numFeature + i + 1]) 131 | train_y.append(classData[trainStartIndex + i]) 132 | return np.array(train_X), np.array(train_y) 133 | 134 | def prediction(self, sampleData, classData, trainStartIndex, numFeature, numTrainSample): 135 | """Return probability of price rise.""" 136 | train_X, train_y = self.preparationTrainSample(sampleData, classData, trainStartIndex, numFeature, numTrainSample) 137 | X = np.array([sampleData[trainStartIndex:trainStartIndex + numFeature]]) 138 | if self.standardizationFeatureFlag: 139 | train_X, X = self.standardizationFeature(train_X, X) 140 | y = [] 141 | for i in range(0, self.numStudyTrial): 142 | clf = tree.DecisionTreeClassifier() 143 | clf.fit(train_X, train_y) 144 | y.append(clf.predict(X)[0]) 145 | return sum(y) * 1.0 / len(y) 146 | 147 | def setTomorrowPriceProbability(self, sampleData, classData): 148 | """Set probability of price rise and buying signal to menber valiables.""" 149 | self.tomorrowPriceProbability_ = (self.prediction(sampleData, classData, 0, self.numFeature, self.numTrainSample) + 1.0) / 2.0 150 | if self.tomorrowPriceProbability_>0.5: 151 | self.tomorrowPriceFlag_ = True 152 | else: 153 | self.tomorrowPriceFlag_ = False 154 | return self.tomorrowPriceProbability_ 155 | 156 | def backTest(self, sampleData, classData, numFeature, numTrainSample, saveBackTestGraph): 157 | """Do back test and return the result.""" 158 | Y = [] 159 | YPrediction = [] 160 | fund = [self.backTestInitialFund] 161 | pastDay = 0 162 | accuracyUp = 0 163 | accuracyDown = 0 164 | for trainStartIndex in range(self.backTestDays, 0, -1): 165 | yPrediction = self.quantizer(self.prediction(sampleData, classData, trainStartIndex, numFeature, numTrainSample)) 166 | y = self.quantizer(classData[trainStartIndex - 1]) 167 | Y.append(y.tolist()) 168 | YPrediction.append(yPrediction.tolist()) 169 | pastDay += 1 170 | if yPrediction == y: 171 | if yPrediction == 1: 172 | accuracyUp += 1 173 | fund.append(fund[pastDay - 1] * (1 + abs(self.appreciationRate_[trainStartIndex - 1]) - self.backTestSpread)) 174 | else: 175 | accuracyDown += 1 176 | if self.marginTrade: 177 | fund.append(fund[pastDay - 1] * (1 + abs(self.appreciationRate_[trainStartIndex - 1]) - self.backTestSpread)) 178 | else: 179 | fund.append(fund[pastDay - 1]) 180 | else: 181 | if yPrediction == 1: 182 | fund.append(fund[pastDay - 1] * (1 - abs(self.appreciationRate_[trainStartIndex - 1]) - self.backTestSpread)) 183 | else: 184 | if self.marginTrade: 185 | fund.append(fund[pastDay - 1] * (1 - abs(self.appreciationRate_[trainStartIndex - 1]) - self.backTestSpread)) 186 | else: 187 | fund.append(fund[pastDay - 1]) 188 | 189 | backTestAccuracyRateUp = float(accuracyUp) / sum(np.array(YPrediction)[np.where(np.array(YPrediction) == 1)]) 190 | backTestAccuracyRateDown = -float(accuracyDown) / sum(np.array(YPrediction)[np.where(np.array(YPrediction) == -1)]) 191 | 192 | trainStartIndex = 0 193 | backTestCurrentPrice = self.chartData_.open[trainStartIndex:trainStartIndex + self.backTestDays + 1] 194 | backTestCurrentPrice = backTestCurrentPrice[::-1].tolist() 195 | backTestDate = self.chartData_.date[trainStartIndex:trainStartIndex + self.backTestDays + 1] 196 | backTestDate = backTestDate[::-1].tolist() 197 | 198 | backTestFinalFund = fund[-1] 199 | backTestInitialCurrentPrice = backTestCurrentPrice[0] 200 | backTestFinalCurrentPrice = backTestCurrentPrice[-1] 201 | backTestIncreasedFundRatio = (backTestFinalFund - self.backTestInitialFund) / self.backTestInitialFund 202 | backTestIncreasedCurrentPriceRatio = (backTestFinalCurrentPrice - backTestInitialCurrentPrice) / backTestInitialCurrentPrice 203 | 204 | columnNames = ["AccuracyRateUp", "AccuracyRateDown", 205 | "InitialFund", "FinalFund", "IncreasedFundRatio", 206 | "InitialCurrentPrice", "FinalCurrentPrice", "IncreasedCurrentPriceRatio"] 207 | columnValues = [backTestAccuracyRateUp, backTestAccuracyRateDown, 208 | self.backTestInitialFund, backTestFinalFund, backTestIncreasedFundRatio, 209 | backTestInitialCurrentPrice, backTestFinalCurrentPrice, backTestIncreasedCurrentPriceRatio] 210 | backTestResult = pd.DataFrame(np.array([columnValues]), columns=columnNames) 211 | 212 | if saveBackTestGraph: 213 | fig1, ax1 = plt.subplots(figsize=(11, 6)) 214 | p1, = ax1.plot(backTestDate, fund, "-ob") 215 | ax1.set_title("Back test (" + self.currentPair + ")") 216 | ax1.set_xlabel("Day") 217 | ax1.set_ylabel("Fund") 218 | plt.grid(fig1) 219 | ax2 = ax1.twinx() 220 | p2, = ax2.plot(backTestDate, backTestCurrentPrice, '-or') 221 | ax2.set_ylabel("Price[" + self.currentPair + "]") 222 | ax1.legend([p1, p2], ["Fund", "Price_" + self.currentPair], loc="upper left") 223 | plt.savefig(self.workingDirPath + "/backTest_" + self.currentPair + ".png", dpi=50) 224 | plt.close() 225 | 226 | self.backTestResult_ = backTestResult 227 | 228 | return backTestResult 229 | 230 | def backTestOptimization(self, sampleData, classData): 231 | """Optimize the number of features and training samples and save the results to a pickle file.""" 232 | X = np.arange(self.backTestOptNumFeatureMin, self.backTestOptNumFeatureMax + 1, 1) 233 | Y = np.arange(self.backTestOptNumTrainSampleMin, self.backTestOptNumTrainSampleMax + 1, 1) 234 | X, Y = np.meshgrid(X, Y) 235 | Z = np.zeros([len(Y[:]), len(X[0])]) 236 | 237 | for i in range(0, len(X[0])): 238 | for j in range(0, len(Y[:])): 239 | Z[j][i] = self.backTest(sampleData, classData, X[j][i], Y[j][i], False)["IncreasedFundRatio"].values[0] 240 | #--- print("-" * 80) 241 | #--- print("NumFeatur: " + str(X[j][i])) 242 | #--- print("NumTrainSample: " + str(Y[j][i])) 243 | #--- print("IncreasedFundRatio[%]: " + str(round(Z[j][i] * 100, 1))) 244 | 245 | maxZRow = np.where(Z == np.max(Z))[0][0] 246 | maxZCol = np.where(Z == np.max(Z))[1][0] 247 | 248 | numFeatureOpt = X[maxZRow][maxZCol] 249 | numTrainSampleOpt = Y[maxZRow][maxZCol] 250 | dateOpt = datetime.datetime.now() 251 | 252 | backTestOptResult = {"X": X, "Y": Y, "Z": Z, "numFeatureOpt": numFeatureOpt, 253 | "numTrainSampleOpt": numTrainSampleOpt, "dateOpt": dateOpt} 254 | with open(self.workingDirPath + "/backTestOptResult_" + self.currentPair + ".pickle", mode='wb') as f: 255 | pickle.dump(backTestOptResult, f) 256 | 257 | print("-" * 30 + " Optimization Result " + "-" * 30) 258 | print("NumFeatur: " + str(numFeatureOpt)) 259 | print("NumTrainSample: " + str(numTrainSampleOpt)) 260 | print("IncreasedFundRatio[%]: " + str(round(Z[maxZRow][maxZCol] * 100, 1))) 261 | 262 | fig = plt.figure() 263 | ax = Axes3D(fig) 264 | ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=plt.cm.hot) 265 | ax.contourf(X, Y, Z, zdir="z", offset=-2, cmap=plt.cm.hot) 266 | ax.set_title("Back test optimization (" + self.currentPair + ")") 267 | ax.set_xlabel("NumFeatur") 268 | ax.set_ylabel("NumTrainSample") 269 | ax.set_zlabel("IncreasedFundRatio") 270 | ax.view_init(90, 90) 271 | plt.savefig(self.workingDirPath + "/backTestOptResult_" + self.currentPair + ".png", dpi=50) 272 | plt.close() 273 | 274 | def fit(self, sampleData, classData): 275 | """Call backTest() and setTomorrowPriceProbability() in one sitting.""" 276 | self.backTest(sampleData, classData, self.numFeature, self.numTrainSample, True) 277 | self.setTomorrowPriceProbability(sampleData, classData) 278 | 279 | def getSummary(self): 280 | """Make summary sentence that include the result of the back test and the prediction of the price rise.""" 281 | summaryStr="" 282 | summaryStr += "-----------------------------------------\n" 283 | summaryStr += "Chart data info.\n" 284 | summaryStr += "-----------------------------------------\n" 285 | summaryStr += "CurrentPair: " + self.currentPair + "\n" 286 | summaryStr += "Today: " + self.todayStr + "\n" 287 | summaryStr += "LatestDayInData: " + self.chartDataLatestDayStr + "\n" 288 | summaryStr += "LatestOpenPriceInData: " + str(self.chartData_.open[0]) + "\n" 289 | summaryStr += "PreviousDayInData: " + str(self.chartData_.date[1])[0:10] + "\n" 290 | summaryStr += "PreviousOpenPriceInData: " + str(self.chartData_.open[1]) + "\n" 291 | summaryStr += "-----------------------------------------\n" 292 | summaryStr += "Back test info.\n" 293 | summaryStr += "-----------------------------------------\n" 294 | if self.useBackTestOptResult: 295 | summaryStr += "ExecOptDay: " + str(self.backTestOptResult_["dateOpt"])[0:19] + "\n" 296 | else: 297 | summaryStr += "ExecOptDay: Nan\n" 298 | summaryStr += "NumFeature: " + str(self.numFeature) + "\n" 299 | summaryStr += "NumTrainSample: " + str(self.numTrainSample) + "\n" 300 | summaryStr += "AccuracyRateUp[%]: " + str(round(self.backTestResult_["AccuracyRateUp"].values[0]*100, 1)) + "\n" 301 | summaryStr += "AccuracyRateDown[%]: " + str(round(self.backTestResult_["AccuracyRateDown"].values[0]*100, 1)) + "\n" 302 | summaryStr += "InitialFund: " + str(self.backTestResult_["InitialFund"].values[0]) + "\n" 303 | summaryStr += "FinalFund: " + str(self.backTestResult_["FinalFund"].values[0]) + "\n" 304 | summaryStr += "IncreasedFundRatio[%]: " + str(round(self.backTestResult_["IncreasedFundRatio"].values[0]*100, 1)) + "\n" 305 | summaryStr += "InitialCurrentPrice: " + str(self.backTestResult_["InitialCurrentPrice"].values[0]) + "\n" 306 | summaryStr += "FinalCurrentPrice: " + str(self.backTestResult_["FinalCurrentPrice"].values[0]) + "\n" 307 | summaryStr += "IncreasedCurrentPriceRatio[%]: " + str(round(self.backTestResult_["IncreasedCurrentPriceRatio"].values[0]*100, 1)) + "\n" 308 | summaryStr += "-----------------------------------------\n" 309 | summaryStr += "Tomorrow " + self.currentPair + " price prediction\n" 310 | summaryStr += "-----------------------------------------\n" 311 | summaryStr += "TomorrowPriceRise?: " + str(self.tomorrowPriceFlag_) +"\n" 312 | summaryStr += "Probability[%]: " + str(round(self.tomorrowPriceProbability_*100,1)) +"\n" 313 | return summaryStr 314 | 315 | def sendMail(self, body): 316 | """Send a mail to inform the summary of the prediction.""" 317 | if self.gmailAddress == "" or self.gmailAddressPassword == "": 318 | return "Set your gmail address and password." 319 | # ---Create message 320 | msg = email.MIMEMultipart.MIMEMultipart() 321 | msg["From"] = self.gmailAddress 322 | msg["To"] = self.gmailAddress 323 | msg["Date"] = email.Utils.formatdate() 324 | msg["Subject"] = "TomorrowPricePrediction( " + self.currentPair + " )" 325 | msg.attach(email.MIMEText.MIMEText(body)) 326 | # ---AttachimentFile 327 | attachimentFiles = [] 328 | if os.path.exists(self.workingDirPath + "/backTest_" + self.currentPair + ".png"): 329 | attachimentFiles.append(self.workingDirPath + "/backTest_" + self.currentPair + ".png") 330 | if os.path.exists(self.workingDirPath + "/backTestOptResult_" + self.currentPair + ".png"): 331 | attachimentFiles.append(self.workingDirPath + "/backTestOptResult_" + self.currentPair + ".png") 332 | for afn in attachimentFiles: 333 | img = open(afn, "rb").read() 334 | mimg = email.MIMEImage.MIMEImage(img, "png", filename=afn) 335 | msg.attach(mimg) 336 | # ---SendMail 337 | smtpobj = smtplib.SMTP("smtp.gmail.com", 587) 338 | smtpobj.ehlo() 339 | smtpobj.starttls() 340 | smtpobj.login(self.gmailAddress, self.gmailAddressPassword) 341 | smtpobj.sendmail(self.gmailAddress, self.gmailAddress, msg.as_string()) 342 | smtpobj.close() 343 | -------------------------------------------------------------------------------- /examples/exchangetrade/getexchangebalance.py: -------------------------------------------------------------------------------- 1 | # Poloniex API wrapper tested on Python 2.7.6 & 3.4.3 2 | # https://github.com/s4w3d0ff/python-poloniex 3 | # BTC: 15D8VaZco22GTLVrFMAehXyif6EGf8GMYV 4 | # TODO: 5 | # [x] PEP8 6 | # [ ] Add better logger access 7 | # [ ] Find out if request module has the equivalent to urlencode 8 | # [ ] Add Push Api application wrapper 9 | # [ ] Convert docstrings to sphinx 10 | # 11 | # Copyright (C) 2016 https://github.com/s4w3d0ff 12 | # 13 | # This program is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU General Public License as published by 15 | # the Free Software Foundation; either version 2 of the License, or 16 | # (at your option) any later version. 17 | # 18 | # This program is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License along 24 | # with this program; if not, write to the Free Software Foundation, Inc., 25 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 26 | from json import loads as _loads 27 | from hmac import new as _new 28 | from hashlib import sha512 as _sha512 29 | 30 | import logging 31 | from time import sleep, time, gmtime, strftime, strptime, localtime, mktime 32 | from calendar import timegm 33 | import datetime 34 | 35 | # pip 36 | from requests import post as _post 37 | # # local 38 | # from .coach import ( 39 | # Coach, epoch2UTCstr, epoch2localstr, 40 | # UTCstr2epoch, localstr2epoch, float2roundPercent, 41 | # time, logging 42 | # ) 43 | # python 3 voodoo 44 | try: 45 | from urllib.parse import urlencode as _urlencode 46 | except ImportError: 47 | from urllib import urlencode as _urlencode 48 | 49 | def main(): 50 | myAPIKey = "************************" 51 | mySecret = "************************************************" 52 | 53 | polo = Poloniex(Key=myAPIKey, Secret=mySecret, timeout=10, coach=True, loglevel=logging.WARNING, extend=True) 54 | balanceAll = polo.myCompleteBalances(account="exchange") 55 | 56 | myBTC = 0 57 | for i in range(len(balanceAll.items())): 58 | myBTC += float(balanceAll.items()[i][1]["btcValue"]) 59 | 60 | tmp = polo.marketTicker() 61 | lastValueUSDT_BTC = 0 62 | for i, currentPair in enumerate(tmp.keys()): 63 | if currentPair == "USDT_BTC": 64 | lastValueUSDT_BTC = float(tmp.values()[i]["last"]) 65 | break 66 | myUSD = lastValueUSDT_BTC * myBTC 67 | 68 | print("-"*35) 69 | print("Your total fund in exchange account:") 70 | print(str(myBTC) + " BTC") 71 | print(str(myUSD) + " USD") 72 | print("\nBreakdown:") 73 | print(" available btcValue onOrders") 74 | for i in range(len(balanceAll.items())): 75 | if balanceAll.items()[i][1]["btcValue"] != "0.00000000": 76 | tmpStr = "" 77 | tmpStr += balanceAll.keys()[i] + " " 78 | tmpStr += balanceAll.values()[i]["available"] + " " 79 | tmpStr += balanceAll.values()[i]["btcValue"] + " " 80 | tmpStr += balanceAll.values()[i]["onOrders"] 81 | print(tmpStr) 82 | 83 | 84 | # Possible Commands 85 | PUBLIC_COMMANDS = [ 86 | 'returnTicker', 87 | 'return24hVolume', 88 | 'returnOrderBook', 89 | 'returnTradeHistory', 90 | 'returnChartData', 91 | 'returnCurrencies', 92 | 'returnLoanOrders'] 93 | 94 | PRIVATE_COMMANDS = [ 95 | 'returnBalances', 96 | 'returnCompleteBalances', 97 | 'returnDepositAddresses', 98 | 'generateNewAddress', 99 | 'returnDepositsWithdrawals', 100 | 'returnOpenOrders', 101 | 'returnTradeHistory', 102 | 'returnAvailableAccountBalances', 103 | 'returnTradableBalances', 104 | 'returnOpenLoanOffers', 105 | 'returnOrderTrades', 106 | 'returnActiveLoans', 107 | 'returnLendingHistory', 108 | 'createLoanOffer', 109 | 'cancelLoanOffer', 110 | 'toggleAutoRenew', 111 | 'buy', 112 | 'sell', 113 | 'cancelOrder', 114 | 'moveOrder', 115 | 'withdraw', 116 | 'returnFeeInfo', 117 | 'transferBalance', 118 | 'returnMarginAccountSummary', 119 | 'marginBuy', 120 | 'marginSell', 121 | 'getMarginPosition', 122 | 'closeMarginPosition'] 123 | 124 | 125 | class Poloniex(object): 126 | """The Poloniex Object!""" 127 | def __init__( 128 | self, Key=False, Secret=False, 129 | timeout=3, coach=False, loglevel=logging.WARNING, extend=False): 130 | """ 131 | APIKey = str api key supplied by Poloniex 132 | Secret = str secret hash supplied by Poloniex 133 | timeout = int time in sec to wait for an api response 134 | (otherwise 'requests.exceptions.Timeout' is raised) 135 | coach = bool to indicate if the api coach should be used 136 | loglevel = logging level object to set the module at 137 | (changes the requests module as well) 138 | 139 | self.apiCoach = object that regulates spacing between api calls 140 | 141 | # Time Placeholders # (MONTH == 30*DAYS) 142 | 143 | self.MINUTE, self.HOUR, self.DAY, self.WEEK, self.MONTH, self.YEAR 144 | """ 145 | # Set wrapper logging level 146 | logging.basicConfig( 147 | format='[%(asctime)s] %(message)s', 148 | datefmt="%H:%M:%S", 149 | level=loglevel) 150 | # Suppress the requests module logging output 151 | logging.getLogger("requests").setLevel(loglevel) 152 | logging.getLogger("urllib3").setLevel(loglevel) 153 | # Call coach, set nonce 154 | self.apicoach, self.nonce = Coach(), int(time()*1000) 155 | # Grab keys, set timeout, ditch coach? 156 | self.Key, self.Secret, self.timeout, self._coaching = \ 157 | Key, Secret, timeout, coach 158 | # Set time labels 159 | self.MINUTE, self.HOUR, self.DAY, self.WEEK, self.MONTH, self.YEAR = \ 160 | 60, 60*60, 60*60*24, 60*60*24*7, 60*60*24*30, 60*60*24*365 161 | 162 | # These namespaces are here because poloniex has overlapping 163 | # namespaces. There are 2 "returnTradeHistory" commands, one public and 164 | # one private. Currently if one were to try: polo('returnTradeHistory') 165 | # it would default to the private command and if no api key is defined a 166 | # 'ValueError' will be raise. The workaround is 'marketTradeHist'. It 167 | # returns the public data (bypassing the 'main' api call function). As 168 | # I continued to write this wrapper I found more 'practical' namespaces 169 | # for most of the api commands (at least at the time of writing). So I 170 | # added them here for those who wish to use them. 171 | if extend: 172 | # Public 173 | self.api = self.__call__ 174 | self.marketTicker = self.returnTicker 175 | self.marketVolume = self.return24hVolume 176 | self.marketStatus = self.returnCurrencies 177 | self.marketLoans = self.returnLoanOrders 178 | self.marketOrders = self.returnOrderBook 179 | self.marketChart = self.returnChartData 180 | # Private 181 | self.myTradeHist = self.returnTradeHistory 182 | self.myBalances = self.returnBalances 183 | self.myAvailBalances = self.returnAvailableAccountBalances 184 | self.myMarginAccountSummary = self.returnMarginAccountSummary 185 | self.myMarginPosition = self.getMarginPosition 186 | self.myCompleteBalances = self.returnCompleteBalances 187 | self.myAddresses = self.returnDepositAddresses 188 | self.myOrders = self.returnOpenOrders 189 | self.myDepositsWithdraws = self.returnDepositsWithdrawals 190 | self.myTradeableBalances = self.returnTradableBalances 191 | self.myActiveLoans = self.returnActiveLoans 192 | self.myOpenLoanOrders = self.returnOpenLoanOffers 193 | self.myFeeInfo = self.returnFeeInfo 194 | self.myLendingHistory = self.returnLendingHistory 195 | self.orderTrades = self.returnOrderTrades 196 | self.createLoanOrder = self.createLoanOffer 197 | self.cancelLoanOrder = self.cancelLoanOffer 198 | 199 | # -----------------Meat and Potatos--------------------------------------- 200 | def __call__(self, command, args={}): 201 | """ 202 | Main Api Function 203 | - encodes and sends with optional [args] to Poloniex api 204 | - raises 'ValueError' if an api key or secret is missing 205 | (and the command is 'private'), or if the is not valid 206 | - returns decoded json api message 207 | """ 208 | global PUBLIC_COMMANDS, PRIVATE_COMMANDS 209 | 210 | # check in with the coach 211 | if self._coaching: 212 | self.apicoach.wait() 213 | 214 | # pass the command 215 | args['command'] = command 216 | 217 | # private? 218 | if command in PRIVATE_COMMANDS: 219 | # check for keys 220 | if not self.Key or not self.Secret: 221 | raise ValueError("A Key and Secret needed!") 222 | # set nonce 223 | args['nonce'] = self.nonce 224 | 225 | try: 226 | # encode arguments for url 227 | postData = _urlencode(args) 228 | # sign postData with our Secret 229 | sign = _new( 230 | self.Secret.encode('utf-8'), 231 | postData.encode('utf-8'), 232 | _sha512) 233 | # post request 234 | ret = _post( 235 | 'https://poloniex.com/tradingApi', 236 | data=args, 237 | headers={ 238 | 'Sign': sign.hexdigest(), 239 | 'Key': self.Key 240 | }, 241 | timeout=self.timeout) 242 | except Exception as e: 243 | raise e 244 | finally: 245 | # increment nonce(no matter what) 246 | self.nonce += 1 247 | # return decoded json 248 | try: 249 | return _loads(ret.text, parse_float=unicode) 250 | except NameError: 251 | return _loads(ret.text, parse_float=str) 252 | 253 | # public? 254 | elif command in PUBLIC_COMMANDS: 255 | try: 256 | ret = _post( 257 | 'https://poloniex.com/public?' + _urlencode(args), 258 | timeout=self.timeout) 259 | except Exception as e: 260 | raise e 261 | try: 262 | return _loads(ret.text, parse_float=unicode) 263 | except NameError: 264 | return _loads(ret.text, parse_float=str) 265 | else: 266 | raise ValueError("Invalid Command!") 267 | 268 | # --PUBLIC COMMANDS------------------------------------------------------- 269 | def returnTicker(self, market=False): 270 | """ Returns the ticker for all markets """ 271 | if market: 272 | return self.__call__('returnTicker')[market.upper()] 273 | return self.__call__('returnTicker') 274 | 275 | def return24hVolume(self): 276 | """ Returns the volume data for all markets """ 277 | return self.__call__('return24hVolume') 278 | 279 | def returnCurrencies(self): 280 | """ Returns additional market info for all markets """ 281 | return self.__call__('returnCurrencies') 282 | 283 | def returnLoanOrders(self, coin): 284 | """ Returns loan order book for """ 285 | return self.__call__('returnLoanOrders', {'currency': str(coin).upper()}) 286 | 287 | def returnOrderBook(self, pair='all', depth=20): 288 | """ 289 | Returns orderbook for [pair='all'] 290 | at a depth of [depth=20] orders 291 | """ 292 | return self.__call__('returnOrderBook', { 293 | 'currencyPair': str(pair).upper(), 294 | 'depth': str(depth) 295 | }) 296 | 297 | def returnChartData(self, pair, period=False, start=False, end=time()): 298 | """ 299 | Returns chart data for with a candle period of 300 | [period=self.DAY] starting from [start=time()-self.YEAR] 301 | and ending at [end=time()] 302 | """ 303 | if not period: 304 | period = self.DAY 305 | if not start: 306 | start = time()-(self.MONTH*2) 307 | return self.__call__('returnChartData', { 308 | 'currencyPair': str(pair).upper(), 309 | 'period': str(period), 310 | 'start': str(start), 311 | 'end': str(end) 312 | }) 313 | 314 | def marketTradeHist(self, pair, start=False, end=time()): 315 | """ 316 | Returns public trade history for 317 | starting at and ending at [end=time()] 318 | """ 319 | if self._coaching: 320 | self.apicoach.wait() 321 | if not start: 322 | start = time()-self.HOUR 323 | try: 324 | ret = _post( 325 | 'https://poloniex.com/public?'+_urlencode({ 326 | 'command': 'returnTradeHistory', 327 | 'currencyPair': str(pair).upper(), 328 | 'start': str(start), 329 | 'end': str(end) 330 | }), 331 | timeout=self.timeout) 332 | except Exception as e: 333 | raise e 334 | try: 335 | return _loads(ret.text, parse_float=unicode) 336 | except NameError: 337 | return _loads(ret.text, parse_float=str) 338 | 339 | # --PRIVATE COMMANDS------------------------------------------------------ 340 | def returnTradeHistory(self, pair): 341 | """ Returns private trade history for """ 342 | return self.__call__('returnTradeHistory', {'currencyPair': str(pair).upper()}) 343 | 344 | def returnBalances(self): 345 | """ Returns coin balances """ 346 | return self.__call__('returnBalances') 347 | 348 | def returnAvailableAccountBalances(self): 349 | """ Returns available account balances """ 350 | return self.__call__('returnAvailableAccountBalances') 351 | 352 | def returnMarginAccountSummary(self): 353 | """ Returns margin account summary """ 354 | return self.__call__('returnMarginAccountSummary') 355 | 356 | def getMarginPosition(self, pair='all'): 357 | """ Returns margin position for [pair='all'] """ 358 | return self.__call__('getMarginPosition', {'currencyPair': str(pair).upper()}) 359 | 360 | def returnCompleteBalances(self, account='all'): 361 | """ Returns complete balances """ 362 | return self.__call__('returnCompleteBalances', {'account': str(account)}) 363 | 364 | def returnDepositAddresses(self): 365 | """ Returns deposit addresses """ 366 | return self.__call__('returnDepositAddresses') 367 | 368 | def returnOpenOrders(self, pair='all'): 369 | """ Returns your open orders for [pair='all'] """ 370 | return self.__call__('returnOpenOrders', {'currencyPair': str(pair).upper()}) 371 | 372 | def returnDepositsWithdrawals(self): 373 | """ Returns deposit/withdraw history """ 374 | return self.__call__('returnDepositsWithdrawals') 375 | 376 | def returnTradableBalances(self): 377 | """ Returns tradable balances """ 378 | return self.__call__('returnTradableBalances') 379 | 380 | def returnActiveLoans(self): 381 | """ Returns active loans """ 382 | return self.__call__('returnActiveLoans') 383 | 384 | def returnOpenLoanOffers(self): 385 | """ Returns open loan offers """ 386 | return self.__call__('returnOpenLoanOffers') 387 | 388 | def returnFeeInfo(self): 389 | """ Returns current trading fees and trailing 30-day volume in BTC """ 390 | return self.__call__('returnFeeInfo') 391 | 392 | def returnLendingHistory(self, start=False, end=time(), limit=False): 393 | if not start: 394 | start = time()-self.MONTH 395 | args = {'start': str(start), 'end': str(end)} 396 | if limit: 397 | args['limit'] = str(limit) 398 | return self.__call__('returnLendingHistory', args) 399 | 400 | def returnOrderTrades(self, orderId): 401 | """ Returns any trades made from """ 402 | return self.__call__('returnOrderTrades', {'orderNumber': str(orderId)}) 403 | 404 | def createLoanOffer(self, coin, amount, rate, autoRenew=0, duration=2): 405 | """ Creates a loan offer for for at """ 406 | return self.__call__('createLoanOffer', { 407 | 'currency': str(coin).upper(), 408 | 'amount': str(amount), 409 | 'duration': str(duration), 410 | 'autoRenew': str(autoRenew), 411 | 'lendingRate': str(rate) 412 | }) 413 | 414 | def cancelLoanOffer(self, orderId): 415 | """ Cancels the loan offer with """ 416 | return self.__call__('cancelLoanOffer', {'orderNumber': str(orderId)}) 417 | 418 | def toggleAutoRenew(self, orderId): 419 | """ Toggles the 'autorenew' feature on loan """ 420 | return self.__call__('toggleAutoRenew', {'orderNumber': str(orderId)}) 421 | 422 | def closeMarginPosition(self, pair): 423 | """ Closes the margin position on """ 424 | return self.__call__('closeMarginPosition', {'currencyPair': str(pair).upper()}) 425 | 426 | def marginBuy(self, pair, rate, amount, lendingRate=2): 427 | """ Creates margin buy order at for """ 428 | return self.__call__('marginBuy', { 429 | 'currencyPair': str(pair).upper(), 430 | 'rate': str(rate), 431 | 'amount': str(amount), 432 | 'lendingRate': str(lendingRate) 433 | }) 434 | 435 | def marginSell(self, pair, rate, amount, lendingRate=2): 436 | """ Creates margin sell order at for """ 437 | return self.__call__('marginSell', { 438 | 'currencyPair': str(pair).upper(), 439 | 'rate': str(rate), 440 | 'amount': str(amount), 441 | 'lendingRate': str(lendingRate) 442 | }) 443 | 444 | def buy(self, pair, rate, amount): 445 | """ Creates buy order for at for """ 446 | return self.__call__('buy', { 447 | 'currencyPair': str(pair).upper(), 448 | 'rate': str(rate), 449 | 'amount': str(amount) 450 | }) 451 | 452 | def sell(self, pair, rate, amount): 453 | """ Creates sell order for at for """ 454 | return self.__call__('sell', { 455 | 'currencyPair': str(pair).upper(), 456 | 'rate': str(rate), 457 | 'amount': str(amount) 458 | }) 459 | 460 | def cancelOrder(self, orderId): 461 | """ Cancels order """ 462 | return self.__call__('cancelOrder', {'orderNumber': str(orderId)}) 463 | 464 | def moveOrder(self, orderId, rate, amount): 465 | """ Moves an order by to for """ 466 | return self.__call__('moveOrder', { 467 | 'orderNumber': str(orderId), 468 | 'rate': str(rate), 469 | 'amount': str(amount) 470 | }) 471 | 472 | def withdraw(self, coin, amount, address): 473 | """ Withdraws to
""" 474 | return self.__call__('withdraw', { 475 | 'currency': str(coin).upper(), 476 | 'amount': str(amount), 477 | 'address': str(address) 478 | }) 479 | 480 | def transferBalance(self, coin, amount, fromac, toac): 481 | """ 482 | Transfers coins between accounts (exchange, margin, lending) 483 | - moves from to 484 | """ 485 | return self.__call__('transferBalance', { 486 | 'currency': str(coin).upper(), 487 | 'amount': str(amount), 488 | 'fromAccount': str(fromac), 489 | 'toAccount': str(toac) 490 | }) 491 | 492 | # coach.py 493 | # Copyright (C) 2016 https://github.com/s4w3d0ff 494 | # 495 | # This program is free software; you can redistribute it and/or modify 496 | # it under the terms of the GNU General Public License as published by 497 | # the Free Software Foundation; either version 2 of the License, or 498 | # (at your option) any later version. 499 | # 500 | # This program is distributed in the hope that it will be useful, 501 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 502 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 503 | # GNU General Public License for more details. 504 | # 505 | # You should have received a copy of the GNU General Public License along 506 | # with this program; if not, write to the Free Software Foundation, Inc., 507 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 508 | 509 | # import logging 510 | # from time import sleep, time, gmtime, strftime, strptime, localtime, mktime 511 | # from calendar import timegm 512 | 513 | # Convertions 514 | def epoch2UTCstr(timestamp=time(), fmat="%Y-%m-%d %H:%M:%S"): 515 | """ 516 | - takes epoch timestamp 517 | - returns UTC formated string 518 | """ 519 | return strftime(fmat, gmtime(timestamp)) 520 | 521 | def UTCstr2epoch(datestr=epoch2UTCstr(), fmat="%Y-%m-%d %H:%M:%S"): 522 | """ 523 | - takes UTC date string 524 | - returns epoch 525 | """ 526 | return timegm(strptime(datestr, fmat)) 527 | 528 | def epoch2localstr(timestamp=time(), fmat="%Y-%m-%d %H:%M:%S"): 529 | """ 530 | - takes epoch timestamp 531 | - returns localtimezone formated string 532 | """ 533 | return strftime(fmat, localtime(timestamp)) 534 | 535 | def localstr2epoch(datestr=epoch2UTCstr(), fmat="%Y-%m-%d %H:%M:%S"): 536 | """ 537 | - takes localtimezone date string, 538 | - returns epoch 539 | """ 540 | return mktime(strptime(datestr, fmat)) 541 | 542 | def float2roundPercent(floatN, decimalP=2): 543 | """ 544 | - takes float 545 | - returns percent(*100) rounded to the Nth decimal place as a string 546 | """ 547 | return str(round(float(floatN)*100, decimalP))+"%" 548 | 549 | # Coach 550 | class Coach(object): 551 | """ 552 | Coaches the api wrapper, makes sure it doesn't get all hyped up on Mt.Dew 553 | Poloniex default call limit is 6 calls per 1 sec. 554 | """ 555 | def __init__(self, timeFrame=1.0, callLimit=6): 556 | """ 557 | timeFrame = float time in secs [default = 1.0] 558 | callLimit = int max amount of calls per 'timeFrame' [default = 6] 559 | """ 560 | self._timeFrame, self._callLimit = timeFrame, callLimit 561 | self._timeBook = [] 562 | 563 | def wait(self): 564 | """ Makes sure our api calls don't go past the api call limit """ 565 | # what time is it? 566 | now = time() 567 | # if it's our turn 568 | if len(self._timeBook) is 0 or \ 569 | (now - self._timeBook[-1]) >= self._timeFrame: 570 | # add 'now' to the front of 'timeBook', pushing other times back 571 | self._timeBook.insert(0, now) 572 | logging.info( 573 | "Now: %d Oldest Call: %d Diff: %f sec" % 574 | (now, self._timeBook[-1], now - self._timeBook[-1]) 575 | ) 576 | # 'timeBook' list is longer than 'callLimit'? 577 | if len(self._timeBook) > self._callLimit: 578 | # remove the oldest time 579 | self._timeBook.pop() 580 | else: 581 | logging.info( 582 | "Now: %d Oldest Call: %d Diff: %f sec" % 583 | (now, self._timeBook[-1], now - self._timeBook[-1]) 584 | ) 585 | logging.info( 586 | "Waiting %s sec..." % 587 | str(self._timeFrame-(now - self._timeBook[-1])) 588 | ) 589 | # wait your turn (maxTime - (now - oldest)) = time left to wait 590 | sleep(self._timeFrame-(now - self._timeBook[-1])) 591 | # add 'now' to the front of 'timeBook', pushing other times back 592 | self._timeBook.insert(0, time()) 593 | # 'timeBook' list is longer than 'callLimit'? 594 | if len(self._timeBook) > self._callLimit: 595 | # remove the oldest time 596 | self._timeBook.pop() 597 | 598 | if __name__ == "__main__": 599 | main() 600 | 601 | -------------------------------------------------------------------------------- /examples/margintrade/getmarginbalance.py: -------------------------------------------------------------------------------- 1 | # Poloniex API wrapper tested on Python 2.7.6 & 3.4.3 2 | # https://github.com/s4w3d0ff/python-poloniex 3 | # BTC: 15D8VaZco22GTLVrFMAehXyif6EGf8GMYV 4 | # TODO: 5 | # [x] PEP8 6 | # [ ] Add better logger access 7 | # [ ] Find out if request module has the equivalent to urlencode 8 | # [ ] Add Push Api application wrapper 9 | # [ ] Convert docstrings to sphinx 10 | # 11 | # Copyright (C) 2016 https://github.com/s4w3d0ff 12 | # 13 | # This program is free software; you can redistribute it and/or modify 14 | # it under the terms of the GNU General Public License as published by 15 | # the Free Software Foundation; either version 2 of the License, or 16 | # (at your option) any later version. 17 | # 18 | # This program is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License along 24 | # with this program; if not, write to the Free Software Foundation, Inc., 25 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 26 | from json import loads as _loads 27 | from hmac import new as _new 28 | from hashlib import sha512 as _sha512 29 | 30 | import logging 31 | from time import sleep, time, gmtime, strftime, strptime, localtime, mktime 32 | from calendar import timegm 33 | import datetime 34 | 35 | # pip 36 | from requests import post as _post 37 | # # local 38 | # from .coach import ( 39 | # Coach, epoch2UTCstr, epoch2localstr, 40 | # UTCstr2epoch, localstr2epoch, float2roundPercent, 41 | # time, logging 42 | # ) 43 | # python 3 voodoo 44 | try: 45 | from urllib.parse import urlencode as _urlencode 46 | except ImportError: 47 | from urllib import urlencode as _urlencode 48 | 49 | def main(): 50 | myAPIKey = "************************" 51 | mySecret = "************************************************" 52 | 53 | polo = Poloniex(Key=myAPIKey, Secret=mySecret, timeout=10, coach=True, loglevel=logging.WARNING, extend=False) 54 | print(polo.getSummary()) 55 | 56 | # Possible Commands 57 | PUBLIC_COMMANDS = [ 58 | 'returnTicker', 59 | 'return24hVolume', 60 | 'returnOrderBook', 61 | 'returnTradeHistory', 62 | 'returnChartData', 63 | 'returnCurrencies', 64 | 'returnLoanOrders'] 65 | 66 | PRIVATE_COMMANDS = [ 67 | 'returnBalances', 68 | 'returnCompleteBalances', 69 | 'returnDepositAddresses', 70 | 'generateNewAddress', 71 | 'returnDepositsWithdrawals', 72 | 'returnOpenOrders', 73 | 'returnTradeHistory', 74 | 'returnAvailableAccountBalances', 75 | 'returnTradableBalances', 76 | 'returnOpenLoanOffers', 77 | 'returnOrderTrades', 78 | 'returnActiveLoans', 79 | 'returnLendingHistory', 80 | 'createLoanOffer', 81 | 'cancelLoanOffer', 82 | 'toggleAutoRenew', 83 | 'buy', 84 | 'sell', 85 | 'cancelOrder', 86 | 'moveOrder', 87 | 'withdraw', 88 | 'returnFeeInfo', 89 | 'transferBalance', 90 | 'returnMarginAccountSummary', 91 | 'marginBuy', 92 | 'marginSell', 93 | 'getMarginPosition', 94 | 'closeMarginPosition'] 95 | 96 | 97 | class Poloniex(object): 98 | """The Poloniex Object!""" 99 | def __init__( 100 | self, Key=False, Secret=False, 101 | timeout=3, coach=False, loglevel=logging.WARNING, extend=False): 102 | """ 103 | APIKey = str api key supplied by Poloniex 104 | Secret = str secret hash supplied by Poloniex 105 | timeout = int time in sec to wait for an api response 106 | (otherwise 'requests.exceptions.Timeout' is raised) 107 | coach = bool to indicate if the api coach should be used 108 | loglevel = logging level object to set the module at 109 | (changes the requests module as well) 110 | 111 | self.apiCoach = object that regulates spacing between api calls 112 | 113 | # Time Placeholders # (MONTH == 30*DAYS) 114 | 115 | self.MINUTE, self.HOUR, self.DAY, self.WEEK, self.MONTH, self.YEAR 116 | """ 117 | # Set wrapper logging level 118 | logging.basicConfig( 119 | format='[%(asctime)s] %(message)s', 120 | datefmt="%H:%M:%S", 121 | level=loglevel) 122 | # Suppress the requests module logging output 123 | logging.getLogger("requests").setLevel(loglevel) 124 | logging.getLogger("urllib3").setLevel(loglevel) 125 | # Call coach, set nonce 126 | self.apicoach, self.nonce = Coach(), int(time()*1000) 127 | # Grab keys, set timeout, ditch coach? 128 | self.Key, self.Secret, self.timeout, self._coaching = \ 129 | Key, Secret, timeout, coach 130 | # Set time labels 131 | self.MINUTE, self.HOUR, self.DAY, self.WEEK, self.MONTH, self.YEAR = \ 132 | 60, 60*60, 60*60*24, 60*60*24*7, 60*60*24*30, 60*60*24*365 133 | 134 | # These namespaces are here because poloniex has overlapping 135 | # namespaces. There are 2 "returnTradeHistory" commands, one public and 136 | # one private. Currently if one were to try: polo('returnTradeHistory') 137 | # it would default to the private command and if no api key is defined a 138 | # 'ValueError' will be raise. The workaround is 'marketTradeHist'. It 139 | # returns the public data (bypassing the 'main' api call function). As 140 | # I continued to write this wrapper I found more 'practical' namespaces 141 | # for most of the api commands (at least at the time of writing). So I 142 | # added them here for those who wish to use them. 143 | if extend: 144 | # Public 145 | self.api = self.__call__ 146 | self.marketTicker = self.returnTicker 147 | self.marketVolume = self.return24hVolume 148 | self.marketStatus = self.returnCurrencies 149 | self.marketLoans = self.returnLoanOrders 150 | self.marketOrders = self.returnOrderBook 151 | self.marketChart = self.returnChartData 152 | # Private 153 | self.myTradeHist = self.returnTradeHistory 154 | self.myBalances = self.returnBalances 155 | self.myAvailBalances = self.returnAvailableAccountBalances 156 | self.myMarginAccountSummary = self.returnMarginAccountSummary 157 | self.myMarginPosition = self.getMarginPosition 158 | self.myCompleteBalances = self.returnCompleteBalances 159 | self.myAddresses = self.returnDepositAddresses 160 | self.myOrders = self.returnOpenOrders 161 | self.myDepositsWithdraws = self.returnDepositsWithdrawals 162 | self.myTradeableBalances = self.returnTradableBalances 163 | self.myActiveLoans = self.returnActiveLoans 164 | self.myOpenLoanOrders = self.returnOpenLoanOffers 165 | self.myFeeInfo = self.returnFeeInfo 166 | self.myLendingHistory = self.returnLendingHistory 167 | self.orderTrades = self.returnOrderTrades 168 | self.createLoanOrder = self.createLoanOffer 169 | self.cancelLoanOrder = self.cancelLoanOffer 170 | 171 | # ---- additional functions 172 | def returnSummary(self): 173 | summary = self.returnMarginAccountSummary() 174 | tmpStr = "" 175 | for k, v in summary.items(): 176 | tmpStr += k + " " + v + "\n" 177 | return tmpStr 178 | 179 | def getOpenMarginPosition(self): 180 | position = self.getMarginPosition() 181 | tmpStr = "" 182 | for k, v in position.items(): 183 | if v["amount"] != "0.00000000": 184 | tmpStr += k + " " + str(v) + "\n" 185 | if tmpStr == "": 186 | return "Nothing" 187 | else: 188 | return tmpStr 189 | 190 | def returnEstimatedValueOfHoldings(self): 191 | """Return EstimatedValueOfHoldings.""" 192 | summary = self.returnMarginAccountSummary() 193 | estimatedValueOfHoldingsAsBTC = float(summary["netValue"]) 194 | lastValueUSDT_BTC = self.returnTicker()["USDT_BTC"]["last"] 195 | estimatedValueOfHoldingsAsUSD = float(lastValueUSDT_BTC) * estimatedValueOfHoldingsAsBTC 196 | return estimatedValueOfHoldingsAsBTC, estimatedValueOfHoldingsAsUSD 197 | 198 | def returnOpenMarginOrders(self): 199 | onOrders = self.returnOpenOrders(pair="all") 200 | tmpStr = "" 201 | for k, v in onOrders.items(): 202 | if v!=[]: 203 | if v[0]["margin"]==1: 204 | tmpStr += k + " " + str(v[0]) + "\n" 205 | if tmpStr == "": 206 | return "Nothing" 207 | else: 208 | return tmpStr 209 | 210 | def getSummary(self): 211 | myBTC, myUSD = self.returnEstimatedValueOfHoldings() 212 | summary = self.returnSummary() 213 | positions = self.getOpenMarginPosition() 214 | onMarginOrders = self.returnOpenMarginOrders() 215 | 216 | summaryStr = "" 217 | summaryStr += "-"*35 + "\n" 218 | summaryStr += "Time: " + str(datetime.datetime.today())[0:19] + "\n" 219 | summaryStr += "\n" 220 | summaryStr += "Your total fund in margin account:\n" 221 | summaryStr += str(myBTC) + " BTC\n" 222 | summaryStr += str(myUSD) + " USD\n" 223 | summaryStr += "\n" 224 | summaryStr += "Summary:\n" 225 | summaryStr += str(summary) 226 | summaryStr += "\n" 227 | # summaryStr += "\n" 228 | summaryStr += "Breakdown:\n" 229 | summaryStr += str(positions) 230 | summaryStr += "\n" 231 | summaryStr += "\n" 232 | summaryStr += "On order:\n" 233 | summaryStr += str(onMarginOrders) 234 | return summaryStr 235 | 236 | # -----------------Meat and Potatos--------------------------------------- 237 | def __call__(self, command, args={}): 238 | """ 239 | Main Api Function 240 | - encodes and sends with optional [args] to Poloniex api 241 | - raises 'ValueError' if an api key or secret is missing 242 | (and the command is 'private'), or if the is not valid 243 | - returns decoded json api message 244 | """ 245 | global PUBLIC_COMMANDS, PRIVATE_COMMANDS 246 | 247 | # check in with the coach 248 | if self._coaching: 249 | self.apicoach.wait() 250 | 251 | # pass the command 252 | args['command'] = command 253 | 254 | # private? 255 | if command in PRIVATE_COMMANDS: 256 | # check for keys 257 | if not self.Key or not self.Secret: 258 | raise ValueError("A Key and Secret needed!") 259 | # set nonce 260 | args['nonce'] = self.nonce 261 | 262 | try: 263 | # encode arguments for url 264 | postData = _urlencode(args) 265 | # sign postData with our Secret 266 | sign = _new( 267 | self.Secret.encode('utf-8'), 268 | postData.encode('utf-8'), 269 | _sha512) 270 | # post request 271 | ret = _post( 272 | 'https://poloniex.com/tradingApi', 273 | data=args, 274 | headers={ 275 | 'Sign': sign.hexdigest(), 276 | 'Key': self.Key 277 | }, 278 | timeout=self.timeout) 279 | except Exception as e: 280 | raise e 281 | finally: 282 | # increment nonce(no matter what) 283 | self.nonce += 1 284 | # return decoded json 285 | try: 286 | return _loads(ret.text, parse_float=unicode) 287 | except NameError: 288 | return _loads(ret.text, parse_float=str) 289 | 290 | # public? 291 | elif command in PUBLIC_COMMANDS: 292 | try: 293 | ret = _post( 294 | 'https://poloniex.com/public?' + _urlencode(args), 295 | timeout=self.timeout) 296 | except Exception as e: 297 | raise e 298 | try: 299 | return _loads(ret.text, parse_float=unicode) 300 | except NameError: 301 | return _loads(ret.text, parse_float=str) 302 | else: 303 | raise ValueError("Invalid Command!") 304 | 305 | # --PUBLIC COMMANDS------------------------------------------------------- 306 | def returnTicker(self, market=False): 307 | """ Returns the ticker for all markets """ 308 | if market: 309 | return self.__call__('returnTicker')[market.upper()] 310 | return self.__call__('returnTicker') 311 | 312 | def return24hVolume(self): 313 | """ Returns the volume data for all markets """ 314 | return self.__call__('return24hVolume') 315 | 316 | def returnCurrencies(self): 317 | """ Returns additional market info for all markets """ 318 | return self.__call__('returnCurrencies') 319 | 320 | def returnLoanOrders(self, coin): 321 | """ Returns loan order book for """ 322 | return self.__call__('returnLoanOrders', {'currency': str(coin).upper()}) 323 | 324 | def returnOrderBook(self, pair='all', depth=20): 325 | """ 326 | Returns orderbook for [pair='all'] 327 | at a depth of [depth=20] orders 328 | """ 329 | return self.__call__('returnOrderBook', { 330 | 'currencyPair': str(pair).upper(), 331 | 'depth': str(depth) 332 | }) 333 | 334 | def returnChartData(self, pair, period=False, start=False, end=time()): 335 | """ 336 | Returns chart data for with a candle period of 337 | [period=self.DAY] starting from [start=time()-self.YEAR] 338 | and ending at [end=time()] 339 | """ 340 | if not period: 341 | period = self.DAY 342 | if not start: 343 | start = time()-(self.MONTH*2) 344 | return self.__call__('returnChartData', { 345 | 'currencyPair': str(pair).upper(), 346 | 'period': str(period), 347 | 'start': str(start), 348 | 'end': str(end) 349 | }) 350 | 351 | def marketTradeHist(self, pair, start=False, end=time()): 352 | """ 353 | Returns public trade history for 354 | starting at and ending at [end=time()] 355 | """ 356 | if self._coaching: 357 | self.apicoach.wait() 358 | if not start: 359 | start = time()-self.HOUR 360 | try: 361 | ret = _post( 362 | 'https://poloniex.com/public?'+_urlencode({ 363 | 'command': 'returnTradeHistory', 364 | 'currencyPair': str(pair).upper(), 365 | 'start': str(start), 366 | 'end': str(end) 367 | }), 368 | timeout=self.timeout) 369 | except Exception as e: 370 | raise e 371 | try: 372 | return _loads(ret.text, parse_float=unicode) 373 | except NameError: 374 | return _loads(ret.text, parse_float=str) 375 | 376 | # --PRIVATE COMMANDS------------------------------------------------------ 377 | def returnTradeHistory(self, pair): 378 | """ Returns private trade history for """ 379 | return self.__call__('returnTradeHistory', {'currencyPair': str(pair).upper()}) 380 | 381 | def returnBalances(self): 382 | """ Returns coin balances """ 383 | return self.__call__('returnBalances') 384 | 385 | def returnAvailableAccountBalances(self): 386 | """ Returns available account balances """ 387 | return self.__call__('returnAvailableAccountBalances') 388 | 389 | def returnMarginAccountSummary(self): 390 | """ Returns margin account summary """ 391 | return self.__call__('returnMarginAccountSummary') 392 | 393 | def getMarginPosition(self, pair='all'): 394 | """ Returns margin position for [pair='all'] """ 395 | return self.__call__('getMarginPosition', {'currencyPair': str(pair).upper()}) 396 | 397 | def returnCompleteBalances(self, account='all'): 398 | """ Returns complete balances """ 399 | return self.__call__('returnCompleteBalances', {'account': str(account)}) 400 | 401 | def returnDepositAddresses(self): 402 | """ Returns deposit addresses """ 403 | return self.__call__('returnDepositAddresses') 404 | 405 | def returnOpenOrders(self, pair='all'): 406 | """ Returns your open orders for [pair='all'] """ 407 | return self.__call__('returnOpenOrders', {'currencyPair': str(pair).upper()}) 408 | 409 | def returnDepositsWithdrawals(self): 410 | """ Returns deposit/withdraw history """ 411 | return self.__call__('returnDepositsWithdrawals') 412 | 413 | def returnTradableBalances(self): 414 | """ Returns tradable balances """ 415 | return self.__call__('returnTradableBalances') 416 | 417 | def returnActiveLoans(self): 418 | """ Returns active loans """ 419 | return self.__call__('returnActiveLoans') 420 | 421 | def returnOpenLoanOffers(self): 422 | """ Returns open loan offers """ 423 | return self.__call__('returnOpenLoanOffers') 424 | 425 | def returnFeeInfo(self): 426 | """ Returns current trading fees and trailing 30-day volume in BTC """ 427 | return self.__call__('returnFeeInfo') 428 | 429 | def returnLendingHistory(self, start=False, end=time(), limit=False): 430 | if not start: 431 | start = time()-self.MONTH 432 | args = {'start': str(start), 'end': str(end)} 433 | if limit: 434 | args['limit'] = str(limit) 435 | return self.__call__('returnLendingHistory', args) 436 | 437 | def returnOrderTrades(self, orderId): 438 | """ Returns any trades made from """ 439 | return self.__call__('returnOrderTrades', {'orderNumber': str(orderId)}) 440 | 441 | def createLoanOffer(self, coin, amount, rate, autoRenew=0, duration=2): 442 | """ Creates a loan offer for for at """ 443 | return self.__call__('createLoanOffer', { 444 | 'currency': str(coin).upper(), 445 | 'amount': str(amount), 446 | 'duration': str(duration), 447 | 'autoRenew': str(autoRenew), 448 | 'lendingRate': str(rate) 449 | }) 450 | 451 | def cancelLoanOffer(self, orderId): 452 | """ Cancels the loan offer with """ 453 | return self.__call__('cancelLoanOffer', {'orderNumber': str(orderId)}) 454 | 455 | def toggleAutoRenew(self, orderId): 456 | """ Toggles the 'autorenew' feature on loan """ 457 | return self.__call__('toggleAutoRenew', {'orderNumber': str(orderId)}) 458 | 459 | def closeMarginPosition(self, pair): 460 | """ Closes the margin position on """ 461 | return self.__call__('closeMarginPosition', {'currencyPair': str(pair).upper()}) 462 | 463 | def marginBuy(self, pair, rate, amount, lendingRate=2): 464 | """ Creates margin buy order at for """ 465 | return self.__call__('marginBuy', { 466 | 'currencyPair': str(pair).upper(), 467 | 'rate': str(rate), 468 | 'amount': str(amount), 469 | 'lendingRate': str(lendingRate) 470 | }) 471 | 472 | def marginSell(self, pair, rate, amount, lendingRate=2): 473 | """ Creates margin sell order at for """ 474 | return self.__call__('marginSell', { 475 | 'currencyPair': str(pair).upper(), 476 | 'rate': str(rate), 477 | 'amount': str(amount), 478 | 'lendingRate': str(lendingRate) 479 | }) 480 | 481 | def buy(self, pair, rate, amount): 482 | """ Creates buy order for at for """ 483 | return self.__call__('buy', { 484 | 'currencyPair': str(pair).upper(), 485 | 'rate': str(rate), 486 | 'amount': str(amount) 487 | }) 488 | 489 | def sell(self, pair, rate, amount): 490 | """ Creates sell order for at for """ 491 | return self.__call__('sell', { 492 | 'currencyPair': str(pair).upper(), 493 | 'rate': str(rate), 494 | 'amount': str(amount) 495 | }) 496 | 497 | def cancelOrder(self, orderId): 498 | """ Cancels order """ 499 | return self.__call__('cancelOrder', {'orderNumber': str(orderId)}) 500 | 501 | def moveOrder(self, orderId, rate, amount): 502 | """ Moves an order by to for """ 503 | return self.__call__('moveOrder', { 504 | 'orderNumber': str(orderId), 505 | 'rate': str(rate), 506 | 'amount': str(amount) 507 | }) 508 | 509 | def withdraw(self, coin, amount, address): 510 | """ Withdraws to
""" 511 | return self.__call__('withdraw', { 512 | 'currency': str(coin).upper(), 513 | 'amount': str(amount), 514 | 'address': str(address) 515 | }) 516 | 517 | def transferBalance(self, coin, amount, fromac, toac): 518 | """ 519 | Transfers coins between accounts (exchange, margin, lending) 520 | - moves from to 521 | """ 522 | return self.__call__('transferBalance', { 523 | 'currency': str(coin).upper(), 524 | 'amount': str(amount), 525 | 'fromAccount': str(fromac), 526 | 'toAccount': str(toac) 527 | }) 528 | 529 | # coach.py 530 | # Copyright (C) 2016 https://github.com/s4w3d0ff 531 | # 532 | # This program is free software; you can redistribute it and/or modify 533 | # it under the terms of the GNU General Public License as published by 534 | # the Free Software Foundation; either version 2 of the License, or 535 | # (at your option) any later version. 536 | # 537 | # This program is distributed in the hope that it will be useful, 538 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 539 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 540 | # GNU General Public License for more details. 541 | # 542 | # You should have received a copy of the GNU General Public License along 543 | # with this program; if not, write to the Free Software Foundation, Inc., 544 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 545 | 546 | # import logging 547 | # from time import sleep, time, gmtime, strftime, strptime, localtime, mktime 548 | # from calendar import timegm 549 | 550 | # Convertions 551 | def epoch2UTCstr(timestamp=time(), fmat="%Y-%m-%d %H:%M:%S"): 552 | """ 553 | - takes epoch timestamp 554 | - returns UTC formated string 555 | """ 556 | return strftime(fmat, gmtime(timestamp)) 557 | 558 | def UTCstr2epoch(datestr=epoch2UTCstr(), fmat="%Y-%m-%d %H:%M:%S"): 559 | """ 560 | - takes UTC date string 561 | - returns epoch 562 | """ 563 | return timegm(strptime(datestr, fmat)) 564 | 565 | def epoch2localstr(timestamp=time(), fmat="%Y-%m-%d %H:%M:%S"): 566 | """ 567 | - takes epoch timestamp 568 | - returns localtimezone formated string 569 | """ 570 | return strftime(fmat, localtime(timestamp)) 571 | 572 | def localstr2epoch(datestr=epoch2UTCstr(), fmat="%Y-%m-%d %H:%M:%S"): 573 | """ 574 | - takes localtimezone date string, 575 | - returns epoch 576 | """ 577 | return mktime(strptime(datestr, fmat)) 578 | 579 | def float2roundPercent(floatN, decimalP=2): 580 | """ 581 | - takes float 582 | - returns percent(*100) rounded to the Nth decimal place as a string 583 | """ 584 | return str(round(float(floatN)*100, decimalP))+"%" 585 | 586 | # Coach 587 | class Coach(object): 588 | """ 589 | Coaches the api wrapper, makes sure it doesn't get all hyped up on Mt.Dew 590 | Poloniex default call limit is 6 calls per 1 sec. 591 | """ 592 | def __init__(self, timeFrame=1.0, callLimit=6): 593 | """ 594 | timeFrame = float time in secs [default = 1.0] 595 | callLimit = int max amount of calls per 'timeFrame' [default = 6] 596 | """ 597 | self._timeFrame, self._callLimit = timeFrame, callLimit 598 | self._timeBook = [] 599 | 600 | def wait(self): 601 | """ Makes sure our api calls don't go past the api call limit """ 602 | # what time is it? 603 | now = time() 604 | # if it's our turn 605 | if len(self._timeBook) is 0 or \ 606 | (now - self._timeBook[-1]) >= self._timeFrame: 607 | # add 'now' to the front of 'timeBook', pushing other times back 608 | self._timeBook.insert(0, now) 609 | logging.info( 610 | "Now: %d Oldest Call: %d Diff: %f sec" % 611 | (now, self._timeBook[-1], now - self._timeBook[-1]) 612 | ) 613 | # 'timeBook' list is longer than 'callLimit'? 614 | if len(self._timeBook) > self._callLimit: 615 | # remove the oldest time 616 | self._timeBook.pop() 617 | else: 618 | logging.info( 619 | "Now: %d Oldest Call: %d Diff: %f sec" % 620 | (now, self._timeBook[-1], now - self._timeBook[-1]) 621 | ) 622 | logging.info( 623 | "Waiting %s sec..." % 624 | str(self._timeFrame-(now - self._timeBook[-1])) 625 | ) 626 | # wait your turn (maxTime - (now - oldest)) = time left to wait 627 | sleep(self._timeFrame-(now - self._timeBook[-1])) 628 | # add 'now' to the front of 'timeBook', pushing other times back 629 | self._timeBook.insert(0, time()) 630 | # 'timeBook' list is longer than 'callLimit'? 631 | if len(self._timeBook) > self._callLimit: 632 | # remove the oldest time 633 | self._timeBook.pop() 634 | 635 | if __name__ == "__main__": 636 | main() 637 | 638 | --------------------------------------------------------------------------------