增加快速记录功能
This commit is contained in:
parent
2b3b516f18
commit
083dda7118
@ -10,18 +10,18 @@
|
||||
<cargoProject FILE="$PROJECT_DIR$/bookkeeper/Cargo.toml" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="76b3b902-7a5c-4bcd-9c1b-8241d748fb44" name="更改" comment="添加登录页面">
|
||||
<list default="true" id="76b3b902-7a5c-4bcd-9c1b-8241d748fb44" name="更改" comment="bug fix: zero division error">
|
||||
<change afterPath="$PROJECT_DIR$/bookkeeper/templates/ftx.html.j2" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/Cargo.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/Cargo.toml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/src/main.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/templates/index.html.j2" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/templates/index.html.j2" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="RsBuildProfile:release" />
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="RsBuildProfile:dev" />
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
@ -82,7 +82,7 @@
|
||||
</component>
|
||||
<component name="RunManager" selected="Cargo.Run bookkeeper">
|
||||
<configuration name="Run bookkeeper" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="buildProfileId" value="release" />
|
||||
<option name="buildProfileId" value="dev" />
|
||||
<option name="command" value="run --package bookkeeper --bin bookkeeper" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$/bookkeeper" />
|
||||
<envs />
|
||||
@ -156,7 +156,9 @@
|
||||
<workItem from="1736239592084" duration="2278000" />
|
||||
<workItem from="1737809478583" duration="76000" />
|
||||
<workItem from="1739197188908" duration="1873000" />
|
||||
<workItem from="1740209396270" duration="6029000" />
|
||||
<workItem from="1740209396270" duration="6333000" />
|
||||
<workItem from="1741527809947" duration="5732000" />
|
||||
<workItem from="1741617416981" duration="867000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Add bookkeeper">
|
||||
<option name="closed" value="true" />
|
||||
@ -278,7 +280,15 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1740214793630</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="16" />
|
||||
<task id="LOCAL-00016" summary="bug fix: zero division error">
|
||||
<option name="closed" value="true" />
|
||||
<created>1740216327515</created>
|
||||
<option name="number" value="00016" />
|
||||
<option name="presentableId" value="LOCAL-00016" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1740216327515</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="17" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@ -311,7 +321,8 @@
|
||||
<MESSAGE value="清仓筛选" />
|
||||
<MESSAGE value="添加总计" />
|
||||
<MESSAGE value="添加登录页面" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="添加登录页面" />
|
||||
<MESSAGE value="bug fix: zero division error" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="bug fix: zero division error" />
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
||||
2
bookkeeper/Cargo.lock
generated
2
bookkeeper/Cargo.lock
generated
@ -481,7 +481,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bookkeeper"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"bcrypt",
|
||||
"chrono",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bookkeeper"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -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 = "<data>")]
|
||||
async fn post_ftx(
|
||||
data: Form<FastTransModel<'_>>,
|
||||
conn: Connection<'_, Db>,
|
||||
cookies: &CookieJar<'_>,
|
||||
) -> Result<Redirect, Status> {
|
||||
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<transaction::Model> = 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<SplitPiece> = 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<Template, Status> {
|
||||
if !verify(cookies) {
|
||||
return Err(Status::Forbidden);
|
||||
}
|
||||
Ok(Template::render("ftx", ()))
|
||||
}
|
||||
|
||||
#[get("/add")]
|
||||
async fn get_add(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
|
||||
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"))
|
||||
}
|
||||
|
||||
42
bookkeeper/templates/ftx.html.j2
Normal file
42
bookkeeper/templates/ftx.html.j2
Normal file
@ -0,0 +1,42 @@
|
||||
<div class="box">
|
||||
<form action="/ftx" method="post">
|
||||
<div class="field">
|
||||
<label class="label" for="code">代码</label>
|
||||
<div class="control">
|
||||
<input id="code" name="code" class="input" type="number" placeholder="Code" value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="name">名称</label>
|
||||
<div class="control">
|
||||
<input id="name" name="name" class="input" type="text" placeholder="Name" value="">
|
||||
</div>
|
||||
</div>
|
||||
<table class="table is-fullwidth is-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><label class="label" for="date">卖出日期</label></th>
|
||||
<th><label class="label" for="sell">卖出价</label></th>
|
||||
<th><label class="label" for="volume">成交量(卖)</label></th>
|
||||
<th><label class="label" for="net_sell">实际卖出</label></th>
|
||||
<th><label class="label" for="other_fee">额外手续费</label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input id="date" name="date" class="input" type="date" placeholder="YYYY-MM-DD" value="0"></td>
|
||||
<td><input id="sell" name="sell" class="input" type="text" placeholder="卖出价" value="0"></td>
|
||||
<td><input id="volume" name="volume" class="input" type="number" placeholder="Volume" value="0"></td>
|
||||
<td><input id="net_sell" name="net_sell" class="input" type="text" placeholder="实际卖出" value="0"></td>
|
||||
<td><input id="other_fee" name="other_fee" class="input" type="text" placeholder="额外手续费" value="0"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-link">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -86,6 +86,8 @@
|
||||
<span> </span>
|
||||
<a id="filterEntry">高级筛选</a>
|
||||
<span> </span>
|
||||
<a id="ftx">快速记录</a>
|
||||
<span> </span>
|
||||
<a href="/?is_done=false">未清仓</a>
|
||||
<span> </span>
|
||||
<a href="/?is_done=true">已清仓</a>
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user