Skip to content

Commit 133ce7b

Browse files
committed
chore: add base clap interface
1 parent aedc7bb commit 133ce7b

File tree

9 files changed

+206
-39
lines changed

9 files changed

+206
-39
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
/nix/store/wnzsd16man9civsdvahfpklfkprzw9x4-pre-commit-config.json
1+
/nix/store/bydqjjc5cihzr4k1ws1wl0kjl4r106x9-pre-commit-config.json

Cargo.lock

Lines changed: 22 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ tabs_in_doc_comments = "allow"
2121
clap = { version = "^4.5.53", features = ["derive"] }
2222
color-eyre = "^0.6.5"
2323
insta = { version = "1.45.0", features = ["json"] }
24-
jiff = "^0.2.16"
24+
jiff = "^0.2.17"
2525
secrecy = { version = "0.10.3", features = ["serde"] }
2626
snapshot_fonts = "1.2.0"
2727
strum = { version = "0.27", features = ["derive"] }
28-
tracing = { version = "^0.1.41", features = ["log", "std", "async-await"] }
29-
v_exchanges = { version = "^0.16.3", features = ["full"] }
28+
tracing = { version = "^0.1.44", features = ["log", "std", "async-await"] }
29+
v_exchanges = { version = "^0.16.7", features = ["full"] }
3030
v_utils = { version = "2.15.9", features = ["full"] }
3131

3232
#[patch.crates-io]

discretionary_engine_strategy/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,21 @@
1010
//! - **Strategy Layer** (`strategy.rs`): Exchange-agnostic strategy implementations that only
1111
//! work with Nautilus types (`TradeTick`, `QuoteTick`, `Bar`, etc.).
1212
//!
13+
//! - **Protocols** (`protocols/`): Protocol definitions and deserialization.
14+
//!
1315
//! This separation ensures:
1416
//! 1. Strategies can be easily tested with mock data
1517
//! 2. Strategies can switch between exchanges without code changes
1618
//! 3. The same strategy code works for backtesting and live trading
1719
1820
pub mod data;
21+
pub mod protocols;
1922
pub mod strategy;
2023

2124
mod start;
2225

2326
use clap as _;
27+
#[cfg(test)]
28+
use insta as _;
2429
pub use start::start;
2530
use tracing_subscriber as _;

discretionary_engine_strategy/src/main.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use clap::{Parser, Subcommand};
1+
use clap::{Args, Parser, Subcommand};
22
use color_eyre::eyre::Result;
3+
use discretionary_engine_strategy::protocols::interpret_protocol_specs;
34
use futures_util as _;
45
use nautilus_bybit as _;
56
use nautilus_model as _;
@@ -10,14 +11,35 @@ use tracing::level_filters::LevelFilter;
1011
struct Cli {
1112
#[command(subcommand)]
1213
command: Commands,
14+
/// Use testnet instead of mainnet
15+
#[arg(long, global = true)]
16+
testnet: bool,
1317
}
1418

1519
#[derive(Subcommand)]
1620
enum Commands {
17-
/// Start the strategy with live market data
21+
/// Submit a position request
22+
Submit(PositionArgs),
23+
/// Start the strategy with live market data (legacy)
1824
Start,
1925
}
2026

