Compare commits
25 Commits
2025.01.08
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
595dbcdb54 | |
|
|
e6d4578807 | |
|
|
23d09487d8 | |
|
|
c1ad535302 | |
|
|
2a01477a5e | |
|
|
b383cde1fe | |
|
|
9ed2927c0b | |
|
|
86ed6830f4 | |
|
|
17a5081f8a | |
|
|
866a130c35 | |
|
|
ad1adeff0a | |
|
|
574d8f82ef | |
|
|
15b2cf5601 | |
|
|
4ed36e1482 | |
|
|
e011621529 | |
|
|
d45d52f34f | |
|
|
394c71035d | |
|
|
b08f320fe1 | |
|
|
7704ea3ea7 | |
|
|
85bc56c11d | |
|
|
a62dc459e3 | |
|
|
f0bfcacc45 | |
|
|
8e999d5049 | |
|
|
e7374b1586 | |
|
|
123a0daf7f |
|
|
@ -5,4 +5,5 @@ keys/api_credentials.db
|
|||
*.pyc
|
||||
*.pyo
|
||||
credentials.py
|
||||
some_tests.py
|
||||
some_tests.py
|
||||
tests.py
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# DCAv2Earn
|
||||
|
||||
DCAv2Earn is a companion project for DCAv2. It automates the process of allocating a portion of idle funds on your exchange accounts to earn programs provided by each exchange. The system ensures that your funds are efficiently utilized to generate passive income while maintaining a minimum balance in your trading accounts at all times.
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic Allocation**: Automatically transfers a configurable percentage of your idle funds to the earn program of each supported exchange.
|
||||
- **Configurable Parameters**:
|
||||
- **Percentage of Funds**: Specify what percentage of your idle funds should be allocated to the earn program.
|
||||
- **Minimum Balance**: Set a minimum balance that must remain in your trading account to prevent over-allocation.
|
||||
- **Step Size**: Define the step size for balance adjustments to ensure smooth transitions.
|
||||
- **Multiple Exchanges Support**: Currently supports Binance, Gate.io, KuCoin and OKX. More exchanges can be added as needed.
|
||||
- **Logging and Monitoring**: Provides real-time monitoring of your funds' allocation and performance.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Python 3.8+**: Ensure you have Python 3.8 or higher installed.
|
||||
- **API Keys**: Obtain API keys from the supported exchanges with the necessary permissions for transferring funds (NOT withdrawing) and accessing earn programs.
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Clone the Repository**:
|
||||
```bash
|
||||
git clone https://gitea.nicosanchez.com.ar/nicolas/DCAv2Earn.git
|
||||
cd DCAv2Earn
|
||||
```
|
||||
|
||||
2. **Configure the System**:
|
||||
- Create a `config.json` file with the necessary configuration parameters.
|
||||
- Use `config.json.example` as a template.
|
||||
|
||||
|
||||
3. **Load the credentials to credentials.py**
|
||||
```
|
||||
Check credentials.py.example and replace the placeholders with your actual API keys. Then, rename the file to credentials.py.
|
||||
```
|
||||
|
||||
4. **Run the System**:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
- **lap_time**: The time in seconds between each lap of the system. During each lap, the system checks and adjusts your funds' allocation.
|
||||
- **minimum_amount_in_trading_account**: The minimum amount of funds that must remain in your trading account at all times to prevent over-allocation.
|
||||
- **percentage_of_funds_to_commit**: The percentage of your idle funds to allocate to the earn program.
|
||||
- **step_size**: The step size for balance adjustments, ensuring smooth transitions between balances.
|
||||
- **api_keys**: API keys for each supported exchange with the necessary permissions. The API keys are stored in credentials.py
|
||||
|
||||
## Usage
|
||||
|
||||
### ATTENTION: THIS PROGRAM IS A PERSONAL PROJECT AND IS NOT INTENDED TO BE USED BY ANYONE ELSE. USE AT YOUR OWN RISK.
|
||||
|
||||
The system runs continuously in the background, automatically allocating your funds to the earn program according to the specified parameters. You can monitor the system's performance and status through the provided logging and monitoring features.
|
||||
|
||||
## Support
|
||||
|
||||
For any questions, issues, or feature requests, please create an issue on the Gitea repository (https://gitea.nicosanchez.com.ar/nicolas/DCAv2Earn/issues).
|
||||
|
||||
## License
|
||||
|
||||
DCAv2Earn is released under the MIT License.
|
||||
|
||||
---
|
||||
10
config.json
10
config.json
|
|
@ -3,7 +3,7 @@
|
|||
"binance": {
|
||||
"currency": "USDT",
|
||||
"minimum_amount_in_trading_account": 1000,
|
||||
"step_size": 500,
|
||||
"step_size": 250,
|
||||
"percentage": 0.5,
|
||||
"time_between_subscriptions": 3600,
|
||||
"time_between_redemptions": 0
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"gateio": {
|
||||
"currency": "USDT",
|
||||
"minimum_amount_in_trading_account": 1000,
|
||||
"step_size": 500,
|
||||
"step_size": 250,
|
||||
"percentage": 0.5,
|
||||
"time_between_subscriptions": 3600,
|
||||
"time_between_redemptions": 0
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"kucoin": {
|
||||
"currency": "USDT",
|
||||
"minimum_amount_in_trading_account": 1000,
|
||||
"step_size": 500,
|
||||
"step_size": 250,
|
||||
"percentage": 0.5,
|
||||
"time_between_subscriptions": 3600,
|
||||
"time_between_redemptions": 0
|
||||
|
|
@ -27,13 +27,13 @@
|
|||
"okx": {
|
||||
"currency": "USDT",
|
||||
"minimum_amount_in_trading_account": 1000,
|
||||
"step_size": 500,
|
||||
"step_size": 250,
|
||||
"percentage": 0.5,
|
||||
"time_between_subscriptions": 3600,
|
||||
"time_between_redemptions": 0
|
||||
}
|
||||
},
|
||||
"lap_time": 10,
|
||||
"lap_time": 30,
|
||||
"api_port": 5011
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"exchanges": {
|
||||
"binance": {
|
||||
"currency": "USDT",
|
||||
"minimum_amount_in_trading_account": 1000,
|
||||
"step_size": 250,
|
||||
"percentage": 0.5,
|
||||
"time_between_subscriptions": 3600,
|
||||
"time_between_redemptions": 0
|
||||
},
|
||||
"gateio": {
|
||||
"currency": "USDT",
|
||||
"minimum_amount_in_trading_account": 1000,
|
||||
"step_size": 250,
|
||||
"percentage": 0.5,
|
||||
"time_between_subscriptions": 3600,
|
||||
"time_between_redemptions": 0
|
||||
},
|
||||
"kucoin": {
|
||||
"currency": "USDT",
|
||||
"minimum_amount_in_trading_account": 1000,
|
||||
"step_size": 250,
|
||||
"percentage": 0.5,
|
||||
"time_between_subscriptions": 3600,
|
||||
"time_between_redemptions": 0
|
||||
},
|
||||
"okx": {
|
||||
"currency": "USDT",
|
||||
"minimum_amount_in_trading_account": 1000,
|
||||
"step_size": 250,
|
||||
"percentage": 0.5,
|
||||
"time_between_subscriptions": 3600,
|
||||
"time_between_redemptions": 0
|
||||
}
|
||||
},
|
||||
"lap_time": 30,
|
||||
"api_port": 5001
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
def get_api_key(exchange):
|
||||
if exchange=="binance":
|
||||
api_key = "place_your_api_key_here"
|
||||
api_secret = "place_your_api_secret_here"
|
||||
return api_key, api_secret
|
||||
elif exchange=="kucoin":
|
||||
api_key = "place_your_api_key_here"
|
||||
api_secret = "place_your_api_secret_here"
|
||||
api_passphrase = "place_your_passphrase_here"
|
||||
return api_key, api_secret, api_passphrase
|
||||
elif exchange=="okx":
|
||||
api_key = "place_your_api_key_here"
|
||||
api_secret = "place_your_api_secret_here"
|
||||
api_passphrase = "place_your_passphrase_here"
|
||||
return api_key, api_secret, api_passphrase
|
||||
elif exchange=="gateio":
|
||||
api_key = "place_your_api_key_here"
|
||||
api_secret = "place_your_api_secret_here"
|
||||
return api_key, api_secret
|
||||
else:
|
||||
return None,None
|
||||
|
|
@ -1,9 +1,28 @@
|
|||
def balance_accounts(spot_balance, earn_balance, lower_limit, step_size, percentage):
|
||||
'''
|
||||
args:
|
||||
spot_balance: float
|
||||
Current spot balance
|
||||
earn_balance: float
|
||||
Current earn balance
|
||||
lower_limit: float
|
||||
Lower limit for spot balance
|
||||
step_size: float
|
||||
Step size for balance adjustment
|
||||
percentage: float
|
||||
Percentage of funds to be allocated to the earn account
|
||||
|
||||
returns:
|
||||
spot_balance: float
|
||||
Updated spot balance
|
||||
earn_balance: float
|
||||
Updated earn balance
|
||||
'''
|
||||
|
||||
spot_balance+=earn_balance
|
||||
earn_balance=0
|
||||
|
||||
#target_spot_balance = (spot_balance + earn_balance) * percentage
|
||||
target_spot_balance = spot_balance * percentage
|
||||
target_spot_balance = spot_balance * (1-percentage)
|
||||
|
||||
while abs(spot_balance - target_spot_balance) >= step_size:
|
||||
if spot_balance < target_spot_balance:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import time
|
||||
import datetime
|
||||
from libraries.balance_accounts import balance_accounts
|
||||
from libraries.colors import colors
|
||||
from decimal import Decimal, getcontext, ROUND_DOWN
|
||||
|
||||
|
||||
class earner:
|
||||
|
|
@ -20,13 +22,23 @@ class earner:
|
|||
self.trading_balance = 0
|
||||
self.earning_balance = 0
|
||||
|
||||
self.is_paused = True
|
||||
self.is_paused = False
|
||||
|
||||
self.status_string = ""
|
||||
|
||||
getcontext().prec = 8
|
||||
getcontext().rounding = ROUND_DOWN
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return str(self.connector)
|
||||
|
||||
def get_currency(self):
|
||||
return self.currency
|
||||
|
||||
def write_to_log(self,message):
|
||||
with open("earn.log", "a") as f:
|
||||
f.write(datetime.datetime.now().strftime(f"[%Y/%m/%d %H:%M:%S] {str(self.connector).upper()} | {message}\n"))
|
||||
|
||||
def toggle_pause(self):
|
||||
self.is_paused = not self.is_paused
|
||||
|
|
@ -41,6 +53,7 @@ class earner:
|
|||
return self.step_size
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.write_to_log(str(e))
|
||||
return 0
|
||||
|
||||
def get_step_size(self):
|
||||
|
|
@ -52,6 +65,7 @@ class earner:
|
|||
return self.percentage
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.write_to_log(str(e))
|
||||
return 0
|
||||
|
||||
def get_percentage(self):
|
||||
|
|
@ -63,6 +77,7 @@ class earner:
|
|||
return self.minimum_amount_in_trading_account
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.write_to_log(str(e))
|
||||
return 0
|
||||
|
||||
def get_minimum_amount_in_trading_account(self):
|
||||
|
|
@ -74,6 +89,7 @@ class earner:
|
|||
return self.time_between_subscriptions
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.write_to_log(str(e))
|
||||
return 0
|
||||
|
||||
def get_time_between_subscriptions(self):
|
||||
|
|
@ -85,6 +101,7 @@ class earner:
|
|||
return self.time_between_redemptions
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.write_to_log(str(e))
|
||||
return 0
|
||||
|
||||
def get_time_between_redemptions(self):
|
||||
|
|
@ -100,63 +117,97 @@ class earner:
|
|||
return self.status_string
|
||||
|
||||
def get_trading_balance(self):
|
||||
return float(self.trading_balance)
|
||||
try:
|
||||
return float(self.trading_balance)
|
||||
except Exception as e:
|
||||
self.write_to_log(f"{e}")
|
||||
return 0.0
|
||||
|
||||
def get_earning_balance(self):
|
||||
return float(self.earning_balance)
|
||||
try:
|
||||
return float(self.earning_balance)
|
||||
except Exception as e:
|
||||
self.write_to_log(f"{e}")
|
||||
return 0.0
|
||||
|
||||
|
||||
def subscribe(self,amount):
|
||||
print(f"{str(self.connector)} | Subscribing {amount} {self.currency}")
|
||||
def subscribe(self,amount,force_pause=False):
|
||||
print(f"{datetime.datetime.now().strftime('[%Y/%m/%d %H:%M:%S]')} | {str(self.connector).upper()} | {colors.green}Subscribing{colors.white} {amount} {self.currency}")
|
||||
self.write_to_log(f"{colors.green}Subscribing{colors.white} {amount} {self.currency}")
|
||||
|
||||
if force_pause:
|
||||
self.pause = True
|
||||
available_product = self.connector.get_available_products(self.currency)
|
||||
subscription = {}
|
||||
if available_product["asset"]==self.currency:
|
||||
if available_product["coin"]==self.currency:
|
||||
#Every exchange has it own subscription method
|
||||
if str(self.connector) in ["binance","kucoin"]:
|
||||
subscription = self.connector.subscribe_product(available_product["productId"],amount)
|
||||
subscription = self.connector.subscribe_product(available_product["product_id"],amount)
|
||||
elif str(self.connector)=="gateio":
|
||||
min_rate = self.connector.get_min_rate(available_product["asset"])["min_rate"]
|
||||
min_rate = self.connector.get_min_rate(available_product["coin"])["min_rate"]
|
||||
time.sleep(.5) #For the sake of the API
|
||||
subscription = self.connector.subscribe_product(available_product["productId"],amount,min_rate)
|
||||
subscription = self.connector.subscribe_product(available_product["product_id"],amount,min_rate)
|
||||
elif str(self.connector)=="okx":
|
||||
transfer = self.connector.transfer_to_funding(available_product["asset"],str(amount))
|
||||
transfer_state = self.connector.get_transfer_state(transfer["transId"])
|
||||
if "Success" in transfer_state:
|
||||
subscription = self.connector.subscribe_product(available_product["productId"],amount)
|
||||
transfer = self.connector.transfer_to_funding(available_product["coin"],str(amount))
|
||||
if "Success" in transfer:
|
||||
transfer_state = self.connector.get_transfer_state(transfer["transId"])
|
||||
if "Success" in transfer_state:
|
||||
subscription = self.connector.subscribe_product(available_product["product_id"],amount)
|
||||
else:
|
||||
print(f"{str(self.connector)} - Transfer of funds state query failed!")
|
||||
self.write_to_log("Transfer of funds state query failed! - " + str(subscription))
|
||||
return 1
|
||||
else:
|
||||
print(f"{str(self.connector)} - Transfer of funds failed!")
|
||||
return 1
|
||||
self.write_to_log("Transfer of funds failed! - " + str(transfer))
|
||||
return 1
|
||||
if "Success" in subscription:
|
||||
self.last_subscription_time = time.time()
|
||||
return 0
|
||||
self.write_to_log("Subscription failed! - " + str(subscription))
|
||||
return 1
|
||||
|
||||
|
||||
def redeem(self,amount):
|
||||
print(f"{str(self.connector)} | Redeeming {amount} {self.currency}")
|
||||
def redeem(self,amount,force_pause=False):
|
||||
print(f"{datetime.datetime.now().strftime('[%Y/%m/%d %H:%M:%S]')} | {str(self.connector).upper()} | {colors.red}Redeeming{colors.white} {amount} {self.currency}")
|
||||
self.write_to_log(f"{colors.red}Redeeming{colors.white} {amount} {self.currency}")
|
||||
|
||||
if force_pause:
|
||||
self.pause = True
|
||||
available_product = self.connector.get_available_products(self.currency)
|
||||
redemption = {}
|
||||
if available_product["asset"]==self.currency:
|
||||
if available_product["coin"]==self.currency:
|
||||
if str(self.connector) in ["binance","gateio"]:
|
||||
redemption = self.connector.redeem_product(available_product["productId"],amount=amount)
|
||||
redemption = self.connector.redeem_product(available_product["product_id"],amount=amount)
|
||||
elif str(self.connector)=="kucoin":
|
||||
position = self.connector.get_position(self.currency)
|
||||
if "Error" not in position:
|
||||
redemption = self.connector.redeem_product(position["orderId"],amount=amount)
|
||||
redemption = self.connector.redeem_product(position["positionId"],amount=amount)
|
||||
time.sleep(1)
|
||||
#The funds go to the funding account - transfer them to the trading account.
|
||||
transfer_step = self.connector.transfer_to_trading(self.currency, amount)
|
||||
else:
|
||||
print(f"{str(self.connector)} - Position not found!")
|
||||
self.write_to_log("Position not found! " + str(position))
|
||||
return 1
|
||||
elif str(self.connector)=="okx":
|
||||
redemption_step = self.connector.redeem_product(self.currency, amount=amount)
|
||||
if "Success" in redemption_step:
|
||||
transfer_step = self.connector.transfer_to_trading(self.currency, redemption_step["amount"])
|
||||
redemption = self.connector.get_transfer_state(transfer_step["transId"])
|
||||
time.sleep(1) #Breathing room
|
||||
funding_balance = self.connector.get_funding_balance(self.currency)
|
||||
transfer_step = self.connector.transfer_to_trading(self.currency, funding_balance[self.currency])
|
||||
if "Success" in transfer_step:
|
||||
redemption = self.connector.get_transfer_state(transfer_step["transId"])
|
||||
else:
|
||||
print(f"{str(self.connector)} - Transfer step failed!")
|
||||
self.write_to_log("Transfer step failed! " + str(transfer_step))
|
||||
return 1
|
||||
else:
|
||||
print(f"{str(self.connector)} - Redemption failed!")
|
||||
print(f"{str(self.connector)} - Redemption step failed!")
|
||||
self.write_to_log("Redemption step failed! " + str(redemption_step))
|
||||
return 1
|
||||
if "Sucess" in redemption:
|
||||
if "Success" in redemption:
|
||||
self.last_redemption_time = time.time()
|
||||
return 0
|
||||
self.write_to_log("Redemption failed! - " + str(redemption))
|
||||
return 1
|
||||
|
||||
|
||||
|
|
@ -185,30 +236,31 @@ class earner:
|
|||
else:
|
||||
self.earning_balance = None
|
||||
|
||||
|
||||
paused_string = ""
|
||||
if not self.is_paused:
|
||||
target_trading_amount, target_earning_amount = balance_accounts(float(self.trading_balance),
|
||||
float(self.earning_balance),
|
||||
self.minimum_amount_in_trading_account,
|
||||
self.step_size,
|
||||
self.percentage)
|
||||
earning_delta = 0
|
||||
target_trading_amount, target_earning_amount = balance_accounts(Decimal(self.trading_balance),
|
||||
Decimal(self.earning_balance),
|
||||
Decimal(self.minimum_amount_in_trading_account),
|
||||
Decimal(self.step_size),
|
||||
Decimal(self.percentage))
|
||||
earning_delta = Decimal(0)
|
||||
if self.earning_balance is None:
|
||||
print(f"{str(self.connector)} - There was an error fetching earning balance")
|
||||
print(f"{str(self.connector).upper()} - There was an error fetching earning balance")
|
||||
else:
|
||||
if float(self.earning_balance)!=0:
|
||||
earning_delta = target_earning_amount - float(self.earning_balance)
|
||||
if earning_delta>self.step_size and time.time()-self.last_subscription_time>self.time_between_subscriptions:
|
||||
#if float(self.earning_balance)!=0:
|
||||
earning_delta = target_earning_amount - Decimal(self.earning_balance)
|
||||
if earning_delta>Decimal(self.step_size) and time.time()-self.last_subscription_time>self.time_between_subscriptions:
|
||||
self.subscribe(earning_delta)
|
||||
if earning_delta<-self.step_size and time.time()-self.last_redemption_time>self.time_between_redemptions:
|
||||
elif earning_delta<-Decimal(self.step_size) and time.time()-self.last_redemption_time>self.time_between_redemptions:
|
||||
self.redeem(-earning_delta)
|
||||
print(f"{str(self.connector)} - Difference: {earning_delta}")
|
||||
#print(f"{str(self.connector)} - Difference: {earning_delta}")
|
||||
else:
|
||||
paused_string = "| "+colors.yellow+"PAUSED"+colors.white
|
||||
|
||||
#Output status to status_string
|
||||
balances_string = f"Trading balance: {float(self.trading_balance):.2f} {self.currency} | Earning balance: {float(self.earning_balance):.2f} {self.currency}"
|
||||
percentages_string = f"On earn: {float(self.earning_balance)/float(self.trading_balance):.2%} {paused_string}"
|
||||
total_balance = float(self.earning_balance)+float(self.trading_balance)
|
||||
percentages_string = f"On earn: {float(self.earning_balance)/total_balance:.2%} {paused_string}"
|
||||
|
||||
self.status_string = f"{colors.cyan}{self}{colors.white} | {balances_string} | {percentages_string}"
|
||||
self.status_string = f"{colors.cyan}{str(self).upper()}{colors.white} | {balances_string} | {percentages_string}"
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ class binance_earn:
|
|||
example: {'redeemId': 483738233, 'success': True}
|
||||
'''
|
||||
try:
|
||||
response = self.client.redeem_flexible_product(productId=product_id, redeemAll=redeem_all, amount=amount, destAccount=destination_account, recvWindow=5000)
|
||||
response = self.client.redeem_flexible_product(product_id, redeemAll=redeem_all, amount=amount, destAccount=destination_account, recvWindow=5000)
|
||||
if response["success"]:
|
||||
return {"Success": "",
|
||||
"orderId": response["redeemId"],
|
||||
|
|
|
|||
|
|
@ -207,8 +207,16 @@ class gateio_earn:
|
|||
return {"Error": "To be implemented"}
|
||||
|
||||
|
||||
def get_rewards_history(self, type="ALL", **kwargs):
|
||||
return {"Error": "To be implemented"}
|
||||
def get_rewards_history(self, coin):
|
||||
url = f"/earn/uni/interests/{coin}"
|
||||
headers = {"Accept": "application/json", "Content-Type": "application/json"}
|
||||
sign_headers = self.gen_sign("GET", self.prefix+url)
|
||||
headers.update(sign_headers)
|
||||
response = requests.get(self.host+self.prefix+url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return {"Error": response.text}
|
||||
|
||||
|
||||
def get_subscription_preview(self, product_id, amount, **kwargs):
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from kucoin_universal_sdk.generate.earn.earn.model_get_account_holding_req impor
|
|||
from kucoin_universal_sdk.generate.earn.earn.model_purchase_req import PurchaseReqBuilder
|
||||
from kucoin_universal_sdk.generate.earn.earn.model_redeem_req import RedeemReqBuilder
|
||||
from kucoin_universal_sdk.generate.account.account.model_get_spot_account_list_req import GetSpotAccountListReqBuilder
|
||||
from kucoin_universal_sdk.generate.account.transfer.model_flex_transfer_req import FlexTransferReqBuilder
|
||||
|
||||
class kucoin_earn:
|
||||
def __init__(self):
|
||||
|
|
@ -42,6 +43,7 @@ class kucoin_earn:
|
|||
kucoin_rest_service = self.client.rest_service()
|
||||
self.account_api = kucoin_rest_service.get_account_service().get_account_api
|
||||
self.earn_api = kucoin_rest_service.get_earn_service().get_earn_api
|
||||
self.transfer_api = kucoin_rest_service.get_account_service().get_transfer_api
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
|
@ -127,6 +129,27 @@ class kucoin_earn:
|
|||
return {"Error": response}
|
||||
|
||||
|
||||
def transfer_to_trading(self, coin, amount):
|
||||
'''
|
||||
Args:
|
||||
coin (str): The coin to transfer to trading
|
||||
amount (float): The amount to transfer
|
||||
Returns:
|
||||
dict: The response from the api
|
||||
'''
|
||||
request = FlexTransferReqBuilder().set_from_account_type("MAIN").set_to_account_type("TRADE").set_currency(coin).set_amount(str(amount)).set_type("INTERNAL").set_client_oid("1234").build()
|
||||
response = self.transfer_api().flex_transfer(request).to_dict()
|
||||
response_dict = response["common_response"]["data"]
|
||||
if "orderId" in response_dict:
|
||||
return {"Success": "",
|
||||
"orderId": response["orderId"],
|
||||
"txId": "",
|
||||
"amount": amount
|
||||
}
|
||||
else:
|
||||
return {"Error": response}
|
||||
|
||||
|
||||
def get_position(self, coin):
|
||||
'''
|
||||
Return {'common_response': {'code': '200000', 'data': {'totalNum': 1, 'items': [{'orderId': '2987632', 'productId': '2152', 'productCategory': 'DEMAND', 'productType': 'DEMAND', 'currency': 'USDT', 'incomeCurrency': 'USDT', 'returnRate': '0.04767484', 'holdAmount': '20', 'redeemedAmount': '0', 'redeemingAmount': '0', 'lockStartTime': 1641806718000, 'lockEndTime': None, 'purchaseTime': 1736027283000, 'redeemPeriod': 0, 'status': 'LOCKED', 'earlyRedeemSupported': 0}], 'currentPage': 1, 'pageSize': 15, 'totalPage': 1}, 'rate_limit': {'limit': 2000, 'remaining': 1995, 'reset': 16550}}, 'totalNum': 1, 'items': [{'orderId': '2987632', 'productId': '2152', 'productCategory': 'DEMAND', 'productType': 'DEMAND', 'currency': 'USDT', 'incomeCurrency': 'USDT', 'returnRate': '0.04767484', 'holdAmount': '20', 'redeemedAmount': '0', 'redeemingAmount': '0', 'lockStartTime': 1641806718000, 'purchaseTime': 1736027283000, 'redeemPeriod': 0, 'status': <StatusEnum.LOCKED: 'LOCKED'>, 'earlyRedeemSupported': <EarlyRedeemSupportedEnum.T_0: 0>}], 'currentPage': 1, 'pageSize': 15, 'totalPage': 1}
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ class okx_earn:
|
|||
"amount": response["data"][0]["amt"]
|
||||
}
|
||||
else:
|
||||
return {"Error": response}
|
||||
return {"Error": response, "Rate": str(rate)}
|
||||
|
||||
|
||||
def redeem_product(self, coin, amount):
|
||||
|
|
@ -207,6 +207,8 @@ class okx_earn:
|
|||
Returns the 24hs average lending rate
|
||||
'''
|
||||
rate = self.earning_api.get_public_borrow_info(coin)
|
||||
if rate["data"][0]["avgRate"]=='0':
|
||||
return str(rate["data"][0]["estRate"])
|
||||
return str(rate["data"][0]["avgRate"])
|
||||
|
||||
|
||||
|
|
|
|||
560
main.py
560
main.py
|
|
@ -1,23 +1,25 @@
|
|||
import libraries.balance_accounts as balance_accounts
|
||||
from libraries.wrappers import earn_binance
|
||||
from libraries.wrappers import earn_kucoin
|
||||
from libraries.wrappers import earn_okx
|
||||
from libraries.wrappers import earn_gateio
|
||||
from libraries.earner import earner
|
||||
from libraries.colors import colors
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from threading import Thread
|
||||
from flask import Flask, jsonify, request
|
||||
from waitress import serve
|
||||
from functools import wraps
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
import sqlite3
|
||||
import socket
|
||||
import signal
|
||||
import os
|
||||
|
||||
|
||||
def load_keys_from_db(file_name: str) -> list:
|
||||
'''
|
||||
Load valid API keys
|
||||
Load valid API keys to a set
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
|
@ -36,7 +38,7 @@ def load_keys_from_db(file_name: str) -> list:
|
|||
data = database_cursor.fetchall()
|
||||
database_connection.close()
|
||||
|
||||
valid_keys = [line[1] for line in data]
|
||||
valid_keys = {line[1] for line in data}
|
||||
|
||||
return valid_keys
|
||||
|
||||
|
|
@ -63,16 +65,15 @@ def seconds_to_time(total_seconds: float) -> str:
|
|||
|
||||
|
||||
def main():
|
||||
executor = ThreadPoolExecutor(max_workers=len(earners))
|
||||
while True:
|
||||
threads = []
|
||||
|
||||
#Run earners
|
||||
for item in earners:
|
||||
threads.append(Thread(target=item.run))
|
||||
for item in threads:
|
||||
item.start()
|
||||
for item in threads:
|
||||
item.join()
|
||||
futures = [executor.submit(e.run) for e in earners]
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
print(f"Error in thread - {e}")
|
||||
|
||||
#Print status
|
||||
subscriptions = []
|
||||
|
|
@ -88,7 +89,7 @@ def main():
|
|||
last_subscription_key = "Never"
|
||||
last_subscription_value = ""
|
||||
else:
|
||||
last_subscription_value = datetime.datetime.fromtimestamp(last_subscription_value).strftime('%Y-%m-%d %H:%M:%S')
|
||||
last_subscription_value = datetime.datetime.fromtimestamp(last_subscription_value).strftime('@%Y/%m/%d %H:%M:%S')
|
||||
last_redemption = max(redemptions, key=lambda d: next(iter(d.values())))
|
||||
last_redemption_key = next(iter(last_redemption.keys()))
|
||||
last_redemption_value = next(iter(last_redemption.values()))
|
||||
|
|
@ -96,15 +97,17 @@ def main():
|
|||
last_redemption_key = "Never"
|
||||
last_redemption_value = ""
|
||||
else:
|
||||
last_redemption_value = datetime.datetime.fromtimestamp(last_redemption_value).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
print("-"*80)
|
||||
last_redemption_value = datetime.datetime.fromtimestamp(last_redemption_value).strftime('@%Y/%m/%d %H:%M:%S')
|
||||
print("-"*90)
|
||||
total_on_trading = sum([item.get_trading_balance() for item in earners])
|
||||
total_on_earning = sum([item.get_earning_balance() for item in earners])
|
||||
total_funds = total_on_earning+total_on_trading
|
||||
time_of_day = datetime.datetime.now().strftime('[%Y/%m/%d %H:%M:%S]')
|
||||
print(f"{time_of_day} | Version {version} | Total funds: {total_on_trading+total_on_earning:.2f} | On earn: {total_on_earning:.2f} ({total_on_earning/total_on_trading*100:.2f}%)")
|
||||
print(f"Uptime: {seconds_to_time(time.time()-start_time)} | Last subscription: {last_subscription_key} - {last_subscription_value} | Last redemption: {last_redemption_key} - {last_redemption_value}")
|
||||
print(colors.blue+"="*80+colors.white)
|
||||
print(f"Last subscription: {last_subscription_key}{last_subscription_value} | Last redemption: {last_redemption_key}{last_redemption_value}")
|
||||
print(f"Version {version} | Total funds: {total_funds:.2f} USDT | On earn: {total_on_earning:.2f} USDT ({total_on_earning/total_funds*100:.2f}%)")
|
||||
print(f"{time_of_day} | Uptime: {seconds_to_time(time.time()-start_time)}")
|
||||
print(colors.blue+"="*90+colors.white)
|
||||
|
||||
#Wait for next lap
|
||||
time.sleep(config["lap_time"])
|
||||
|
||||
|
|
@ -116,7 +119,48 @@ def main():
|
|||
|
||||
earn_api = Flask(__name__)
|
||||
|
||||
#Helper functions
|
||||
def require_api_key(func):
|
||||
'''
|
||||
Validates API key
|
||||
'''
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
key = request.headers.get("X-API-KEY")
|
||||
if not key or key not in valid_keys:
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
@earn_api.route("/get_global_status", methods=['GET'])
|
||||
@require_api_key
|
||||
def get_global_status():
|
||||
'''
|
||||
GET request
|
||||
|
||||
Parameters:
|
||||
None
|
||||
'''
|
||||
|
||||
response = {}
|
||||
for item in earners:
|
||||
response[str(item.connector)] = {"currency": item.get_currency(),
|
||||
"trading_balance": item.get_trading_balance(),
|
||||
"earning_balance": item.get_earning_balance(),
|
||||
"is_paused": item.get_is_paused(),
|
||||
"step_size": item.get_step_size(),
|
||||
"percentage": item.get_percentage(),
|
||||
"minimum_amount_in_trading_account": item.get_minimum_amount_in_trading_account(),
|
||||
"time_between_subscriptions": item.get_time_between_subscriptions(),
|
||||
"time_between_redemptions": item.get_time_between_redemptions(),
|
||||
"last_subscription": item.get_last_subscription(),
|
||||
"last_redemption": item.get_last_redemption()}
|
||||
response["uptime"] = time.time() - start_time
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
@earn_api.route("/toggle_pause", methods=['POST'])
|
||||
@require_api_key
|
||||
def toggle_pause():
|
||||
'''
|
||||
GET request
|
||||
|
|
@ -124,25 +168,23 @@ def toggle_pause():
|
|||
Parameters:
|
||||
broker: str
|
||||
'''
|
||||
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.toggle_pause()
|
||||
return jsonify({'Status': item.get_is_paused()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.toggle_pause()
|
||||
return jsonify({'Status': item.get_is_paused()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/get_step_size", methods=["GET"])
|
||||
@require_api_key
|
||||
def get_step_size():
|
||||
'''
|
||||
GET request
|
||||
|
|
@ -150,254 +192,298 @@ def get_step_size():
|
|||
Parameters:
|
||||
broker: str
|
||||
'''
|
||||
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'step_size': item.get_step_size()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'step_size': item.get_step_size()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/set_step_size", methods=["POST"])
|
||||
def set_step_size():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
new_step_size = request.json["new_step_size"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_step_size is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_step_size(new_step_size)
|
||||
return jsonify({'step_size': new_step_size})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
@require_api_key
|
||||
def set_step_size():
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
new_step_size = request.json["new_step_size"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_step_size is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_step_size(new_step_size)
|
||||
return jsonify({'step_size': new_step_size})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/get_percentage", methods=["GET"])
|
||||
@earn_api.route("/get_percentage", methods=["GET"])
|
||||
@require_api_key
|
||||
def get_percentage():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'percentage': item.get_percentage()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'percentage': item.get_percentage()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/set_percentage", methods=["POST"])
|
||||
def set_percentage():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
new_percentage = request.json["new_percentage"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_percentage is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_percentage(new_percentage)
|
||||
return jsonify({'percentage': new_percentage})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
@require_api_key
|
||||
def set_percentage():
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
new_percentage = request.json["new_percentage"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_percentage is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_percentage(new_percentage)
|
||||
return jsonify({'percentage': new_percentage})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/get_time_between_subscriptions", methods=["GET"])
|
||||
def get_time_between_subscriptions():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'time_between_subscriptions': item.get_time_between_subscriptions()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
@earn_api.route("/get_time_between_subscriptions", methods=["GET"])
|
||||
@require_api_key
|
||||
def get_time_between_subscriptions():
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'time_between_subscriptions': item.get_time_between_subscriptions()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/set_time_between_subscriptions", methods=["POST"])
|
||||
@require_api_key
|
||||
def set_time_between_subscriptions():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
new_time_between_subscriptions = request.json["new_time_between_subscriptions"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_time_between_subscriptions is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_time_between_subscriptions(new_time_between_subscriptions)
|
||||
return jsonify({'percentage': new_time_between_subscriptions})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
new_time_between_subscriptions = request.json["new_time_between_subscriptions"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_time_between_subscriptions is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_time_between_subscriptions(new_time_between_subscriptions)
|
||||
return jsonify({'time_between_subscriptions': new_time_between_subscriptions})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/get_time_between_redemptions", methods=["GET"])
|
||||
@require_api_key
|
||||
def get_time_between_redemptions():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'time_between_redemptions': item.get_time_between_redemptions()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'time_between_redemptions': item.get_time_between_redemptions()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/set_time_between_redemptions", methods=["POST"])
|
||||
@require_api_key
|
||||
def set_time_between_redemptions():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
new_time_between_redemptions = request.json["new_time_between_redemptions"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_time_between_redemptions is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_time_between_redemptions(new_time_between_redemptions)
|
||||
return jsonify({'percentage': new_time_between_redemptions})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
new_time_between_redemptions = request.json["new_time_between_redemptions"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_time_between_redemptions is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_time_between_redemptions(new_time_between_redemptions)
|
||||
return jsonify({'time_between_redemptions': new_time_between_redemptions})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/get_minimum_amount_in_trading_account", methods=["GET"])
|
||||
@require_api_key
|
||||
def get_minimum_amount_in_trading_account():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'minimum_amount_in_trading_account': item.get_minimum_amount_in_trading_account()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'minimum_amount_in_trading_account': item.get_minimum_amount_in_trading_account()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/set_minimum_amount_in_trading_account", methods=["POST"])
|
||||
@require_api_key
|
||||
def set_minimum_amount_in_trading_account():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
broker = request.json["broker"]
|
||||
new_minimum_amount_in_trading_account = request.json["new_minimum_amount_in_trading_account"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_minimum_amount_in_trading_account is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_minimum_amount_in_trading_account(new_minimum_amount_in_trading_account)
|
||||
return jsonify({'percentage': new_minimum_amount_in_trading_account})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
broker = request.json["broker"]
|
||||
new_minimum_amount_in_trading_account = request.json["new_minimum_amount_in_trading_account"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if new_minimum_amount_in_trading_account is None:
|
||||
return jsonify({'Error': 'new_step_size is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
item.set_minimum_amount_in_trading_account(new_minimum_amount_in_trading_account)
|
||||
return jsonify({'minimum_amount_in_trading_account': new_minimum_amount_in_trading_account})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/get_last_subscription", methods=["GET"])
|
||||
@require_api_key
|
||||
def get_last_subscription():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'last_subscription': item.get_last_subscription()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'last_subscription': item.get_last_subscription()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/get_last_redemption", methods=["GET"])
|
||||
@require_api_key
|
||||
def get_last_redemption():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'last_redemption': item.get_last_redemption()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'last_redemption': item.get_last_redemption()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/get_total_balance", methods=["GET"])
|
||||
@require_api_key
|
||||
def get_total_balance():
|
||||
if "X-API-KEY" in request.headers and request.headers.get("X-API-KEY") in valid_keys:
|
||||
valid_brokers = [str(item.connector) for item in earners]
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_brokers:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'trading_balance': item.get_trading_balance(), 'earning_balance': item.get_earning_balance()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
return jsonify({'Error': 'API key not valid'}), 401
|
||||
broker = request.args.get("broker")
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
return jsonify({'trading_balance': item.get_trading_balance(), 'earning_balance': item.get_earning_balance()})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
'''
|
||||
Missing endpoints:
|
||||
/get_total_balance
|
||||
'''
|
||||
|
||||
@earn_api.route("/subscribe", methods=["POST"])
|
||||
@require_api_key
|
||||
def subscribe():
|
||||
'''
|
||||
args:
|
||||
broker: broker name
|
||||
amount: amount to subscribe
|
||||
force_pause: True or False
|
||||
'''
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
amount = request.json["amount"]
|
||||
force_pause = False
|
||||
if "force_pause" in request.json:
|
||||
force_pause = request.json["force_pause"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if amount is None:
|
||||
return jsonify({'Error': 'amount is None'})
|
||||
if force_pause is None:
|
||||
return jsonify({'Error': 'force_pause is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
subscription = item.subscribe(amount, force_pause=force_pause)
|
||||
return jsonify({'Success': str(subscription)})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
@earn_api.route("/redeem", methods=["POST"])
|
||||
@require_api_key
|
||||
def redeem():
|
||||
'''
|
||||
args:
|
||||
broker: broker name
|
||||
amount: amount to redeem
|
||||
force_pause: True or False
|
||||
'''
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
broker = request.json["broker"]
|
||||
amount = request.json["amount"]
|
||||
force_pause = False
|
||||
if "force_pause" in request.json:
|
||||
force_pause = request.json["force_pause"]
|
||||
if broker is None:
|
||||
return jsonify({'Error': 'broker is None'})
|
||||
if broker not in valid_broker_list:
|
||||
return jsonify({'Error': 'broker not valid'})
|
||||
if amount is None:
|
||||
return jsonify({'Error': 'amount is None'})
|
||||
if force_pause is None:
|
||||
return jsonify({'Error': 'force_pause is None'})
|
||||
for item in earners:
|
||||
if str(item.connector)==broker:
|
||||
redemption = item.redeem(amount, force_pause=force_pause)
|
||||
return jsonify({'Success': str(redemption)})
|
||||
return jsonify({'Error': 'broker not found'})
|
||||
|
||||
|
||||
|
||||
def run_API(port):
|
||||
serve(earn_api, host="0.0.0.0", port=port)
|
||||
#earn_api.run(host="0.0.0.0", port=port)
|
||||
|
||||
|
||||
executor = None
|
||||
#Shutdown handler
|
||||
def shutdown_handler(signum, _):
|
||||
print(f"Received signal {signum}, shutting down as gracefully as possible...")
|
||||
if executor:
|
||||
executor.shutdown(wait=True, timeout=5)
|
||||
os._exit(0)
|
||||
|
||||
# Register signals for shutdown handler
|
||||
signal.signal(signal.SIGINT, shutdown_handler)
|
||||
signal.signal(signal.SIGTERM, shutdown_handler)
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
|
||||
version = "2025.01.08"
|
||||
version = "2025.11.20"
|
||||
start_time = time.time()
|
||||
|
||||
with open("config.json") as f:
|
||||
|
|
@ -412,15 +498,23 @@ if __name__=="__main__":
|
|||
for item in config["exchanges"]:
|
||||
earners.append(earner(connectors[item], config["exchanges"][item]))
|
||||
|
||||
#Valid broker list
|
||||
valid_broker_list = [str(item.connector) for item in earners]
|
||||
|
||||
#Load valid API keys
|
||||
valid_keys = load_keys_from_db("keys/api_credentials.db")
|
||||
|
||||
#Threads to run: main loop and flask api
|
||||
main_threads = [Thread(target=main),Thread(target=run_API, args=(config["api_port"],))]
|
||||
api_thread = Thread(target=run_API, args=(config["api_port"],), daemon=True)
|
||||
|
||||
#Iterate indefinitely:
|
||||
for m in main_threads:
|
||||
m.start()
|
||||
api_thread.start()
|
||||
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
api_thread.join()
|
||||
shutdown_handler(signal.SIGINT, None)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
from libraries.wrappers import earn_binance
|
||||
from libraries.wrappers import earn_okx
|
||||
from libraries.wrappers import earn_gateio
|
||||
from libraries.balance_accounts import balance_accounts
|
||||
from decimal import Decimal
|
||||
|
||||
binance = earn_binance.binance_earn()
|
||||
gateio = earn_gateio.gateio_earn()
|
||||
okx = earn_okx.okx_earn()
|
||||
|
||||
total_profits = []
|
||||
print("Profits OKX:")
|
||||
total_rewards = Decimal(0)
|
||||
rewards = okx.get_lending_history("USDT")
|
||||
for item in rewards["data"]:
|
||||
total_rewards += Decimal(item["earnings"])
|
||||
print(total_rewards)
|
||||
total_profits.append(total_rewards)
|
||||
|
||||
print("Profits Gate.io:")
|
||||
total_rewards = gateio.get_rewards_history("USDT")["interest"]
|
||||
print(total_rewards)
|
||||
total_profits.append(Decimal(total_rewards))
|
||||
|
||||
print("Profits Binance:")
|
||||
total_rewards = Decimal(0)
|
||||
rewards = binance.get_rewards_history()
|
||||
for item in rewards["rows"]:
|
||||
total_rewards += Decimal(item["rewards"])
|
||||
print(total_rewards)
|
||||
total_profits.append(total_rewards)
|
||||
|
||||
print(f"Total: {sum(total_profits)}")
|
||||
Loading…
Reference in New Issue