In this guide, we’ll build Hedge — an ambient app that tracks stock prices and posts updates to your feed on a schedule.
Source Code View the complete Hedge app on GitHub
What We’re Building
Hedge fetches stock prices from Alpha Vantage and posts updates to your Truffle feed every 2 hours during market hours.
Features:
Configurable stock tickers via environment variables
Price charts generated dynamically
Runs on a schedule (only during specified hours)
Project Structure
hedge/
├── app.py # Main application code
├── truffile.yaml # App configuration
└── icon.png # App icon (optional)
Step 1: Create the Config File
Create truffile.yaml to define your app:
metadata :
name : Hedge
type : background
description : |
Track your stock portfolio with live price updates and charts in your feed.
process :
cmd :
- python
- app.py
working_directory : /
environment :
PYTHONUNBUFFERED : "1"
HEDGE_TICKERS : "AAPL,MSFT,GOOGL"
icon_file : ./icon.png
default_schedule :
type : interval
interval :
duration : "2hr"
schedule :
daily_window : "06:00-22:00"
files :
- source : ./app.py
destination : ./app.py
run : |
pip install requests
pip install gourmet[ambient]
Key Configuration
Field Description type: backgroundMakes this an Ambient app default_scheduleWhen and how often to run environmentEnvironment variables your app can read filesFiles to copy into the container runShell commands to install dependencies
Schedule Options
Interval — Run every N minutes/hours:
default_schedule :
type : interval
interval :
duration : "10m" # 10 minutes
Times — Run at specific times:
default_schedule :
type : times
times :
run_times : [ "09:00" , "12:00" , "18:00" ]
Step 2: Write the App
Create app.py:
import os
import requests
from datetime import datetime
from gourmet.ambient import run_ambient, AmbientContext
# Read config from environment variables
API_KEY = os.getenv( "ALPHAVANTAGE_API_KEY" , "demo" )
TICKERS = os.getenv( "HEDGE_TICKERS" , "AAPL,MSFT" ).split( "," )
BASE_URL = "https://www.alphavantage.co/query"
def fetch_stock_data ( symbol : str ) -> dict | None :
"""Fetch daily stock data from Alpha Vantage."""
try :
resp = requests.get( BASE_URL , params = {
"function" : "TIME_SERIES_DAILY" ,
"symbol" : symbol.strip().upper(),
"outputsize" : "compact" ,
"apikey" : API_KEY ,
}, timeout = 30 )
resp.raise_for_status()
data = resp.json()
if "Time Series (Daily)" not in data:
return None
return data
except Exception as e:
print ( f "Failed to fetch { symbol } : { e } " )
return None
def hedge_ambient ( ctx : AmbientContext):
"""Main ambient function — runs on schedule."""
for symbol in TICKERS :
symbol = symbol.strip().upper()
if not symbol:
continue
# Fetch the data
data = fetch_stock_data(symbol)
if not data:
continue
# Parse the response
ts = data[ "Time Series (Daily)" ]
latest_date = sorted (ts.keys(), reverse = True )[ 0 ]
latest = ts[latest_date]
price = float (latest[ "4. close" ])
open_price = float (latest[ "1. open" ])
change = price - open_price
change_pct = (change / open_price) * 100
arrow = "📈" if change >= 0 else "📉"
# Format the content
title = f " { arrow } { symbol } : $ { price :.2f} "
body = f """
{ symbol } Stock Update
Price: $ { price :.2f}
Change: { '+' if change >= 0 else '' }{ change :.2f} ( { change_pct :+.2f} %)
""" .strip()
# Post to feed
ctx.bg.post_to_feed(
title = title,
body = body,
src_uri = f "https://finance.yahoo.com/quote/ { symbol } " ,
content_timestamp = datetime.now()
)
if __name__ == "__main__" :
run_ambient(hedge_ambient)
Step 3: Understanding the Code
The Ambient Pattern
Every ambient app follows this pattern:
from gourmet.ambient import run_ambient, AmbientContext
def my_app ( ctx : AmbientContext):
# Your logic here
ctx.bg.post_to_feed( ... )
if __name__ == "__main__" :
run_ambient(my_app)
The post_to_feed() Function
This is how you post content to your feed:
ctx.bg.post_to_feed(
title = "Card Title" , # Required: shown prominently
body = "Card content..." , # Required: main text
src_uri = "https://..." , # Optional: link to source
media_uris = [ "https://..." ], # Optional: list of image URLs
content_timestamp = datetime.now() # Optional: when content was created
)
Parameter Type Required Description titlestr Yes Card title shown in feed bodystr Yes Main content text src_uristr No Link to original source media_urislist[str] No Image URLs to display content_timestampdatetime No When the content was created
Reading Configuration
Use environment variables for configuration:
# In truffile.yaml
environment:
MY_SETTING : "value"
# In app.py
import os
my_setting = os.getenv( "MY_SETTING" , "default" )
API Reference
gourmet.ambient Module
Import the ambient runtime:
from gourmet.ambient import run_ambient, AmbientContext
run_ambient(func)
The entry point for your ambient app. Call this in your __main__ block.
def my_app ( ctx : AmbientContext):
# Your app logic
pass
if __name__ == "__main__" :
run_ambient(my_app)
Parameter Type Description funcCallable[[AmbientContext], None]Your main app function
AmbientContext
The context object passed to your ambient function. Provides access to Truffle services.
def my_app ( ctx : AmbientContext):
# Access the background service
ctx.bg.post_to_feed( ... )
Property Type Description ctx.bgBackgroundServiceService for posting to the feed
ctx.bg.post_to_feed()
Posts a card to your Truffle feed.
ctx.bg.post_to_feed(
title = "Card Title" ,
body = "Card content text..." ,
src_uri = "https://example.com/source" ,
media_uris = [ "https://example.com/image.png" ],
content_timestamp = datetime.now()
)
Parameter Type Required Description titlestrYes Card title displayed prominently bodystrYes Main content text (supports markdown) src_uristrNo URL to the original source (opens when tapped) media_urislist[str]No List of image URLs to display content_timestampdatetimeNo When the content was created
Images : Use publicly accessible URLs for media_uris. The images will be displayed in the feed card.
Step 4: Deploy Your App
Once your app is ready, deploy it to your Truffle:
Truffile will:
Validate your truffile.yaml
Check Python syntax
Upload files to your Truffle
Run the install commands
Register the app with the scheduler
CLI Reference See all deploy options including interactive mode
Tips
Test locally before deploying by running your script directly:Note: post_to_feed() won’t work locally, but you can test your data fetching logic.
Rate limits : Be mindful of API rate limits. The Alpha Vantage free tier for example allows 25 requests/day. Adjust your schedule accordingly.
Next Steps
SDK Source Code
truffile SDK View the complete SDK source code, including the CLI, client library, and inference proxy.