Warning
SolTrade trades real money on Solana mainnet. Start with small amounts you can afford to lose, test with a new wallet first, and never risk funds you can't spare. Not financial advice — you are responsible for your own trades.
-
Install uv:
Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Linux / macOS
curl -LsSf https://astral.sh/uv/install.sh | sh -
Clone the repository and create the configuration file:
git clone https://github.com/etcherfx/sol-trade.git cd sol-trade cp config.json.sample config.json -
Configure credentials and tokens:
- Copy
.env.sampleto.envand setSOLTRADE_PRIVATE_KEY(your Solana wallet). - In
config.json, setsecondary_mints/secondary_mint_symbols— the token(s) you want to trade.
- Copy
-
Start the bot:
uv run main.py
Paper trade first — swaps are simulated and the wallet is never touched:
uv run main.py --dry-run
SolTrade reads its configuration from config.json and its credentials from the .env file, both in the project root. Copy config.json.sample to config.json and .env.sample to .env before the first run. Environment variables take precedence over config.json.
Config is hot-reloaded: edits to config.json are picked up automatically on the next trading cycle (≤ 1 minute) — strategy, feature toggles, slippage, whale wallets, polling intervals, and the traded tokens all apply live. Structural settings (the primary token you pay with, RPC, exchange) and .env credentials still require a restart. Removing a token that currently has an open position is refused until that position is closed.
Secrets live in the git-ignored .env file:
| Variable | Purpose |
|---|---|
SOLTRADE_PRIVATE_KEY |
Solana wallet private key (base58) |
SOLTRADE_JUPITER_API_KEY |
Jupiter API key — optional, sent only if set |
| Setting | What it does | Default |
|---|---|---|
rpc_https |
Solana RPC endpoint for balances and token data | https://api.mainnet-beta.solana.com |
jup_api |
Jupiter Swap API endpoint | https://api.jup.ag/swap/v2 |
primary_mint / primary_mint_symbol |
The token you pay with (usually a stablecoin) | EPjF..v / USDC |
secondary_mints / secondary_mint_symbols |
The token(s) you want to trade | [So11..2] / [SOL] |
sol_mint |
Mint address of native SOL | So11111111111111111111111111111111111111112 |
price_update_seconds |
How often token prices refresh | 60 |
max_slippage |
Maximum accepted slippage in BPS (100 BPS = 1%) | 50 |
strategy |
The strategy to trade with | default |
secondary_weights |
Portfolio weight per token (parallel to secondary_mints); empty = equal split |
[] |
token_strategies |
Per-token strategy overrides, e.g. {"SOL": "default", "JUP": "momentum"} |
{} |
token_exchanges |
Per-token candle-data exchange overrides, e.g. {"POPCAT": "mexc"}; missing = data_exchange |
{} |
data_exchange |
Exchange used for candlestick data (via ccxt) | okx |
candles_path |
Local SQLite store for candlestick history | data/candles.db |
| Setting | What it does | Default |
|---|---|---|
whale_tracking_enabled |
Poll configured whale wallets and produce signals | true |
whale_wallets |
Wallet addresses to watch, per token symbol | {} |
whale_poll_interval_minutes |
How often whale balances are polled | 5 |
confluence_enabled |
Route every trade through the confluence filter | true |
market_regime_enabled |
Scale position sizes by market regime | false |
sentiment_enabled |
Pause trading when sentiment crashes | false |
sentiment_pause_hours |
How long a sentiment block lasts | 4 |
sentiment_threshold |
Per-token block threshold (-1 to +1) | -0.5 |
sentiment_crash_threshold |
Market-wide crash threshold (-1 to +1) | -0.7 |
whale_data_path |
Where whale snapshots are stored | data/whale_data.json |
regime_data_path |
Where market regime data is stored | data/regime_data.json |
sentiment_data_path |
Where sentiment data is stored | data/sentiment_data.json |
Trade several tokens with per-token allocation and strategies. Tokens are hot-reloadable — add or remove them in config.json and the change applies on the next cycle, no restart.
secondary_weights is a list parallel to secondary_mints — each entry is that token's share of total capital (cash plus all open positions). Weights are normalized to sum to 1 on load; an empty list means an equal split. A weight of 0 makes a token watch-only — it is analyzed but never bought.
"secondary_mints": ["So1111...2", "JUPyiwr...gBz"],
"secondary_mint_symbols": ["SOL", "JUP"],
"secondary_weights": [0.6, 0.4]A buy deploys at most weight × total_capital, so each token gets its slice instead of the first signal taking everything.
token_strategies assigns a different strategy per token; tokens not listed use the global strategy.
"token_strategies": { "SOL": "default", "JUP": "momentum" }token_exchanges assigns a different candle-data exchange per token (any ccxt exchange id); tokens not listed use the global data_exchange. Use it when one exchange doesn't list all your tokens — e.g. POPCAT is missing on OKX but available on MEXC:
"data_exchange": "okx",
"token_exchanges": { "POPCAT": "mexc" }Both settings hot-reload, but data_exchange itself is structural — changing it still requires a restart. Each exchange client is created once and reused; candle history is keyed by symbol, so switching a token's exchange starts fresh data for it.
- Adding a token is always safe — it starts fresh (new position CSV, no history).
- Removing a token with an open position is refused: the change is rolled back and an error is logged, because a removed token stops being managed (no stop-loss, take-profit, or trailing stop). Close the position first.
- When the token set changes, the P&L baseline is recaptured automatically so profit figures stay correct.
SolTrade runs a continuous loop:
- Fetch — the bot retrieves fresh prices and candlesticks for every configured token.
- Analyze — the active strategy computes indicators (EMA, RSI, and Bollinger Bands by default) and produces
entry/exitsignals. - Act — buy signals open a position; sell signals, stop-losses, take-profits, and trailing stops close it. Every trade is routed through the Jupiter Swap API.
- Protect — open positions are tracked and persisted to disk, so a restart resumes where it left off.
Optional layers — whale tracking, a confluence sizing filter, market regime detection, and a sentiment circuit breaker — operate between the signal and the trade. See Advanced features for details.
The bot runs inside a full-screen terminal UI:
| Screen | Key | Shows |
|---|---|---|
| Dashboard | 1 |
Live wallet balance, portfolio value, profit, and per-token indicators |
| Logs | 2 |
Full log history (scroll with arrow keys, PgUp/PgDn, Home/End) |
| Help | 3 |
Keybindings and general info |
Tab cycles through the screens; q or Ctrl-C quits. The bot keeps trading in the background while you browse the UI.
| Feature | What it does |
|---|---|
| Technical analysis | EMA, RSI, and Bollinger Bands out of the box — pure Python, no C libraries |
| Multiple tokens | Trade several tokens in the same loop |
| Position management | Stop-loss, take-profit, and trailing stop on every position |
| Custom strategies | Use your own strategy file |
| Whale tracking | Watches configured wallets for accumulation or dumping |
| Confluence filter | Sizes every trade from whale activity, market regime, and sentiment |
| Market regime | Scales positions down in bearish markets (opt-in) |
| Sentiment breaker | Pauses trading when social sentiment crashes (opt-in) |
How whale tracking, the confluence filter, market regime, and sentiment work
The tracker polls the wallets listed in whale_wallets at the interval specified by whale_poll_interval_minutes and compares balances over 1-hour, 4-hour, and 24-hour windows:
| Signal | Meaning |
|---|---|
ACCUMULATING |
Whales are net buying (>10% balance increase) |
DUMPING |
Whales are net selling (>10% balance decrease) |
NEUTRAL |
No significant movement |
NO_DATA |
No wallets configured, or not enough snapshots yet |
"whale_wallets": {
"SOL": ["wallet_address_1", "wallet_address_2"]
}Top token holders can be discovered with the built-in CLI. The command below lists the ten largest holders of SOL:
uv run -m sol_trade.whale_discovery So11111111111111111111111111111111111111112 10Usage: uv run -m sol_trade.whale_discovery TOKEN_MINT [LIMIT]
The filter combines the whale signal, market regime, and sentiment to determine position size:
| TA Signal | Whale Activity | Action | Position Size |
|---|---|---|---|
| BUY | ACCUMULATING | Full entry | 100% |
| BUY | NEUTRAL | Half entry | 50% |
| BUY | DUMPING | Skip | 0% |
| SELL | DUMPING | Full exit | 100% |
| SELL | NEUTRAL | Half exit | 50% |
| SELL | ACCUMULATING | Partial exit | 50% |
[!NOTE] With no whale wallets configured (or while the tracker is still collecting snapshots), trades pass at full size. The matrix only applies once wallets are set up and at least two snapshots exist.
In bearish regimes, all position sizes drop an additional 50%. Protective exits (stop-loss, take-profit, trailing stop) always execute at 100%.
The market is classified from the SOL/USDC daily trend (20-day SMA) and DEX volume, and entries are scaled accordingly:
| Regime | Condition | Position Modifier |
|---|---|---|
| BULLISH | Price above 20-day SMA + rising volume | 1.0x |
| NEUTRAL | Mixed signals | 1.0x |
| BEARISH | Price below 20-day SMA + falling volume | 0.5x |
This feature is enabled by setting "market_regime_enabled": true in config.json.
The breaker polls social sentiment from Reddit for the tracked tokens and pauses trading when sentiment collapses:
- Token pause — a token is blocked when its score drops below
sentiment_threshold. - Market crash — all new entries pause when every tracked token is below
sentiment_crash_threshold. - Recovery — blocks expire automatically after
sentiment_pause_hours.
This feature is enabled by setting "sentiment_enabled": true in config.json.
Note
Strategy names must be a single word, lowercase — momentum, trendline, etc.
- Create
strategies/{name}_strategy.py. - Define a class
{Name}Strategy(BaseStrategy)with the following methods:__init__(self, df)— storeself.dfand set the risk parametersstoploss,takeprofit,trailing_stoploss, andtrailing_stoploss_target(percentages).apply_strategy(self)— compute indicators, then setself.df["entry"] = 1on bars that should buy andself.df["exit"] = 1on bars that should sell.
- Set
"strategy": "{name}"inconfig.json.
Indicators (ema, sma, rsi) are available from sol_trade.strategy — pure-Python, TA-Lib-equivalent implementations.
Example — a momentum strategy
# strategies/momentum_strategy.py
import pandas as pd
from sol_trade.strategy import ema, rsi
from .base_strategy import BaseStrategy
class MomentumStrategy(BaseStrategy):
def __init__(self, df: pd.DataFrame):
self.df = df
self.stoploss = 5
self.takeprofit = 10
self.trailing_stoploss = 2
self.trailing_stoploss_target = 5
def apply_strategy(self):
self.df["ema_fast"] = ema(self.df["close"], 8)
self.df["ema_slow"] = ema(self.df["close"], 21)
self.df["rsi"] = rsi(self.df["close"], 14)
entry = (self.df["ema_fast"] > self.df["ema_slow"]) & (self.df["rsi"] <= 40)
exit_ = (self.df["ema_fast"] < self.df["ema_slow"]) | (self.df["rsi"] >= 70)
self.df.loc[entry, "entry"] = 1
self.df.loc[exit_, "exit"] = 1
return self.df[!IMPORTANT] The loader already picks the class for the configured strategy name, so
apply_strategymust not be gated onconfig().strategy— a guard likeif config().strategy == "momentum"silently no-ops when the strategy is assigned per-token viatoken_strategies.
New strategies may be contributed via pull request.
What happens if I stop the bot while I'm holding a position?
Your open position is saved to data/{TOKEN}_data.csv. On restart, the bot resumes managing its stop-loss and take-profit.
Do I need a Jupiter API key?
No. It is optional and only sent if set — the default swap/v2 endpoint works without one.
Can I trade more than one token?
Yes. Add each token to secondary_mints (and its symbol to secondary_mint_symbols) and SolTrade trades them all in the same loop.
Where is my private key stored?
Only in the .env file on your machine. The bot loads and signs locally — it is never sent to any server, and .env is git-ignored.
| Term | Meaning |
|---|---|
| Primary mint | The token you trade with, usually a stablecoin like USDC |
| Secondary mint | The token you trade for, e.g. SOL |
| Trading interval | Minutes between each technical analysis pass |
| Price update interval | Seconds between price refreshes |
| Slippage | Difference between expected and executed trade price |
| BPS | Basis points — 100 BPS = 1% |
| Whale | A wallet holding a large amount of a token |
