Skip to content

Commit

Permalink
Resolved conflicts between repositories
Browse files Browse the repository at this point in the history
The conflicts required for merging have been resolved, but the branch is by no means ready for main.
  • Loading branch information
noahtheprogrammer committed Jul 24, 2024
1 parent 719b6bd commit 194e74a
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 118 deletions.
13 changes: 5 additions & 8 deletions soltrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
82 changes: 38 additions & 44 deletions soltrade/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
89 changes: 23 additions & 66 deletions soltrade/trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -78,83 +70,48 @@ 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]
print(stoploss, trailing_stoploss)

# 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')
Expand Down

0 comments on commit 194e74a

Please sign in to comment.