diff --git a/README.md b/README.md index d731c45..0920da6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ #What it does ------------ -Dollar-cost-average bitcoin buying bot for gemini, features: -1. does periodic buys using only maker orders (fees for API maker orders on gemini are 0.1%, as of August 2019) +Dollar-cost-average cryptocurency buying bot for gemini, features: +1. does periodic buys using only maker orders (fees for API maker orders on gemini are 0.1%, as of May 2021) 2. to try to get the best price, it starts at a lower price (configurable) and gradually increases price (at a configurable time frame) by resubmitting and gradually higher price up to the current best bid a. refreshes in relation to the current bid (in case of price going up and trade not executing) @@ -10,7 +10,7 @@ Dollar-cost-average bitcoin buying bot for gemini, features: 3. configurable frequencies and amounts or purchase 4. configurable maximum coin price above which it will not buy 5. adds a random delay to purchase times to help mitigate adversaries predicting exact purchase times -6. there is a hardcoded maximum value spent per day in fiat, currently 500 fiat units per day (e.g. 500 USD), to help mitigate against accidentally spending more than desired on cryptocurrency purchased. This can be changed directly in the code if desired. +6. there is a configurable maximum value spent per day in fiat, set by default to 500 fiat units per day (e.g. 500 USD), to help mitigate against accidentally spending more than desired on cryptocurrency purchased. This can be changed in the configuration file if desired. 7. can do automatic sells of cryptocurrency too NOTE: This program is meant to be run on a computer or server that is generally up most of the time (because the purchase timer resets in the case of a machine reboot), and for security purposes that computer would be dedicated to running this program. @@ -19,12 +19,11 @@ NOTE: This program is meant to be run on a computer or server that is generally #INSTALLATION ------------ -##1. INSTALL PYTHON3 and PIP3 +##1. Install python3 and pip3: ###example debian ``` -apt-get install python3 -apt-get install python3-pip +sudo apt-get install python3 python3-pip ``` ###example centos7 @@ -34,78 +33,67 @@ yum install rh-python36-python scl enable rh-python36 bash ``` -##2a (either 2a or 2b). INSTALL PYTHON LIBRARIES (DIFFICULT WAY) +##2. Clone repository: ``` -cd /tmp -mkdir pycabuild -cd pycabuild +git clone https://github.com/151henry151/pyca.git ``` -###checkout a specific release of libraries to help mitigate against dependency attacks (v2.21.0) -``` -cd /tmp/pycabuild -git clone https://github.com/kennethreitz/requests.git -cd requests -git fetch origin 5a1e738ea9c399c3f59977f2f98b083986d6037a -git reset --hard FETCH_HEAD -pip3 install . -``` - -###checkout a specific release of libraries to help mitigate against dependency attacks (last checkin as of 2/9/2018) -``` -cd /tmp/pycabuild -git clone https://github.com/mattselph/gemini-python-unoffc.git -cd gemini-python-unoffc -git fetch origin 684ae57b2c36cd96739e2b0d15db94ed6e27bba4 -git reset --hard FETCH_HEAD -pip3 install . -``` - -##2b (either 2a or 2b). INSTALL PYTHON DEPENDECIES (EASY WAY) +##3 Create a virtual environment (venv) and install python libraries: ``` +cd pyca +python3 -m venv venv +source venv/bin/activate pip3 install gemini-python-unoffc requests chardet urllib3 idna certifi ``` +NOTE: see https://docs.python.org/3/library/venv.html for more information on creation and use of virtual environments. To exit the virtual environment run ```deactivate``` - -##3. SET UP THIS PROGRAM -###download repository -###e.g. +##4. Run program to generate a blank config file (pyca.cfg): ``` -cd /tmp/pycabuild -git clone https://github.com/onyxcoyote/pyca.git +python3 pyca ``` -##4. copy all *.py files to the install location +##5. Copy example.cfg to pyca.cfg: ``` -mkdir /srv/pyca -cp /tmp/pycabuild/pyca/*.py /srv/pyca -cd /srv/pyca +cp example.cfg pyca.cfg ``` - -##5. run program to generate a blank config file (pyca.cfg) -update parameters in pyca.cfg: +##5a. Update parameters in pyca.cfg: api_key/api_secret from gemini, needs trading permissions. Do not require session heartbeat. leave is_sandbox as True for testing (gemini sandbox) or change to False to trade with actual money update other settings as desired -##6a. run program manually +##6. running program manually: ``` -cd /srv/pyca -python3 . +source venv/bin/activate +python3 pyca ``` +If everything runs correctly, then you can go ahead and create a system service to run the program automatically on system startup and restart it if interrupted: -NOTE: ideally set the program as a service so it runs automatically on startup +##6b. run program automatically on startup using systemd by creating this file in /etc/systemd/system/pyca.service: +``` +[Unit] +Description=pyca -##6b. run program automatically on startup using systemd, for example -TODO: add pyca.service example +[Service] +WorkingDirectory=/home/username/pyca/ +ExecStart=/home/username/pyca/venv/bin/python3 /home/username/pyca +Restart=always +RestartSec=45 +StandardOutput=file:/var/log/pyca.log +StandardError=file:/var/log/pyca_error.log +SyslogIdentifier=pyca +[Install] +WantedBy=multi-user.target + +``` +Note that this example service file assumes you are using a virtualenv, if not you can modify the first path in ExecStart to point at your python3 binary. ##7. changing parameters -If parameters are changed in the pyca.cfg file, the program would need to be stopped and re-started. +If parameters are changed in the pyca.cfg file, the program would need to be stopped and re-started. If you are running the program automatically with systemd as described in 6b, ```systemctl restart pyca.service``` ##8. secure pyca.cfg diff --git a/pyca/geminiBuyDCAPostOnly.py b/pyca/geminiBuyDCAPostOnly.py index 71f4400..3ea06f2 100644 --- a/pyca/geminiBuyDCAPostOnly.py +++ b/pyca/geminiBuyDCAPostOnly.py @@ -92,10 +92,12 @@ def __init__(self, _Enabled, _OrdersPerDay, _OrderQuantityPerDayInFiat, _TradeSy if((self.OrdersPerDay < 0.0) | (self.OrdersPerDay > 7200.0)): raise ValueError('invalid value for GeminiBuyDCAPostOnly.OrdersPerDay') - if((self.OrderQuantityPerDayInFiat < 0.00) | (self.OrderQuantityPerDayInFiat > 500.00)): #temporary maximum purchase per day in fiat of 500 fiat units (e.g. 500 USD) + if(self.OrderQuantityPerDayInFiat < 0.00): raise ValueError('invalid value for GeminiBuyDCAPostOnly.OrderQuantityPerDayInFiat') - - if(self.TradeSymbol != "btcusd"): + elif(self.OrderQuantityPerDayInFiat < self.HardMaximumQuantityPerDayInFiat): + raise ValueError('Hard Maximum Quantity Per Day in Fiat exceeded') + + if(self.TradeSymbol not in ["ethusd", "btcusd"]): raise ValueError('invalid value for GeminiBuyDCAPostOnly.TradeSymbol') if((self.MaxDaysCatchup < 1.0) | (self.MaxDaysCatchup > 20.0)): diff --git a/pyca/geminiTradeDCAPostOnly.py b/pyca/geminiTradeDCAPostOnly.py index 3cc0f23..fd1146f 100644 --- a/pyca/geminiTradeDCAPostOnly.py +++ b/pyca/geminiTradeDCAPostOnly.py @@ -231,7 +231,12 @@ def resubmitTrade(self, _orderObj): _quantityInFiat = float(_orderObj["remaining_amount"]) * float(_orderObj["price"]) #determine coin trade quantity - coinQuantity = round(_quantityInFiat / pricePerCoin,8) #assume 8 decimal places is max resolution on coin quantity + if(self.TradeSymbol=="btcusd"): + coinQuantity = round(_quantityInFiat / pricePerCoin,8) #assume 8 decimal places is max resolution on coin quantity + elif(self.TradeSymbol=="ethusd"): + coinQuantity = round(_quantityInFiat / pricePerCoin,6) # assume 6 decimal places is max resolution on coin quantity + else: + raise ValueError('invalidQuantity') print("coinQuantity:" + str(coinQuantity)) if(coinQuantity < 0.00001): @@ -240,7 +245,7 @@ def resubmitTrade(self, _orderObj): return try: - result = __main__.geminiClient.client.new_order(client_order_id=clientOrderId.getOrderId(), symbol=self.TradeSymbol, amount=str(coinQuantity), price=str(pricePerCoin), side=self.TradeSide, type='exchange limit', options=ORDER_OPTIONS) #API call + result = __main__.geminiClient.client.new_order(client_order_id=clientOrderId.getOrderId(), symbol=self.TradeSymbol, amount=str(round(coinQuantity, 6)), price=str(pricePerCoin), side=self.TradeSide, type='exchange limit', options=ORDER_OPTIONS) #API call print("trade order result: " + str(result)) except Exception as e: print("Error: " + str(e) + " Traceback: " + str(traceback.print_tb(e.__traceback__))) @@ -279,7 +284,7 @@ def doNewTrade(self): #determine coin order quantity - coinQuantity = round(_quantityInFiat / pricePerCoin,8) #assume 8 decimal places is max resolution on coin quantity + coinQuantity = round(_quantityInFiat / pricePerCoin,6) #assume 8 decimal places is max resolution on coin quantity print(" coinQuantity:" + str(coinQuantity)) if(coinQuantity < 0.00001): #todo: configure from gemini settings