Building a Local AI Assistant on Linux — Recent Progress on Echo
Last week, I made significant strides in building my local AI assistant, Echo, on my Ubuntu machine. This article covers the recent updates, including how I refined my AI's content strategy, improved my trading bots, and enhanced the session checkpoint system.
2026-04-01 — Publisher Wired to Content Strategy
I've been working on making my content more dynamic and relevant by integrating it with a content strategy file. Here's how I did it:
# echo_devto_publisher.py
import json
import os
def read_content_strategy():
with open('content_strategy.json', 'r') as f:
return json.load(f)
def update_publisher():
strategy = read_content_strategy()
if strategy.get('next'):
next_topic = strategy['next']
# Use next_topic to set content for the publisher
elif strategy.get('queued'):
queued_topic = strategy['queued'][0]
# Use queued_topic to set content for the publisher
else:
# Use generic content for the publisher
pass
update_publisher()
After making this change, I reset my content queue to ensure all my topics are ready to publish. I also deleted any generic articles from March 31 to keep my feed fresh. Next Tuesday, I'll be sharing how I built a two-way phone bridge for my AI using ntfy.sh.
2026-04-01 — Trade Brain v2
I've been working on my trading bots, specifically the Trade Brain, which has seen a few updates. Here are the key changes:
- Increased Position Sizing: I've increased the position size to 10% per trend trade and 8% for momentum trades, with a maximum of 8 positions.
- Added Trailing Stop: This feature protects gains after a 2% upward movement.
- Sector Awareness: The bot now prevents over-concentration in the same sector.
- Updated Watchlists: I've added XOM (energy), IWM (small cap), RKLB, and IONQ to the watchlist.
- Fixed Take Profit: For trend trades, the take profit is set to 5%, and for momentum trades, it's 3%.
The first v2 cycle saw the entry of XOM (energy trend) and RKLB (momentum).
2026-04-01 — Crypto Brain Live
I've also made progress on my Crypto Brain, a 24/7 trading bot for cryptocurrencies. Here are the details:
# core/crypto_brain.py
import alpaca_trade_api as tradeapi
import pandas as pd
import talib
API_KEY = 'your_api_key'
API_SECRET = 'your_api_secret'
BASE_URL = 'https://paper-api.alpaca.markets'
api = tradeapi.REST(API_KEY, API_SECRET, BASE_URL, api_version='v2')
def get_cryptos_data():
assets = ['BTC/USD', 'ETH/USD', 'SOL/USD', 'AVAX/USD']
dfs = [pd.DataFrame(api.get_bars(asset, '1H', limit=720).df) for asset in assets]
return pd.concat(dfs, keys=assets)
def crypto_strategy(df):
indicators = talib.RSI(df['close'], timeperiod=14)
mean_reversion = (indicators < 30) & (df['close'].pct_change() > 0.04)
momentum = (df['close'].pct_change() > 0.06)
return mean_reversion & momentum
data = get_cryptos_data()
trades = data[data.apply(crypto_strategy, axis=1)]
print(trades)
This bot uses the RSI mean reversion strategy combined with a 6-hour momentum check. The take profit is set to 4%, and the stop loss is 2%. The first scan revealed that all coins were in the oversold RSI range (31-33).
2026-04-02 — Session Checkpoint Upgraded
To ensure a smooth session summary, I upgraded the session checkpoint system:
# session_checkpoint.py
import re
def collect_session_focus():
headers = []
with open('session_summary.json', 'r') as f:
session_summary = json.load(f)
if 'override_focus' in session_summary:
return session_summary['override_focus']
headers = [line.strip() for line in f.readlines() if re.match(r'^##\s', line)]
return ' + '.join(headers[:4])
focus = collect_session_focus()
print(focus)
This script now collects all ## headers from the session summary and filters out noise. The focus is then joined into a natural-sounding briefing by the LLM, which speaks the joined focus at 8am.
2026-04-02 — Briefing Fixed — Direct Ollama Call
Finally, I fixed the daily briefing by calling Ollama directly via HTTP:
python
# daily_briefing.py
import requests
def call_ollama():
url = 'https://ollama.com/api/v1/brief'
headers = {'Content-Type': 'application/json'}
data = {
Top comments (0)