Integrating Machine Learning and Python AI Agents

 

In the first part of this series (or here for non subscribers), we built a Bitcoin trading bot that listens to trading signals (for example from a Discord channel) and executes trades on LN Markets. The core pieces were:

  • Signal ingestion from Discord (or any other alert source).
  • Order execution via the LN Markets REST API.
  • Persistence of trades into a local SQLite database to avoid duplicate executions.

In this second part, we’ll evolve that architecture into something more intelligent: a bot that keeps your existing trading strategy logic, but delegates parts of your strategy to a Python-based machine learning (ML) agent.

This is a pattern you can reuse:

  • Keep your entry strategy and core trading logic in Node.js.
  • Let a separate Python agent learn from your historical trades.
  • Have the agent produce data-driven parameters your bot can consume at runtime.

1. From Static Rules to Data‑Driven Parameters

In a typical first version of a trading bot, some parameters are hard-coded or stored in a config file:

  • Fixed protective distances per timeframe.
  • One-size-fits-all trailing behavior.
  • Limited awareness of how different indicators or regimes behave.

This is easy to implement but has clear limitations:

  • It doesn’t adapt when the market regime changes.
  • It can’t tell that some indicator/timeframe combinations consistently behave better or worse.
  • You end up tuning numbers manually, often with incomplete feedback.

The architecture we’ll discuss keeps your discretionary or rule-based strategy intact, but adds a feedback loop:

  1. Log trades and price behavior into a database.
  2. Train a Python ML model over those trades.
  3. Write out a compact JSON file with regime-specific parameter guidance.
  4. Have the Node.js bot read that JSON and adjust parameters accordingly.

The result is a bot that is still your strategy, but with a data-informed parameter layer wrapping it.

2. High-Level Architecture

Let’s zoom out. The evolved system has four main components:

  • Signal source: Discord messages, TradingView webhooks, or any alerting mechanism that produces structured signals.
  • Node.js trading bot (execution engine)
 Parses incoming signals.
Decides whether a new trade should be opened or an existing one closed.
Calls LN Markets via ln-markets/api.
Persists trade metadata into trades.db.
  • SQLite database (trades.db)
Stores each trade along with metadata such as indicator, timeframe, trade type, and price information.
Accumulates realized statistics about how trades behave once they are opened.
Python parameter ML agent
Runs periodically (e.g., hourly) in its own virtual environment.
Loads historical trades from trades.db.
Engineers features and trains an ML model.
Writes out a JSON file (e.g., params.json) containing per‑regime parameters.
The Node bot reloads this JSON and uses it as an overlay on top of your existing config.

Conceptually, the pipeline looks like this:

Signals → Node Bot → LN Markets & trades.db
trades.db → Python ML Agent → params.json → Node Bot (param overlay)

3. Instrumenting Your Bot: Logging the Right Data

Before bringing ML into the picture, your bot needs to produce the raw material the agent will learn from.

In the Node.js side you already have:

  • A trade creation path that:
  • Receives a signal from Discord or another source.
  • Decides whether to open a long or short.
  • Sends the order to LN Markets (for example with futuresNewTrade).
  • Writes the trade to trades.db (ID, timestamps, prices, indicator, timeframe, trade type).

To support ML-driven parameter management, add or ensure:

  • Regime identifiers
    For each trade, log fields such as:
  • Indicator name (e.g., “MACD” or similar).
  • Timeframe (e.g., 15m, 1h, 4h).
  • Trade type (e.g., long/short).
  • A simple market regime flag (e.g., “downtrend” vs “not downtrend”).
  • Price behavior after entry
    Your bot can run a frequent cron (e.g., every 30 seconds) that:
Fetches open trades from LN Markets.
Tracks the min and max price each trade sees while it is open.
Persists these “extreme” prices back into the database as simple summary statistics.

This is enough for a Python agent to reconstruct:

  • How much adverse movement trades typically experience after entry.
  • How much favorable movement is common by regime.
  • How different regimes (indicator + timeframe + trend flag) differ in realized behavior.

4. The Python Parameter ML Agent

The Python agent‘s responsibilities are:

  1. Read historical trades from SQLite
  • Connect to trades.db 
  • Query trades with the fields logged earlier:
  • Indicator, timeframe, trade type.
  • Regime flag(s).
  • Entry and protective prices.
  • Optionally, stored min/max excursion prices.

def main():
"""Main execution function."""
print("=" * 60)
print("AI Trading Bot Parameter Generator")
print("=" * 60)

# Resolve paths
db_path = Path(__file__).parent / DB_PATH
output_path = Path(__file__).parent / OUTPUT_PATH

if not db_path.exists():
print(f"ERROR: Database not found at {db_path}")
return 1

# Ensure output directory exists
output_path.parent.mkdir(parents=True, exist_ok=True)

# Load data
df = load_trade_data(str(db_path))

2. Engineer features: Typical features include:

  • Categorical / one‑hot:
Indicator.
Timeframe.
Trade type (long vs short).
  • Numerical:
Regime flags (e.g., a boolean for trending vs ranging).
Distance to a moving average at entry.
Whether the entry was near a recent local high/low.
Simple time-of-day or day-of-week information.
def engineer_features(df):
"""Engineer features for ML model."""
# Create derived features
df['ma_distance'] = np.where(
df['movingAverage'].notna() & (df['entryprice'] != 0),
(df['entryprice'] - df['movingAverage']) / df['entryprice'],
0
)

df['local_bottom_distance'] = np.where(
df['localBottom'].notna() & (df['entryprice'] != 0),
(df['entryprice'] - df['localBottom']) / df['entryprice'],
0
)

