<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrew</title>
    <description>The latest articles on DEV Community by Andrew (@andrew_52469d4b1f1f849304).</description>
    <link>https://dev.to/andrew_52469d4b1f1f849304</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2650127%2F38c640f9-fe04-4586-988d-f36d1e7da915.png</url>
      <title>DEV Community: Andrew</title>
      <link>https://dev.to/andrew_52469d4b1f1f849304</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrew_52469d4b1f1f849304"/>
    <language>en</language>
    <item>
      <title>How I Built a Bitcoin Trading Bot in Rust (With Live Execution on AWS)</title>
      <dc:creator>Andrew</dc:creator>
      <pubDate>Fri, 03 Jan 2025 08:37:53 +0000</pubDate>
      <link>https://dev.to/andrew_52469d4b1f1f849304/how-i-built-a-bitcoin-trading-bot-in-rust-with-live-execution-on-aws-3k9j</link>
      <guid>https://dev.to/andrew_52469d4b1f1f849304/how-i-built-a-bitcoin-trading-bot-in-rust-with-live-execution-on-aws-3k9j</guid>
      <description>&lt;p&gt;I recently built a Bitcoin trading bot using Rust that:&lt;br&gt;
✅ Fetches BTC price data from the TAAPI API.&lt;br&gt;
✅ Stores historical market data in PostgreSQL (AWS RDS).&lt;br&gt;
✅ Uses EMA 9, EMA 50, and RSI indicators to detect trends.&lt;br&gt;
✅ Executes real trades via limit orders (with stop-loss &amp;amp; take-profit).&lt;br&gt;
✅ Runs every minute on a cron job inside an EC2 instance.&lt;br&gt;
✅ Logs trade performance into AWS QuickSight for monitoring&lt;/p&gt;

&lt;p&gt;Rust’s speed, reliability, and async capabilities made it an excellent choice for a real-time trading system. In this post, I’ll walk through how I built it, the technical decisions, and key challenges.&lt;/p&gt;

&lt;p&gt;🛠️ Tech Stack Overview&lt;/p&gt;
&lt;h2&gt;
  
  
  Component             |               Technology Used
&lt;/h2&gt;

&lt;p&gt;Programming Language:            Rust 🦀&lt;br&gt;
Data Source (API):           TAAPI (Technical Analysis API)&lt;br&gt;
Database:                    PostgreSQL (AWS RDS)&lt;br&gt;
Order Execution:                     API Requests&lt;br&gt;
Hosting:                             AWS EC2 (Ubuntu)&lt;br&gt;
Scheduling:                  Cron Job (Runs Every Minute)&lt;br&gt;
Monitoring:                  AWS QuickSight (Trade Log Metrics)&lt;br&gt;
Async Execution:                     Tokio&lt;/p&gt;

&lt;p&gt;🔍 Trading Strategy&lt;br&gt;
This bot makes trading decisions using a trend-following strategy based on:&lt;br&gt;
✔️ Exponential Moving Averages (EMA) → Fast (9) vs. Slow (50)&lt;br&gt;
✔️ Relative Strength Index (RSI) → Measures overbought/oversold conditions&lt;br&gt;
✔️ Market Trend Detection → Compares recent BTC price movements&lt;/p&gt;

&lt;p&gt;📈 Trade Signal Logic&lt;/p&gt;
&lt;h2&gt;
  
  
  Condition                 |                  Action Taken
&lt;/h2&gt;

&lt;p&gt;EMA 9 crosses above EMA 50 + Bullish Trend:    Enter Long&lt;br&gt;
EMA 9 crosses below EMA 50 + Bearish Trend:    Enter Short&lt;br&gt;
RSI above 70:                                 Avoid Long (Overbought)&lt;br&gt;
RSI below 30:                                 Avoid Short (Oversold)&lt;/p&gt;

&lt;p&gt;Risk Management:&lt;br&gt;
• Stop-loss: -5%&lt;br&gt;
• Take-profit: +10%&lt;/p&gt;

&lt;p&gt;Instead of market orders, I use limit orders to reduce slippage.&lt;/p&gt;

