Skip to content

Commit 291c402

Browse files
committed
Fixes #55 Adds init command which create config templates
1 parent df5cf81 commit 291c402

6 files changed

Lines changed: 159 additions & 2 deletions

File tree

src/cli.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
pub mod analyze;
2+
pub mod init;
23
mod orders;
34
pub mod quote;
45
pub mod run;
56
mod status;
67

78
use crate::cli::analyze::AnalyzeArgs;
9+
use crate::cli::init::InitArgs;
810
use crate::cli::orders::OrdersArgs;
911
use crate::cli::quote::QuoteArgs;
1012
use crate::cli::run::RunCommandArgs;
@@ -21,6 +23,8 @@ pub struct Cli {
2123
pub enum Command {
2224
#[command(about = "Analyze stocks")]
2325
Analyze(AnalyzeArgs),
26+
#[command(about = "Generate a starter config file")]
27+
Init(InitArgs),
2428
#[command(about = "Fetch recent orders")]
2529
Orders(OrdersArgs),
2630
#[command(about = "Fetch quote")]

src/cli/init.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use clap::{Args, ValueEnum};
2+
use std::path::PathBuf;
3+
4+
#[derive(Args, Debug)]
5+
pub struct InitArgs {
6+
/// Type of config to generate (greed, strategy, agent)
7+
#[arg(value_name = "TYPE", value_enum)]
8+
pub config_type: InitConfigType,
9+
10+
/// Path to write the template file (defaults to current directory)
11+
#[arg(value_name = "PATH")]
12+
pub path: Option<PathBuf>,
13+
}
14+
15+
#[derive(Clone, Debug, ValueEnum)]
16+
pub enum InitConfigType {
17+
Greed,
18+
Strategy,
19+
Agent,
20+
}

src/config/agent.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ impl AgentProvider {
9292

9393
fn resolve_env_var_url(url: &str) -> Result<String, GreedError> {
9494
if let Some(var_name) = url.strip_prefix('$') {
95-
Ok(env::var(var_name)?)
95+
env::var(var_name).map_err(|_| {
96+
GreedError::new(&format!(
97+
"agent url environment variable '{}' is not set",
98+
var_name
99+
))
100+
})
96101
} else {
97102
Ok(url.to_string())
98103
}
@@ -173,7 +178,12 @@ mod tests {
173178
url: "$GREED_NONEXISTENT_VAR_XYZ".to_string(),
174179
model: "llama3".to_string(),
175180
};
176-
assert!(provider.resolve_env_vars().is_err());
181+
let err = provider.resolve_env_vars().unwrap_err();
182+
assert!(
183+
err.to_string()
184+
.contains("agent url environment variable 'GREED_NONEXISTENT_VAR_XYZ' is not set"),
185+
"unexpected error message: {err}"
186+
);
177187
}
178188

179189
#[test]

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub mod run;
2222
mod statistics;
2323
mod strategy;
2424
mod tactic;
25+
pub mod template;
2526
mod trading_days;
2627

2728
pub async fn greed_loop(args: GreedRunnerArgs) -> Result<(), GreedError> {

src/main.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use clap::{CommandFactory, Parser};
22
use log::LevelFilter;
33
use simplelog::{ColorChoice, CombinedLogger, Config, ConfigBuilder, TermLogger, TerminalMode};
44

5+
use greed::error::GreedError;
56
use greed::platform::args::PlatformArgs;
7+
use greed::template;
68
use greed::{analyze_stocks, fetch_quote, fetch_recent_orders, fetch_status, greed_loop};
79

810
use crate::cli::{Cli, Command};
@@ -22,6 +24,12 @@ async fn async_main(log_config: Config) {
2224
let cli = Cli::parse();
2325
let command = cli.command;
2426
match command {
27+
Command::Init(args) => {
28+
generate_config_template(args)
29+
.await
30+
.expect("config template generation failed");
31+
}
32+
2533
Command::Analyze(args) => {
2634
analyze_stocks(
2735
&args.symbols,
@@ -64,6 +72,26 @@ async fn async_main(log_config: Config) {
6472
}
6573
}
6674

75+
async fn generate_config_template(args: cli::init::InitArgs) -> Result<(), GreedError> {
76+
use cli::init::InitConfigType;
77+
78+
let (tmpl, filename) = match args.config_type {
79+
InitConfigType::Greed => (template::greed_config_template(), "greed.toml"),
80+
InitConfigType::Strategy => (template::strategy_config_template(), "strategy.toml"),
81+
InitConfigType::Agent => (template::agent_config_template(), "agent.toml"),
82+
};
83+
84+
let output_path = match args.path {
85+
Some(p) if p.is_dir() => p.join(filename),
86+
Some(p) => p,
87+
None => std::env::current_dir()?.join(filename),
88+
};
89+
90+
tokio::fs::write(&output_path, tmpl).await?;
91+
println!("Wrote template to {}", output_path.display());
92+
Ok(())
93+
}
94+
6795
fn create_log_config() -> Config {
6896
ConfigBuilder::new()
6997
.set_time_offset_to_local()

src/template.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
pub fn greed_config_template() -> &'static str {
2+
r#"# Greed configuration file
3+
4+
# Platform to use for trading. Currently only "alpaca" is supported.
5+
platform = "alpaca"
6+
7+
# How often (in seconds) to run the trading loop.
8+
interval = 60
9+
10+
# Strategies allow you to compose multiple tactic configs together.
11+
# Each strategy references a file path or agent config, and gets a share of your portfolio.
12+
# [[strategies]]
13+
# name = "My Strategy"
14+
# portfolio_percent = 100.0 # Percentage of portfolio allocated to this strategy
15+
# path = "strategy.toml" # Path to a local tactic config file
16+
# # OR use an AI agent strategy:
17+
# # agent_path = "agent.toml"
18+
19+
# Tactics define buy/sell rules for individual assets.
20+
# [[tactics]]
21+
# name = "ETF"
22+
# [tactics.buy]
23+
# for = { stock = "VTI" }
24+
# when = { below_median_percent = 5.0, median_period = "month" }
25+
# do = { buy_percent = 10.0 }
26+
# [tactics.sell]
27+
# for = { stock = "VTI" }
28+
# when = { gain_above_percent = 5.0 }
29+
# do = { sell_all = true }
30+
"#
31+
}
32+
33+
pub fn strategy_config_template() -> &'static str {
34+
r#"# Strategy configuration file
35+
# A strategy defines buy/sell tactics for one or more assets.
36+
37+
# Platform to use for trading. Currently only "alpaca" is supported.
38+
platform = "alpaca"
39+
40+
# How often (in seconds) to run the trading loop.
41+
interval = 60
42+
43+
# Each [[tactics]] block defines buy/sell rules for one asset.
44+
[[tactics]]
45+
name = "VTI"
46+
47+
[tactics.buy]
48+
for = { stock = "VTI" }
49+
when = { below_median_percent = 1.0 }
50+
do = { buy_percent = 25 }
51+
52+
[tactics.sell]
53+
for = { stock = "VTI" }
54+
when = { gain_above_percent = 1.0 }
55+
do = { sell_all = true }
56+
"#
57+
}
58+
59+
pub fn agent_config_template() -> &'static str {
60+
r#"# Agent configuration file
61+
# An agent uses an AI model to make trading decisions.
62+
63+
# The system prompt that describes the agent's trading strategy and behavior.
64+
prompt = "You are a trading agent. Analyze the current portfolio and market conditions, then decide whether to buy or sell."
65+
66+
# Provider configuration for the AI model.
67+
[agent_provider]
68+
# Provider type. Currently only "Ollama" is supported.
69+
type = "Ollama"
70+
# URL of the Ollama server. Can be a literal URL or an environment variable (e.g. "$OLLAMA_URL").
71+
url = "http://localhost:11434"
72+
# The model to use (e.g. "llama3", "mistral").
73+
model = "llama3"
74+
75+
# Optional allowlist of stock symbols the agent is permitted to trade.
76+
# If empty, all symbols are allowed.
77+
# allow = ["VTI", "SPY"]
78+
79+
# Optional denylist of stock symbols the agent is not permitted to trade.
80+
# deny = ["GME", "AMC"]
81+
82+
# Tool permissions — set any to false to disable that capability for the agent.
83+
[tools]
84+
account = true
85+
positions = true
86+
open_orders = true
87+
quotes = true
88+
buy = true
89+
sell = true
90+
web_fetch = true
91+
read_note = true
92+
write_note = true
93+
"#
94+
}

0 commit comments

Comments
 (0)