Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod analyze;
pub mod init;
mod orders;
pub mod quote;
pub mod run;
mod status;

use crate::cli::analyze::AnalyzeArgs;
use crate::cli::init::InitArgs;
use crate::cli::orders::OrdersArgs;
use crate::cli::quote::QuoteArgs;
use crate::cli::run::RunCommandArgs;
Expand All @@ -21,6 +23,8 @@ pub struct Cli {
pub enum Command {
#[command(about = "Analyze stocks")]
Analyze(AnalyzeArgs),
#[command(about = "Generate a starter config file")]
Init(InitArgs),
#[command(about = "Fetch recent orders")]
Orders(OrdersArgs),
#[command(about = "Fetch quote")]
Expand Down
20 changes: 20 additions & 0 deletions src/cli/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use clap::{Args, ValueEnum};
use std::path::PathBuf;

#[derive(Args, Debug)]
pub struct InitArgs {
/// Type of config to generate (greed, strategy, agent)
#[arg(value_name = "TYPE", value_enum)]
pub config_type: InitConfigType,

/// Path to write the template file (defaults to current directory)
#[arg(value_name = "PATH")]
pub path: Option<PathBuf>,
}

#[derive(Clone, Debug, ValueEnum)]
pub enum InitConfigType {
Greed,
Strategy,
Agent,
}
14 changes: 12 additions & 2 deletions src/config/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ impl AgentProvider {

fn resolve_env_var_url(url: &str) -> Result<String, GreedError> {
if let Some(var_name) = url.strip_prefix('$') {
Ok(env::var(var_name)?)
env::var(var_name).map_err(|_| {
GreedError::new(&format!(
"agent url environment variable '{}' is not set",
var_name
))
})
} else {
Ok(url.to_string())
}
Expand Down Expand Up @@ -173,7 +178,12 @@ mod tests {
url: "$GREED_NONEXISTENT_VAR_XYZ".to_string(),
model: "llama3".to_string(),
};
assert!(provider.resolve_env_vars().is_err());
let err = provider.resolve_env_vars().unwrap_err();
assert!(
err.to_string()
.contains("agent url environment variable 'GREED_NONEXISTENT_VAR_XYZ' is not set"),
"unexpected error message: {err}"
);
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod run;
mod statistics;
mod strategy;
mod tactic;
pub mod template;
mod trading_days;

pub async fn greed_loop(args: GreedRunnerArgs) -> Result<(), GreedError> {
Expand Down
28 changes: 28 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use clap::{CommandFactory, Parser};
use log::LevelFilter;
use simplelog::{ColorChoice, CombinedLogger, Config, ConfigBuilder, TermLogger, TerminalMode};

use greed::error::GreedError;
use greed::platform::args::PlatformArgs;
use greed::template;
use greed::{analyze_stocks, fetch_quote, fetch_recent_orders, fetch_status, greed_loop};

use crate::cli::{Cli, Command};
Expand All @@ -22,6 +24,12 @@ async fn async_main(log_config: Config) {
let cli = Cli::parse();
let command = cli.command;
match command {
Command::Init(args) => {
generate_config_template(args)
.await
.expect("config template generation failed");
}

Command::Analyze(args) => {
analyze_stocks(
&args.symbols,
Expand Down Expand Up @@ -64,6 +72,26 @@ async fn async_main(log_config: Config) {
}
}

async fn generate_config_template(args: cli::init::InitArgs) -> Result<(), GreedError> {
use cli::init::InitConfigType;

let (tmpl, filename) = match args.config_type {
InitConfigType::Greed => (template::greed_config_template(), "greed.toml"),
InitConfigType::Strategy => (template::strategy_config_template(), "strategy.toml"),
InitConfigType::Agent => (template::agent_config_template(), "agent.toml"),
};

let output_path = match args.path {
Some(p) if p.is_dir() => p.join(filename),
Some(p) => p,
None => std::env::current_dir()?.join(filename),
};

tokio::fs::write(&output_path, tmpl).await?;
println!("Wrote template to {}", output_path.display());
Ok(())
}

fn create_log_config() -> Config {
ConfigBuilder::new()
.set_time_offset_to_local()
Expand Down
94 changes: 94 additions & 0 deletions src/template.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
pub fn greed_config_template() -> &'static str {
r#"# Greed configuration file

# Platform to use for trading. Currently only "alpaca" is supported.
platform = "alpaca"

# How often (in seconds) to run the trading loop.
interval = 60

# Strategies allow you to compose multiple tactic configs together.
# Each strategy references a file path or agent config, and gets a share of your portfolio.
# [[strategies]]
# name = "My Strategy"
# portfolio_percent = 100.0 # Percentage of portfolio allocated to this strategy
# path = "strategy.toml" # Path to a local tactic config file
# # OR use an AI agent strategy:
# # agent_path = "agent.toml"

# Tactics define buy/sell rules for individual assets.
# [[tactics]]
# name = "ETF"
# [tactics.buy]
# for = { stock = "VTI" }
# when = { below_median_percent = 5.0, median_period = "month" }
# do = { buy_percent = 10.0 }
# [tactics.sell]
# for = { stock = "VTI" }
# when = { gain_above_percent = 5.0 }
# do = { sell_all = true }
"#
}

pub fn strategy_config_template() -> &'static str {
r#"# Strategy configuration file
# A strategy defines buy/sell tactics for one or more assets.

# Platform to use for trading. Currently only "alpaca" is supported.
platform = "alpaca"

# How often (in seconds) to run the trading loop.
interval = 60

# Each [[tactics]] block defines buy/sell rules for one asset.
[[tactics]]
name = "VTI"

[tactics.buy]
for = { stock = "VTI" }
when = { below_median_percent = 1.0 }
do = { buy_percent = 25 }

[tactics.sell]
for = { stock = "VTI" }
when = { gain_above_percent = 1.0 }
do = { sell_all = true }
"#
}

pub fn agent_config_template() -> &'static str {
r#"# Agent configuration file
# An agent uses an AI model to make trading decisions.

# The system prompt that describes the agent's trading strategy and behavior.
prompt = "You are a trading agent. Analyze the current portfolio and market conditions, then decide whether to buy or sell."

# Provider configuration for the AI model.
[agent_provider]
# Provider type. Currently only "Ollama" is supported.
type = "Ollama"
# URL of the Ollama server. Can be a literal URL or an environment variable (e.g. "$OLLAMA_URL").
url = "http://localhost:11434"
# The model to use (e.g. "llama3", "mistral").
model = "llama3"

# Optional allowlist of stock symbols the agent is permitted to trade.
# If empty, all symbols are allowed.
# allow = ["VTI", "SPY"]

# Optional denylist of stock symbols the agent is not permitted to trade.
# deny = ["GME", "AMC"]

# Tool permissions — set any to false to disable that capability for the agent.
[tools]
account = true
positions = true
open_orders = true
quotes = true
buy = true
sell = true
web_fetch = true
read_note = true
write_note = true
"#
}
Loading