&lt;p&gt;📡 Data Collection: Storing Market Data in PostgreSQL&lt;br&gt;
The bot fetches BTC price data from the TAAPI API every minute and stores it in PostgreSQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub async fn upsert_historical_data(
    client: &amp;amp;Client,
    symbol: &amp;amp;str,
    price: f32,
    timestamp: NaiveDateTime,
    ema9: f32,
    ema50: f32,
    rsi: f32,
) -&amp;gt; Result&amp;lt;u64, Error&amp;gt; {
    let query = "
        INSERT INTO trading_bot.stg.historical_data (
            symbol, price, timestamp, ema9, ema50, rsi
        )
        VALUES ($1, $2, $3, $4, $5, $6)
        ON CONFLICT (symbol, timestamp) 
        DO UPDATE SET
            price = EXCLUDED.price,
            ema9 = EXCLUDED.ema9,
            ema50 = EXCLUDED.ema50,
            rsi = EXCLUDED.rsi;
    ";

    let rows_affected = client
        .execute(query, &amp;amp;[&amp;amp;symbol, &amp;amp;price, &amp;amp;timestamp, &amp;amp;ema9, &amp;amp;ema50, &amp;amp;rsi])
        .await?;

    Ok(rows_affected)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📌 Why Upsert?&lt;br&gt;
This ensures that historical data is updated without duplication and serves essential for evaluating long and short entry points.&lt;/p&gt;

&lt;p&gt;📊 Trade Signal Detection: Checking Market Trends&lt;br&gt;
The bot checks if a trend is forming using SQL-based logic.&lt;/p&gt;

&lt;p&gt;Code Snippet: Detecting Buy/Sell Signals&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub async fn get_latest_signal(client: &amp;amp;Client) -&amp;gt; Result&amp;lt;Option&amp;lt;Row&amp;gt;, SignalError&amp;gt; {
    let signal_query = "
        with lookback_data as (
        select
            id,
            symbol,
            price as close,
            timestamp,
            ema9,
            ema50,
            rsi,
            lag(price, 48) over (partition by symbol order by timestamp) as lookback_price
        from
            trading_bot.stg.historical_data
        ),
        price_change as (
            select
                *,
                ((close - lookback_price) / nullif(lookback_price, 0)) * 100 as price_change_per
            from
                lookback_data
        ),
        market_trend as (
            select
                *,
                case
                    when price_change_per &amp;gt; (0.02 * 100) and close &amp;gt; ema50 then 'bullish'
                    when price_change_per &amp;lt; -(0.02 * 100) and close &amp;lt; ema50 then 'bearish'
                    else 'consolidating'
                end as market_trend
            from
                price_change
        ),
        signals as (
            select
                *,
                case
                    when market_trend = 'bullish' and ema9 &amp;gt; ema50 and rsi &amp;lt; 70 then 'long'
                    when market_trend = 'bearish' and ema9 &amp;lt; ema50 and rsi &amp;gt; 30 then 'short'
                    else null
                end as signal
            from
                market_trend
        )
        select id, symbol, signal, close as price, timestamp, market_trend, ema9, ema50, rsi
        from signals
        where signal is not null
        order by timestamp desc
        limit 1;
    ";

    let result = client.query(signal_query, &amp;amp;[]).await.map_err(SignalError::from)?;

    if let Some(row) = result.into_iter().next() {
        Ok(Some(row))
    } else {
        Err(SignalError::NoSignalError)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📌 Key Optimizations:&lt;br&gt;
• Uses window functions to analyze past data.&lt;br&gt;
• Ensures signals are based on recent price movements.&lt;/p&gt;

&lt;p&gt;💰 Executing Trades: API Request to Buy/Sell&lt;br&gt;
If a trade signal is detected, the bot places a limit order via API.&lt;/p&gt;

&lt;p&gt;Code Snippet: Executing a Trade&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pub async fn execute_trade(symbol: &amp;amp;str, action: &amp;amp;str, price: f32) -&amp;gt; Result&amp;lt;(), reqwest::Error&amp;gt; {
    let client = reqwest::Client::new();
    let order_payload = json!({
        "symbol": symbol,
        "side": action,
        "type": "LIMIT",
        "price": price,
        "quantity": 1.0
    });

    let response = client.post("https://api.exchange.com/order")
        .json(&amp;amp;order_payload)
        .send()
        .await?;

    println!("Trade executed: {:?}", response.text().await?);
    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📌 Why Limit Orders?&lt;br&gt;
Market orders are vulnerable to slippage, so I set a limit price to execute only at a desired level.&lt;/p&gt;

&lt;p&gt;🖥️ Deployment on AWS&lt;br&gt;
The bot runs on an EC2 instance (Ubuntu) with a cron job:&lt;br&gt;
&lt;code&gt;* * * * * /usr/bin/cargo run --release &amp;gt;&amp;gt; bot.log 2&amp;gt;&amp;amp;1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;📌 Why EC2?&lt;br&gt;
• Persistent uptime&lt;br&gt;
• Low-cost scalability&lt;br&gt;
• Easy PostgreSQL RDS integration&lt;/p&gt;

&lt;p&gt;📊 Performance Monitoring in AWS QuickSight&lt;/p&gt;

&lt;p&gt;To track performance, I log trades into AWS QuickSight, displaying:&lt;br&gt;
✅ Trade win rate&lt;br&gt;
✅ Balance growth (%)&lt;br&gt;
✅ Average trade duration&lt;/p&gt;

&lt;p&gt;📌 Why QuickSight?&lt;br&gt;
• Real-time performance insights&lt;br&gt;
• Can export custom reports&lt;/p&gt;

&lt;p&gt;⚠️ Challenges &amp;amp; Lessons Learned&lt;/p&gt;

&lt;p&gt;1️⃣ Handling API Rate Limits&lt;br&gt;
• Used exponential backoff and retry logic.&lt;br&gt;
• Delayed requests when hitting rate limits.&lt;/p&gt;

&lt;p&gt;2️⃣ Testing with Historical Data&lt;br&gt;
• Backtested against 2019-2024 BTC, ETH, SOL.&lt;br&gt;
• Showed ~50% performance improvement over buy-and-hold strategies.&lt;/p&gt;

&lt;p&gt;🚀 What’s Next?&lt;br&gt;
✔️ Adding multi-asset support (ETH, SOL, BNB).&lt;br&gt;
✔️ Implementing machine learning-based signals.&lt;br&gt;
✔️ Deploying as a serverless function (AWS Lambda).&lt;/p&gt;

&lt;p&gt;💬 What do you think? Have you built a Rust trading bot before? Let’s discuss in the comments! 👇&lt;/p&gt;

</description>
      <category>rust</category>
      <category>postgres</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