df['local_top_distance'] = np.where(
df['localTop'].notna() & (df['entryprice'] != 0),
(df['localTop'] - df['entryprice']) / df['entryprice'],
0
)

print(f"Engineered features for {len(df)} trades")
return df

3. Train an ML model: The agent can use a tree-based regressor such as XGBoost (or any library in your stack) to learn a mapping from:

  • (indicator, timeframe, regime, trade type, features)
    → recommended parameters (you decide the exact definition).
def train_model(df):
"""Train XGBoost model """
print("Training XGBoost model...")

# Define target and features
target = 'sl_pct'
exclude_cols = [
target,
'entryprice',
'stoploss',
'takeprofit',
'profit',
'movingAverage',
'localBottom',
'localTop',
'features',
]

feature_cols = [col for col in df.columns if col not in exclude_cols]

X = df[feature_cols]
y = df[target]

# Split data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)

# Train model
model = XGBRegressor(
n_estimators=100,
max_depth=5,
learning_rate=0.1,
random_state=42,
objective='reg:squarederror'
)

model.fit(X_train, y_train)

# Evaluate
y_pred = model.predict(X_test)
mae = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))

print(f"Model trained successfully!")
print(f" MAE: {mae:.4f}%")
print(f" RMSE: {rmse:.4f}%")
print(f" Feature importance (top 5):")

feature_importance = sorted(
zip(feature_cols, model.feature_importances_),
key=lambda x: x[1],
reverse=True
)[:5]

for feat, importance in feature_importance:
print(f" {feat}: {importance:.4f}")

return model, feature_cols
  1. Importantly, this model is never called directly from the Node.js bot at runtime.
    Instead, the agent runs offline, finishes training, and then produces a static JSON artifact.

4. Aggregate by regime and write JSON: Rather than storing one prediction per trade, the agent:

  • Groups trades by (indicator, timeframe, regime flag, trade type).
  • Computes aggregates per group, such as:
  • How trades in that regime have historically behaved.
  • A recommended set of parameters for that regime.

The output is written to something like:

  • analysis/params.json

Structured as a nested object keyed by:

  • Indicator → timeframe → regime key → trade type.

Each leaf contains statistics and recommended parameters.
Again, this is your design: you decide what “parameter” means; the pattern here is only about how to export and consume it.

5. Calling the Python Agent from Node.js

 

On the Node.js side, you integrate the Python agent as a background job.

  1. Define a helper to run the agent: A helper function builds a command that:
  • Points to the Python interpreter inside the agent’s virtual environment.
  • Calls the training script.
  • Sets the working directory to the agent folder.
  • Captures and logs stdout / stderr.
  • Treats any non‑zero exit as a failure and logs it.

This keeps the ML agent separate from your trading loop. The trading bot only cares that a JSON file appears when training succeeds.

2. Schedule periodic retraining: Using a scheduler like node-cron, you can:

  • Run the agent hourly (or at any cadence you choose).
  • After each run:
Reload the params.json file into memory.
  1. If anything goes wrong (Python error, data problem, etc.), your bot can simply continue using the previous JSON or fall back to static config. That way the ML agent can fail safely without interrupting live trading.

6. Using ML Output Inside the Trading Bot

 

With params.json successfully loaded, the Node.js bot can expose a couple of helper functions that act as a thin translation layer between:

  • The ML agent’s JSON schema, and
  • The rest of your trading logic.

Typical patterns:

  • Lookup helper for parameters. A function that takes:
  • Indicator.
  • Timeframe.
  • Regime flag(s).
  • Trade type.

Then:

  • Attempts to find a matching entry in params.json.
  • If found, returns the recommended parameters for that regime.
  • If not found, returns your existing config-based defaults.

This ensures backwards compatibility: you can always turn off the ML overlay or let it “fill in” only for regimes with enough data.

  • The key point: the structure and decisions about your trading algo stay in Node.js;
    the numbers feeding into those decisions can be learned and updated automatically by the Python agent.

7. Safety, Monitoring, and Versioning

Whenever you plug ML into a live trading system, you need guardrails. Some important patterns:

  • Safe fallbacks
  • Never overwrite the previous JSON on failed training runs.
  • If loading JSON fails, keep running with the last known-good version or static config values.
  • Config switch
  • Add a feature flag (for example in your existing config file) to:
  • Enable or disable ML-derived parameters.
  • Allow quick rollback to pure config behavior.
  • Logging
  • For each trade, log:
  • Whether the parameters came from ML or from static config.
  • Which regime key was used.
  • The model version (if included in JSON metadata).
  • Model metadata
  • Store simple metadata inside the JSON:
  • Training end timestamp.
  • Number of samples used.
  • Model version identifier (for example a Git commit or semantic version).
  • Retraining policy
  • Decide how often to retrain (e.g. daily or weekly).
  • Optionally use a rolling window of recent trades (e.g. last few months) if you want the model to focus on current market conditions.

These patterns are generic and apply to any trading strategy, not just the one in this bot.

8. Conclusion

In Part 1 we focused on connecting a Node.js bot to LN Markets and executing trades from external signals. In this Part 2, we extended that architecture with a Python ML agent that learns from your historical trades and feeds the bot with regime-aware parameters via a simple JSON interface.

The critical design choices were:

  • Keep the trading engine and the ML agent loosely coupled.
  • Use trades.db as the shared source of truth.
  • Expose ML output only through a small, versioned JSON file.
  • Maintain fallbacks and observability at every step.

From here, you can extend the same pattern to:

  • Tune entry thresholds offline.
  • Introduce ML-assisted filters for trade quality.
  • Add dynamic position sizing based on regime performance.

All of this leverages machine learning and Python AI agents to make your trading bot more adaptive and data-driven.