27+
#[derive(Args, Clone, Debug)]
28+
struct PositionArgs {
29+
/// Target change in exposure. So positive for buying, negative for selling.
30+
#[arg(short, long, allow_hyphen_values = true)]
31+
size_usdt: f64,
32+
/// _only_ the coin name itself. e.g. "BTC" or "ETH". Providing full symbol currently will error on the stage of making price requests for the coin.
33+
#[arg(short, long)]
34+
coin: String,
35+
/// acquisition protocols parameters, in the format of "<protocol>-<params>", e.g. "ts:p0.5". Params consist of their starting letter followed by the value, e.g. "p0.5" for 0.5% offset. If multiple params are required, they are separated by '-'.
36+
#[arg(short, long)]
37+
acquisition_protocols: Vec<String>,
38+
/// followup protocols parameters, in the format of "<protocol>-<params>", e.g. "ts:p0.5". Params consist of their starting letter followed by the value, e.g. "p0.5" for 0.5% offset. If multiple params are required, they are separated by '-'.
39+
#[arg(short, long)]
40+
followup_protocols: Vec<String>,
41+
}
42+
2143
#[tokio::main]
2244
async fn main() -> Result<()> {
2345
color_eyre::install()?;
@@ -27,6 +49,24 @@ async fn main() -> Result<()> {
2749
let cli = Cli::parse();
2850

2951
match cli.command {
52+
Commands::Submit(args) => {
53+
// Parse and validate protocols (no execution yet)
54+
let acquisition_protocols = interpret_protocol_specs(args.acquisition_protocols)?;
55+
let followup_protocols = interpret_protocol_specs(args.followup_protocols)?;
56+
57+
println!("Position: {} USDT of {}", args.size_usdt, args.coin);
58+
println!("Testnet: {}", cli.testnet);
59+
println!();
60+
println!("Acquisition protocols ({}):", acquisition_protocols.len());
61+
for (i, protocol) in acquisition_protocols.iter().enumerate() {
62+
println!(" [{}] {:?} -> {}", i, protocol, protocol.signature());
63+
}
64+
println!();
65+
println!("Followup protocols ({}):", followup_protocols.len());
66+
for (i, protocol) in followup_protocols.iter().enumerate() {
67+
println!(" [{}] {:?} -> {}", i, protocol, protocol.signature());
68+
}
69+
}
3070
Commands::Start => discretionary_engine_strategy::start().await?,
3171
}
3272

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! DummyMarket protocol - sends a single market order.
2+
3+
use std::{fmt, str::FromStr};
4+
5+
use color_eyre::eyre::{Result, bail};
6+
7+
/// A protocol that simply sends one market order.
8+
///
9+
/// This is the simplest possible protocol, used for testing and debugging.
10+
#[derive(Clone, Debug, Default)]
11+
pub struct DummyMarket;
12+
13+
impl DummyMarket {
14+
/// Protocol prefix used for parsing.
15+
pub const PREFIX: &'static str = "dm";
16+
}
17+
18+
impl fmt::Display for DummyMarket {
19+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20+
write!(f, "{}", Self::PREFIX)
21+
}
22+
}
23+
24+
impl FromStr for DummyMarket {
25+
type Err = color_eyre::eyre::Report;
26+
27+
fn from_str(spec: &str) -> Result<Self> {
28+
// Accept "dm" or "dm:" with no additional params
29+
let trimmed = spec.trim();
30+
if trimmed == Self::PREFIX || trimmed.starts_with(&format!("{}:", Self::PREFIX)) {
31+
// For now, DummyMarket has no parameters
32+
let after_prefix = trimmed.strip_prefix(Self::PREFIX).unwrap_or("");
33+
let after_colon = after_prefix.strip_prefix(':').unwrap_or(after_prefix);
34+
if after_colon.is_empty() {
35+
return Ok(DummyMarket);
36+
}
37+
bail!("DummyMarket does not accept parameters, got: {after_colon}");
38+
}
39+
bail!("Expected protocol spec starting with '{}', got: {spec}", Self::PREFIX)
40+
}
41+
}
42+
43+
#[cfg(test)]
44+
mod tests {
45+
use super::*;
46+
47+
#[test]
48+
fn parse_dm() {
49+
let dm = DummyMarket::from_str("dm").unwrap();
50+
assert_eq!(dm.to_string(), "dm");
51+
}
52+
53+
#[test]
54+
fn parse_dm_with_colon() {
55+
let dm = DummyMarket::from_str("dm:").unwrap();
56+
assert_eq!(dm.to_string(), "dm");
57+
}
58+
59+
#[test]
60+
fn parse_dm_with_params_fails() {
61+
let result = DummyMarket::from_str("dm:p0.5");
62+
assert!(result.is_err());
63+
}
64+
65+
#[test]
66+
fn parse_wrong_prefix_fails() {
67+
let result = DummyMarket::from_str("ts:p0.5");
68+
assert!(result.is_err());
69+
}
70+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//! Protocol module for discretionary_engine_strategy.
2+
//!
3+
//! This is a simplified version focused only on protocol deserialization,
4+
//! without execution logic.
5+
6+
mod dummy_market;
7+
8+
use std::str::FromStr;
9+
10+
use color_eyre::eyre::{Result, bail};
11+
pub use dummy_market::DummyMarket;
12+
13+
/// Protocol types supported by the strategy engine.
14+
#[derive(Clone, Debug)]
15+
pub enum Protocol {
16+
DummyMarket(DummyMarket),
17+
}
18+
19+
impl FromStr for Protocol {
20+
type Err = color_eyre::eyre::Report;
21+
22+
fn from_str(spec: &str) -> Result<Self> {
23+
if let Ok(dm) = DummyMarket::from_str(spec) {
24+
Ok(Protocol::DummyMarket(dm))
25+
} else {
26+
bail!("Could not convert string to any Protocol\nString: {spec}")
27+
}
28+
}
29+
}
30+
31+
impl Protocol {
32+
/// Get the protocol signature (its string representation).
33+
pub fn signature(&self) -> String {
34+
match self {
35+
Protocol::DummyMarket(dm) => dm.to_string(),
36+
}
37+
}
38+
}
39+
40+
/// Parse protocol specs from command line arguments.
41+
pub fn interpret_protocol_specs(protocol_specs: Vec<String>) -> Result<Vec<Protocol>> {
42+
let protocol_specs: Vec<String> = protocol_specs.into_iter().filter(|s| !s.is_empty()).collect();
43+
if protocol_specs.is_empty() {
44+
bail!("No protocols specified");
45+
}
46+
let mut protocols = Vec::new();
47+
for spec in protocol_specs {
48+
let protocol = Protocol::from_str(&spec)?;
49+
protocols.push(protocol);
50+
}
51+
Ok(protocols)
52+
}

0 commit comments

Comments
 (0)