From 083dda7118ceb5bf023c5caa0b766ac2768a8147 Mon Sep 17 00:00:00 2001 From: wjsjwr Date: Mon, 10 Mar 2025 22:54:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BF=AB=E9=80=9F=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/workspace.xml | 27 +++++-- bookkeeper/Cargo.lock | 2 +- bookkeeper/Cargo.toml | 2 +- bookkeeper/src/main.rs | 117 ++++++++++++++++++++++++++++- bookkeeper/templates/ftx.html.j2 | 42 +++++++++++ bookkeeper/templates/index.html.j2 | 12 +++ 6 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 bookkeeper/templates/ftx.html.j2 diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ca1bf27..73cf921 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -10,18 +10,18 @@ - + + - - + - + - @@ -311,7 +321,8 @@ - diff --git a/bookkeeper/Cargo.lock b/bookkeeper/Cargo.lock index f5d29db..972b434 100644 --- a/bookkeeper/Cargo.lock +++ b/bookkeeper/Cargo.lock @@ -481,7 +481,7 @@ dependencies = [ [[package]] name = "bookkeeper" -version = "0.2.1" +version = "0.3.0" dependencies = [ "bcrypt", "chrono", diff --git a/bookkeeper/Cargo.toml b/bookkeeper/Cargo.toml index a7d9eab..49dea79 100644 --- a/bookkeeper/Cargo.toml +++ b/bookkeeper/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bookkeeper" -version = "0.2.1" +version = "0.3.0" edition = "2021" [dependencies] diff --git a/bookkeeper/src/main.rs b/bookkeeper/src/main.rs index 7c88657..f5a02b7 100644 --- a/bookkeeper/src/main.rs +++ b/bookkeeper/src/main.rs @@ -350,6 +350,118 @@ async fn delete_tx( } } +#[derive(FromForm, Debug)] +struct FastTransModel<'r> { + code: usize, + name: &'r str, + sell: &'r str, + #[field(validate = range(100..))] + volume: i32, + net_sell: &'r str, + other_fee: &'r str, + date: &'r str, +} + +/// Fast Transaction 模式: +/// 必要条件: +/// 1. 买入单存在 +/// 算法: +/// 1. 买入时间早于FTX时间,且没有分割订单,按买入价格从低到高,排列未完成订单。 +/// 2. 如果没有满足条件的买入单,则报错退出。 +/// 3. 如果当前买入单的成交量刚好等于FTX的剩余成交量,完成循环并进入6。 +/// 4. 如果当前买入单的成交量大于FTX的剩余成交量,则报错退出。 +/// 5. FTX剩余成交量减少当前买入单的成交量,切换下一个买入单,并进入3。 +/// 6. 按照满足条件的买入单的成交量进行分配,并更新结果。 +#[post("/ftx", data = "")] +async fn post_ftx( + data: Form>, + conn: Connection<'_, Db>, + cookies: &CookieJar<'_>, +) -> Result { + if !verify(cookies) { + return Err(Status::Forbidden); + } + let db = conn.into_inner(); + + let date = NaiveDate::parse_from_str(data.date, "%Y-%m-%d").unwrap(); + + let rows: Vec = TE::find() + .filter(transaction::Column::Code.eq(data.code as i32)) + .filter(transaction::Column::Name.eq(data.name)) + .filter(transaction::Column::IsDone.eq(false)) + .filter(transaction::Column::Date.lte(date)) + .filter(transaction::Column::SplitPieces.is_null()) + .order_by_asc(transaction::Column::Buy) + .order_by_asc(transaction::Column::Date) + .all(db) + .await + .unwrap(); + + let mut vol = data.volume; + let mut selected = 0; + for r in &rows { + if vol < r.volume { + println!("volume {} is less than {}", vol, r.volume); + return Err(Status::BadRequest); + } + vol -= r.volume; + selected += 1; + if vol == 0 { + break; + } + } + + if vol > 0 { + println!("remaining volume {}", vol); + return Err(Status::BadRequest); + } + + if selected <= 0 { + println!("No transaction selected"); + return Err(Status::BadRequest); + } + + let after_fee = + Decimal::from_str(data.net_sell).unwrap() - Decimal::from_str(data.other_fee).unwrap(); + + for i in 0..selected { + let mut sp: Vec = Vec::new(); + sp.push(SplitPiece { + date, + sell: Decimal::from_str(data.sell).unwrap(), + net_sell: after_fee * Decimal::from(rows[i].volume) / Decimal::from(data.volume), + volume: rows[i].volume, + }); + let v: serde_json::value::Value = serde_json::to_value(sp).unwrap(); + + let mut rec: transaction::ActiveModel = rows[i].clone().into(); + rec.split_pieces = Set(Some(v)); + rec.is_done = Set(true); + rec.gain = Set(Some( + (Decimal::from_str(data.sell).unwrap() - rows[i].buy) * Decimal::from(rows[i].volume), + )); + rec.net_gain = Set(Some( + after_fee * Decimal::from(rows[i].volume) / Decimal::from(data.volume) + - rows[i].net_buy, + )); + + match rec.update(db).await { + Err(_) => return Err(Status::InternalServerError), + Ok(_) => continue, + } + } + + Ok(Redirect::to("/")) +} + +#[get("/ftx")] +async fn get_ftx(cookies: &CookieJar<'_>) -> Result { + if !verify(cookies) { + return Err(Status::Forbidden); + } + Ok(Template::render("ftx", ())) +} + #[get("/add")] async fn get_add(cookies: &CookieJar<'_>) -> Result { if !verify(cookies) { @@ -451,7 +563,10 @@ async fn rocket() -> _ { })) .mount( "/", - routes![index, add, get_add, get_tx, post_tx, delete_tx, login_page, login_post], + routes![ + index, add, get_add, get_tx, post_tx, delete_tx, login_page, login_post, post_ftx, + get_ftx + ], ) .mount("/static", FileServer::from("static")) } diff --git a/bookkeeper/templates/ftx.html.j2 b/bookkeeper/templates/ftx.html.j2 new file mode 100644 index 0000000..809633a --- /dev/null +++ b/bookkeeper/templates/ftx.html.j2 @@ -0,0 +1,42 @@ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+
+ diff --git a/bookkeeper/templates/index.html.j2 b/bookkeeper/templates/index.html.j2 index 82a8f3f..7177a40 100644 --- a/bookkeeper/templates/index.html.j2 +++ b/bookkeeper/templates/index.html.j2 @@ -86,6 +86,8 @@       高级筛选       + 快速记录 +       未清仓       已清仓 @@ -130,6 +132,16 @@ openModal($target); }); + document.getElementById('ftx').addEventListener('click', async () => { + const $target = document.getElementById("modal"); + const response = await fetch("/ftx"); + if (!response.ok) { + return; + } + document.getElementById('modal-content').innerHTML = await response.text(); + openModal($target); + }); + (document.querySelectorAll('.modify-row') || []).forEach(($cl) => { $cl.addEventListener('click', async () => { const rowId = $cl.attributes.getNamedItem("row-id").value;