Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strategy adjustment features pulled from @jaredrsommer fork #32

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions blank_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"api_key": "",
"private_key": "",
"custom_rpc_https": "",
"other_mint": "So11111111111111111111111111111111111111112",
"other_mint_symbol": "SOL",
"price_update_seconds": "60",
"trading_interval_minutes": "15",
"verbose": true,
"strategy": "default",
"sell_at_loss": false,
"stoploss": "20.0",
"trailing_stoploss_target": "5.0",
"trailing_stoploss": "1.5",
"telegram": true,
"tg_bot_token": "",
"tg_bot_uid": ""
}
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ requests==2.31
solana==0.29.0
matplotlib==3.8
solders==0.14.4
python-dotenv==1.0.1
python-dotenv==1.0.1
pandas_ta==0.3.14b0
7 changes: 5 additions & 2 deletions soltrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
from soltrade.trading import start_trading
from soltrade.log import log_general


# Initialize configuration
config()

def check_json_state() -> bool:
if config().keypair and config().secondary_mint:
return True

return False

# Prints "Soltrade" and information about the connected wallet
print(""" $$\ $$\ $$\
splash = (""" $$\ $$\ $$\
$$ | $$ | $$ |
$$$$$$$\ $$$$$$\ $$ |$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$ | $$$$$$\
$$ _____|$$ __$$\ $$ |\_$$ _| $$ __$$\ \____$$\ $$ __$$ |$$ __$$\
Expand All @@ -22,6 +22,8 @@ def check_json_state() -> bool:
$$$$$$$ |\$$$$$$ |$$ | \$$$$ |$$ | \$$$$$$$ |\$$$$$$$ |\$$$$$$$\
\_______/ \______/ \__| \____/ \__| \_______| \_______| \_______|
""")

print(splash)
can_run = check_json_state()

# Error catching in case the program is unable to find the properties of the wallet
Expand All @@ -34,6 +36,7 @@ def check_json_state() -> bool:
# Checks if the run prompt should be displayed
if can_run:
log_general.debug("Soltrade has successfully imported the API requirements.")

start_trading()
else:
exit()
21 changes: 20 additions & 1 deletion soltrade/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ def __init__(self):
self.trading_interval_minutes = None
self.slippage = None # BPS
self.computeUnitPriceMicroLamports = None
self.stoploss = None
self.trailing_stoploss = None
self.trailing_stoploss_target = None
self.telegram = None
self.tg_bot_token = None
self.tg_bot_uid = None
self.load_config()

def load_config(self):
Expand All @@ -41,11 +47,24 @@ def load_config(self):

# DEFAULT FEE OF ROUGHLY $0.04 TODAY
self.computeUnitPriceMicroLamports = int(os.getenv("COMPUTE_UNIT_PRICE_MICRO_LAMPORTS") or 20 * 14000)

#with open(self.path, 'r') as file:
# try:
# config_data = json.load(file)
# 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"]

@property
def keypair(self) -> Keypair:
try:
return Keypair.from_bytes(base58.b58decode(self.private_key))
b58_string = self.private_key
keypair = Keypair.from_base58_string(b58_string)
# print(f"Using Wallet: {keypair.pubkey()}")

return keypair
except Exception as e:
log_general.error(f"Error decoding private key: {e}")
exit(1)
Expand Down
28 changes: 0 additions & 28 deletions soltrade/indicators.py

This file was deleted.

84 changes: 84 additions & 0 deletions soltrade/strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import pandas as pd
import pandas_ta as pta
import talib.abstract as ta
from soltrade.config import config
from soltrade.log import log_general

def strategy(df: pd.DataFrame):
if config().strategy == 'default' or None:
if config().strategy is None:
log_general.info("No strategy selected in config.json using default")

### Populate default indicators
# Calculates EMA
df['ema_s'] = ta.EMA(df['close'], timeperiod = 8)
df['ema_m'] = ta.EMA(df['close'], timeperiod = 21)

# Bollinger Bands
sma = ta.SMA(df['close'], timeperiod = 20)
std = df['close'].rolling(20).std()
df['upper_bband'] = sma + std * 2
df['lower_bband'] = sma - std * 2

# RSI
df['rsi'] = ta.RSI(df, timeperiod=14)


### Entry
entry = (
((df['ema_s'] > df['ema_m']) |
(df['close'] < df['lower_bband'])) &
(df['rsi'] <= 31)
)
df.loc[entry, 'entry'] = 1


### Exit
exit = (
((df['ema_s'] < df['ema_m']) |
(df['close'] > df['upper_bband'])) &
(df['rsi'] >= 65)
)
df.loc[exit, 'exit'] = 1

return df

if config().strategy == 'custom':

df['cci'] = ta.CCI(df)

def calc_stoploss(df):
# Can also write custom stoploss functions using indicators.
sl = float(config().stoploss)

df['stoploss'] = df['entry_price'].iloc[-1] * (1 - sl / 100)

return df

def calc_trailing_stoploss(df):
# Can also write custom stoploss functions using indicators.
tsl = float(config().trailing_stoploss)
tslt = float(config().trailing_stoploss_target)

high_prices = df['high']
df['entry_price'] * (1 + tslt / 100)
trailing_stop = []
tracking_started = False # Flag to indicate when to start tracking the trailing stop-loss
highest_price = df['high'].iloc[0]

for price in high_prices:
if not tracking_started and price >= df['entry_price'].iloc[0] * (1 + tslt / 100):
# Start tracking the trailing stop-loss once the target price is reached
tracking_started = True
stop_price = highest_price * (1 - tsl / 100)
elif tracking_started:
if price > highest_price:
highest_price = price
stop_price = highest_price * (1 - tsl / 100)

trailing_stop.append(stop_price if tracking_started else None)

df['trailing_stoploss'] = trailing_stop
df['trailing_stoploss_target'] = df['entry_price'] * (1 + tslt / 100)

return df
131 changes: 75 additions & 56 deletions soltrade/trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

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
Expand All @@ -27,7 +28,7 @@ def fetch_candlestick() -> dict:
def perform_analysis():
log_general.debug("Soltrade is analyzing the market; no trade has been executed.")

global stoploss, takeprofit
global stoploss, takeprofit, trailing_stoploss, entry_price

market().load_position()

Expand All @@ -36,71 +37,89 @@ def perform_analysis():
candle_dict = candle_json["Data"]["Data"]

# Creates DataFrame for manipulation
columns = ['close', 'high', 'low', 'open', 'time', 'VF', 'VT']
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))

# DataFrame variable for TA-Lib manipulation
cl = df['close']

# Technical analysis values used in trading algorithm
price = cl.iat[-1]
ema_short = calculate_ema(dataframe=df, length=5)
ema_medium = calculate_ema(dataframe=df, length=20)
rsi = calculate_rsi(dataframe=df, length=14)
upper_bb, lower_bb = calculate_bbands(dataframe=df, length=14)
stoploss = market().sl
takeprofit = market().tp

log_general.debug(f"""
price: {price}
short_ema: {ema_short}
med_ema: {ema_medium}
upper_bb: {upper_bb.iat[-1]}
lower_bb: {lower_bb.iat[-1]}
rsi: {rsi}
stop_loss {stoploss}
take_profit: {takeprofit}
""")

if not market().position:
if not MarketPosition().position:
input_amount = find_balance(config().primary_mint)

if (ema_short > ema_medium or price < lower_bb.iat[-1]) and rsi <= 31:
log_transaction.info("Soltrade has detected a buy signal.")
if df['entry'].iloc[-1] == 1:
buy_msg = f"Soltrade has detected a buy signal using {input_amount} ${config().primary_mint_symbol}."
log_transaction.info(buy_msg)
# log_transaction.info(get_statistics())
if input_amount <= 0:
log_transaction.warning(f"Soltrade has detected a buy signal, but does not have enough {config().primary_mint_symbol} to trade.")
fund_msg = f"Soltrade has detected a buy signal, but does not have enough ${config().primary_mint_symbol} to trade."
log_transaction.info(fund_msg)
return
is_swapped = asyncio.run(perform_swap(input_amount, config().primary_mint))
if is_swapped:
stoploss = market().sl = cl.iat[-1] * 0.925
takeprofit = market().tp = cl.iat[-1] * 1.25
market().update_position(True, stoploss, takeprofit)
return
else:
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)
df = calc_trailing_stoploss(df)
stoploss = df['stoploss'].iloc[-1]
trailing_stoploss = df['trailing_stoploss'].iloc[-1]
print(df.tail(2))
# Save DataFrame to JSON file
json_file_path = 'data.json'
save_dataframe_to_json(df, json_file_path)


else:
# Read DataFrame from JSON file
df = read_dataframe_from_json(json_file_path)
print(df.tail(2))
input_amount = find_balance(config().secondary_mint)

if price <= stoploss or price >= takeprofit:
log_transaction.info("Soltrade has detected a sell signal. Stoploss or takeprofit has been reached.")
is_swapped = asyncio.run(perform_swap(input_amount, config().secondary_mint))
if is_swapped:
stoploss = takeprofit = market().sl = market().tp = 0
market().update_position(False, stoploss, takeprofit)
return

if (ema_short < ema_medium or price > upper_bb.iat[-1]) and rsi >= 68:
log_transaction.info("Soltrade has detected a sell signal. EMA or BB has been reached.")
is_swapped = asyncio.run(perform_swap(input_amount, config().secondary_mint))
if is_swapped:
stoploss = takeprofit = market().sl = market().tp = 0
market().update_position(False, stoploss, takeprofit)
return
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 = 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().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 = 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().secondary_mint))
stoploss = takeprofit = 0
df['entry_price'] = None

# Check Strategy
if df['exit'].iloc[-1] == 1:
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().secondary_mint))
stoploss = takeprofit = 0
df['entry_price'] = None

# This starts the trading function on a timer
def start_trading():
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()
perform_analysis()

# Function to save DataFrame to JSON file
def save_dataframe_to_json(df, file_path):
df_json = df.to_json(orient='records')
with open(file_path, 'w') as f:
json.dump(df_json, f)

# Function to read DataFrame from JSON file
def read_dataframe_from_json(file_path):
with open(file_path, 'r') as f:
df_json = json.load(f)
return pd.read_json(df_json)
Loading