diff --git a/soltrade.py b/soltrade.py index f7f6496..7e88d7f 100644 --- a/soltrade.py +++ b/soltrade.py @@ -4,12 +4,10 @@ from soltrade.log import log_general # Initialize configuration -config_path = 'config.json' -config(config_path) +config() -def check_json_state(): - # print(config().keypair , config().other_mint) - if config().keypair and config().other_mint: +def check_json_state() -> bool: + if config().keypair and config().secondary_mint: return True return False @@ -30,10 +28,9 @@ def check_json_state(): # Error catching in case the program is unable to find the properties of the wallet try: - log_general.info(f"Soltrade has detected {find_balance(config().other_mint)} {config().other_mint_symbol} tokens available for trading.") - + log_general.info(f"Soltrade has detected {find_balance(config().primary_mint)} {config().primary_mint_symbol} tokens available for trading.") except Exception as e: - log_general.error(f"Error finding {config().other_mint_symbol} balance: {e}") + log_general.error(f"Error finding {config().primary_mint_symbol} balance: {e}") exit() # Checks if the run prompt should be displayed diff --git a/soltrade/config.py b/soltrade/config.py index 1aa7094..be2430a 100644 --- a/soltrade/config.py +++ b/soltrade/config.py @@ -6,18 +6,21 @@ from solders.keypair import Keypair from solana.rpc.api import Client from soltrade.log import log_general - +from dotenv import load_dotenv +import os class Config: - def __init__(self, path): - self.path = path + def __init__(self): + load_dotenv() + self.api_key = None self.private_key = None self.custom_rpc_https = None - self.usdc_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + self.primary_mint = None + self.primary_mint_symbol = None self.sol_mint = "So11111111111111111111111111111111111111112" - self.other_mint = None - self.other_mint_symbol = None + self.secondary_mint = None + self.secondary_mint_symbol = None self.price_update_seconds = None self.trading_interval_minutes = None self.slippage = None # BPS @@ -31,38 +34,30 @@ def __init__(self, path): self.load_config() def load_config(self): - if not os.path.exists(self.path): - log_general.error( - "Soltrade was unable to detect the JSON file. Are you sure config.json has not been renamed or removed?") - exit(1) + self.api_key = os.getenv('API_KEY') + self.private_key = os.getenv("WALLET_PRIVATE_KEY") + self.custom_rpc_https = os.getenv("custom_rpc_https", "https://api.mainnet-beta.solana.com/") + self.primary_mint = os.getenv("PRIMARY_MINT", "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") + self.primary_mint_symbol = os.getenv("PRIMARY_MINT_SYMBOL", "USD") + self.secondary_mint = os.getenv("SECONDARY_MINT", "") + self.secondary_mint_symbol = os.getenv("SECONDARY_MINT_SYMBOL", "UNKNOWN") + self.price_update_seconds = int(os.getenv("PRICE_UPDATE_SECONDS") or 60) + self.trading_interval_minutes = int(os.getenv("TRADING_INTERVALS_MINUTE") or 1) + self.slippage = int(os.getenv("SLIPPAGE") or 50) + + # config_data = json.load(file) + # self.computeUnitPriceMicroLamports = int(config_data.get("computeUnitPriceMicroLamports", 20 * 14000)) # default fee of roughly $.04 today + # self.verbose = config_data.get("verbose", False) + # self.strategy = config_data.get("strategy", "default") + # self.stoploss = config_data["stoploss"] + # self.trailing_stoploss = config_data["trailing_stoploss"] + # self.trailing_stoploss_target = config_data["trailing_stoploss_target"] - with open(self.path, 'r') as file: - try: - config_data = json.load(file) - self.api_key = config_data["api_key"] - self.private_key = config_data["private_key"] - self.custom_rpc_https = config_data.get("custom_rpc_https") or "https://api.mainnet-beta.solana.com/" - self.other_mint = config_data.get("other_mint", "") - self.other_mint_symbol = config_data.get("other_mint_symbol", "UNKNOWN") - self.price_update_seconds = int(config_data.get("price_update_seconds", 60)) - self.trading_interval_minutes = int(config_data.get("trading_interval_minutes", 1)) - self.slippage = int(config_data.get("slippage", 50)) - self.computeUnitPriceMicroLamports = int(config_data.get("computeUnitPriceMicroLamports", 20 * 14000)) # default fee of roughly $.04 today - self.verbose = config_data.get("verbose", False) - self.strategy = config_data.get("strategy", "default") - self.stoploss = config_data["stoploss"] - self.trailing_stoploss = config_data["trailing_stoploss"] - self.trailing_stoploss_target = config_data["trailing_stoploss_target"] - # print(len(self.private_key), self.private_key) - except json.JSONDecodeError as e: - log_general.error(f"Error parsing JSON: {e}") - exit(1) - except KeyError as e: - log_general.error(f"Missing configuration key: {e}") - exit(1) + # DEFAULT FEE OF ROUGHLY $0.04 TODAY + self.computeUnitPriceMicroLamports = int(os.getenv("COMPUTE_UNIT_PRICE_MICRO_LAMPORTS") or 20 * 14000) @property - def keypair(self): + def keypair(self) -> Keypair: try: b58_string = self.private_key keypair = Keypair.from_base58_string(b58_string) @@ -74,27 +69,26 @@ def keypair(self): exit(1) @property - def public_address(self): + def public_address(self) -> Pubkey: return self.keypair.pubkey() @property - def client(self): + def client(self) -> Client: rpc_url = self.custom_rpc_https return Client(rpc_url) - + @property - def decimals(self): - response = self.client.get_account_info_json_parsed(Pubkey.from_string(config().other_mint)).to_json() + def decimals(self) -> int: + response = self.client.get_account_info_json_parsed(Pubkey.from_string(config().secondary_mint)).to_json() json_response = json.loads(response) - value = 10**json_response["result"]["value"]["data"]["parsed"]["info"]["decimals"] + value = 10 ** json_response["result"]["value"]["data"]["parsed"]["info"]["decimals"] return value _config_instance = None -def config(path=None): +def config() -> Config: global _config_instance - if _config_instance is None and path is not None: - _config_instance = Config(path) + _config_instance = Config() return _config_instance diff --git a/soltrade/trading.py b/soltrade/trading.py index 3a6f443..550a022 100644 --- a/soltrade/trading.py +++ b/soltrade/trading.py @@ -2,41 +2,36 @@ import asyncio import pandas as pd -from apscheduler.schedulers.background import BackgroundScheduler -from soltrade.transactions import perform_swap, MarketPosition +from apscheduler.schedulers.background import BlockingScheduler + +from soltrade.transactions import perform_swap, market from soltrade.indicators import calculate_ema, calculate_rsi, calculate_bbands from soltrade.strategy import strategy, calc_stoploss, calc_trailing_stoploss from soltrade.wallet import find_balance from soltrade.log import log_general, log_transaction from soltrade.config import config - -# Stoploss and trading values for statistics and algorithm -stoploss = takeprofit = 0 -ema_short = ema_medium = 0 -upper_bb = lower_bb = 0 -rsi = 0 -price = 0 - +market('position.json') # Pulls the candlestick information in fifteen minute intervals -def fetch_candlestick(): +def fetch_candlestick() -> dict: url = "https://min-api.cryptocompare.com/data/v2/histominute" headers = {'authorization': config().api_key} - params = {'fsym': config().other_mint_symbol, 'tsym': 'USD', 'limit': 200, 'aggregate': config().trading_interval_minutes} + params = {'tsym': config().primary_mint_symbol, 'fsym': config().secondary_mint_symbol, 'limit': 50, 'aggregate': config().trading_interval_minutes} response = requests.get(url, headers=headers, params=params) if response.json().get('Response') == 'Error': log_general.error(response.json().get('Message')) exit() return response.json() - # Analyzes the current market variables and determines trades def perform_analysis(): log_general.debug("Soltrade is analyzing the market; no trade has been executed.") - global stoploss, trailing_stoploss, engage_tsl global entry_price + global stoploss, takeprofit + + market().load_position() # Converts JSON response for DataFrame manipulation candle_json = fetch_candlestick() @@ -45,23 +40,20 @@ def perform_analysis(): # Creates DataFrame for manipulation columns = ['close', 'high', 'low', 'open', 'time'] df = pd.DataFrame(candle_dict, columns=columns) - df['time'] = pd.to_datetime(df['time'], unit='s') - + df['time'] = pd.to_datetime(df['time'], unit='s') df = strategy(df) print(df.tail(2)) if not MarketPosition().position: - usdc_balance = find_balance(config().usdc_mint) - input_amount = round(usdc_balance, 1) - 0.01 + input_amount = find_balance(config().primary_mint) if df['entry'].iloc[-1] == 1: - buy_msg = f"Soltrade has detected a buy signal using {input_amount} USDC" + buy_msg = f"Soltrade has detected a buy signal for {input_amount} ${config().primary_mint_symbol}." log_transaction.info(buy_msg) # log_transaction.info(get_statistics()) if input_amount <= 0 or input_amount >= usdc_balance: - fund_msg = "Soltrade has detected a buy signal, but does not have enough USDC to trade." - log_transaction.info(fund_msg) + log_transaction.warning(f"Soltrade has detected a buy signal, but does not have enough ${config().primary_mint_symbol} to trade.") return - asyncio.run(perform_swap(input_amount, config().usdc_mint)) + asyncio.run(perform_swap(input_amount, config().primary_mint)) df['entry_price'] = df['close'].iloc[-1] entry_price = df['entry_price'] df = calc_stoploss(df) @@ -78,7 +70,7 @@ def perform_analysis(): # Read DataFrame from JSON file df = read_dataframe_from_json(json_file_path) print(df.tail(2)) - input_amount = round(find_balance(config().other_mint), 1) - 0.01 + input_amount = find_balance(config().secondary_mint) df = calc_trailing_stoploss(df) stoploss = df['stoploss'].iloc[-1] trailing_stoploss = df['trailing_stoploss'].iloc[-1] @@ -86,75 +78,40 @@ def perform_analysis(): # Check Stoploss if df['close'].iloc[-1] <= stoploss: - sl_msg = "Soltrade has detected a sell signal. Stoploss has been reached." + sl_msg = f"Soltrade has detected a sell signal for {input_amount} ${config().secondary_mint_symbol}. Stoploss has been reached." log_transaction.info(sl_msg) # log_transaction.info(get_statistics()) - asyncio.run(perform_swap(input_amount, config().other_mint)) + asyncio.run(perform_swap(input_amount, config().secondary_mint)) stoploss = takeprofit = 0 df['entry_price'] = None # Check Trailing Stoploss if trailing_stoploss is not None: if df['close'].iloc[-1] < trailing_stoploss: - tsl_msg = "Soltrade has detected a sell signal. Trailing stoploss has been reached." + tsl_msg = f"Soltrade has detected a sell signal for {input_amount} ${config().secondary_mint_symbol}. Trailing stoploss has been reached." log_transaction.info(tsl_msg) # log_transaction.info(get_statistics()) - asyncio.run(perform_swap(input_amount, config().other_mint)) + asyncio.run(perform_swap(input_amount, config().secondary_mint)) stoploss = takeprofit = 0 df['entry_price'] = None # Check Strategy if df['exit'].iloc[-1] == 1: - exit_msg = "Soltrade has detected a sell signal from the strategy." + exit_msg = f"Soltrade has detected a sell signal for {input_amount} ${config().secondary_mint_symbol}." log_transaction.info(exit_msg) # log_transaction.info(get_statistics()) - asyncio.run(perform_swap(input_amount, config().other_mint)) + asyncio.run(perform_swap(input_amount, config().secondary_mint)) stoploss = takeprofit = 0 df['entry_price'] = None - # This starts the trading function on a timer def start_trading(): - output_message = "Soltrade has now initialized the trading algorithm." - log_general.info(output_message) - log_general.debug("Available commands are /statistics, /pause, /resume, and /quit.") - - trading_sched = BackgroundScheduler() + log_general.info("Soltrade has now initialized the trading algorithm.") + trading_sched = BlockingScheduler() trading_sched.add_job(perform_analysis, 'interval', seconds=config().price_update_seconds, max_instances=1) trading_sched.start() perform_analysis() - while True: - event = input().lower() - if event == '/pause': - trading_sched.pause() - log_general.info("Soltrade has now been paused.") - - if event == '/resume': - trading_sched.resume() - log_general.info("Soltrade has now been resumed.") - if event == '/statistics': - print_statistics() - - if event == '/quit': - log_general.info("Soltrade has now been shut down.") - exit() - - -def get_statistics(): - return f""" - -Short EMA {ema_short} -Medium EMA {ema_medium} -Relative Strength Index {rsi} -Price {price} -Upper Bollinger Band {upper_bb.iat[-1]} -Lower Bollinger Band {lower_bb.iat[-1]}""" - - -def print_statistics(): - log_general.debug(get_statistics()) - # Function to save DataFrame to JSON file def save_dataframe_to_json(df, file_path): df_json = df.to_json(orient='records')