debot_db/
transaction_log.rs

1// transaction_log.rs
2
3use bson::doc;
4use bson::Bson;
5use bson::Document;
6use debot_utils::get_local_time;
7use debot_utils::HasId;
8use mongodb::Collection;
9use mongodb::{
10    options::{ClientOptions, Tls, TlsOptions},
11    Database,
12};
13use rust_decimal::Decimal;
14use serde::{Deserialize, Serialize};
15use shared_mongodb::{database, ClientHolder};
16use std::collections::HashMap;
17use std::error;
18use std::fs::File;
19use std::io::{Read, Write};
20use std::path::Path;
21use std::sync::Arc;
22use std::time::SystemTime;
23use tokio::sync::Mutex;
24
25use crate::delete_item_all;
26use crate::SearchMode;
27use crate::TradingStrategy;
28use crate::{
29    create_unique_index, insert_item, search_item, search_items, update_item, Counter, CounterType,
30    Entity,
31};
32
33async fn get_last_id<T: Default + Entity + HasId>(db: &Database) -> u32 {
34    let item = T::default();
35    match search_items(
36        db,
37        &item,
38        crate::SearchMode::Descending,
39        Some(1),
40        None,
41        Some("id"),
42    )
43    .await
44    {
45        Ok(mut items) => items.pop().and_then(|item| item.id()).unwrap_or(0),
46        Err(e) => {
47            log::info!("get_last_id: {:?}", e);
48            0
49        }
50    }
51}
52
53#[derive(Serialize, Deserialize, Clone, Debug)]
54pub enum SampleTerm {
55    TradingTerm,
56    ShortTerm,
57    LongTerm,
58}
59
60impl SampleTerm {
61    pub fn to_numeric(&self) -> Decimal {
62        match self {
63            SampleTerm::TradingTerm => Decimal::new(1, 0),
64            SampleTerm::ShortTerm => Decimal::new(2, 0),
65            SampleTerm::LongTerm => Decimal::new(3, 0),
66        }
67    }
68}
69
70#[derive(Serialize, Deserialize, Clone, Debug)]
71pub struct FundConfig {
72    pub token: String,
73    pub trading_strategy: TradingStrategy,
74    pub balance_per_strategy: Decimal,
75    pub risk_reward: Decimal,
76    pub take_profit_ratio: Option<Decimal>,
77    pub atr_spread: Option<Decimal>,
78    pub atr_term: SampleTerm,
79    pub open_minutes: i64,
80}
81
82#[derive(Serialize, Deserialize, Clone, Debug)]
83pub struct AppState {
84    pub id: u32,
85    pub last_execution_time: Option<SystemTime>,
86    pub last_equity: Option<Decimal>,
87    pub ave_dd: Option<Decimal>,
88    pub max_dd: Option<Decimal>,
89    pub cumulative_return: Decimal,
90    pub cumulative_dd: Decimal,
91    pub score: Option<Decimal>,
92    pub score_2: Option<Decimal>,
93    pub score_3: Option<Decimal>,
94    pub curcuit_break: bool,
95    pub error_time: Vec<String>,
96    pub max_invested_amount: Decimal,
97    pub fund_configs: Option<Vec<FundConfig>>,
98}
99
100impl Default for AppState {
101    fn default() -> Self {
102        Self {
103            id: 1,
104            last_execution_time: None,
105            last_equity: None,
106            ave_dd: None,
107            max_dd: None,
108            cumulative_return: Decimal::ZERO,
109            cumulative_dd: Decimal::ZERO,
110            score: None,
111            score_2: None,
112            score_3: None,
113            curcuit_break: false,
114            error_time: vec![],
115            max_invested_amount: Decimal::ZERO,
116            fund_configs: Some(vec![]),
117        }
118    }
119}
120
121#[derive(Serialize, Deserialize, Clone, Debug, Default)]
122pub struct PnlLog {
123    pub id: Option<u32>,
124    pub date: String,
125    pub pnl: Decimal,
126}
127
128impl HasId for PnlLog {
129    fn id(&self) -> Option<u32> {
130        self.id
131    }
132}
133
134#[derive(Serialize, Deserialize, Debug, Clone, Default)]
135pub struct PricePoint {
136    pub timestamp: i64,
137    pub timestamp_str: String,
138    pub price: Decimal,
139    pub volume: Option<Decimal>,
140    pub num_trades: Option<u64>,
141    pub funding_rate: Option<Decimal>,
142    pub open_interest: Option<Decimal>,
143    pub oracle_price: Option<Decimal>,
144}
145
146impl PricePoint {
147    pub fn new(
148        price: Decimal,
149        timestamp: Option<i64>,
150        volume: Option<Decimal>,
151        num_trades: Option<u64>,
152        funding_rate: Option<Decimal>,
153        open_interest: Option<Decimal>,
154        oracle_price: Option<Decimal>,
155    ) -> Self {
156        let (local_timestamp, timestamp_str) = get_local_time();
157        let timestamp = timestamp.unwrap_or(local_timestamp);
158        Self {
159            timestamp,
160            timestamp_str,
161            price,
162            volume,
163            num_trades,
164            funding_rate,
165            open_interest,
166            oracle_price,
167        }
168    }
169}
170
171#[derive(Serialize, Deserialize, Clone, Debug, Default)]
172pub struct PriceLog {
173    pub id: Option<u32>,
174    pub name: String,
175    pub token_name: String,
176    pub price_point: PricePoint,
177}
178
179impl HasId for PriceLog {
180    fn id(&self) -> Option<u32> {
181        self.id
182    }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
186pub enum CandlePattern {
187    #[default]
188    None,
189    Hammer,
190    InvertedHammer,
191    BullishEngulfing,
192    BearishEngulfing,
193    Doji,
194    Marubozu,
195    MorningStar,
196    EveningStar,
197    ThreeWhiteSoldiers,
198    ThreeBlackCrows,
199    PiercingPattern,
200    DarkCloudCover,
201    Harami,
202    HaramiCross,
203    SpinningTop,
204}
205
206impl CandlePattern {
207    pub fn to_one_hot(&self) -> [Decimal; 16] {
208        let mut one_hot = [Decimal::ZERO; 16];
209
210        match self {
211            CandlePattern::None => one_hot[0] = Decimal::ONE,
212            CandlePattern::Hammer => one_hot[1] = Decimal::ONE,
213            CandlePattern::InvertedHammer => one_hot[2] = Decimal::ONE,
214            CandlePattern::BullishEngulfing => one_hot[3] = Decimal::ONE,
215            CandlePattern::BearishEngulfing => one_hot[4] = Decimal::ONE,
216            CandlePattern::Doji => one_hot[5] = Decimal::ONE,
217            CandlePattern::Marubozu => one_hot[6] = Decimal::ONE,
218            CandlePattern::MorningStar => one_hot[7] = Decimal::ONE,
219            CandlePattern::EveningStar => one_hot[8] = Decimal::ONE,
220            CandlePattern::ThreeWhiteSoldiers => one_hot[9] = Decimal::ONE,
221            CandlePattern::ThreeBlackCrows => one_hot[10] = Decimal::ONE,
222            CandlePattern::PiercingPattern => one_hot[11] = Decimal::ONE,
223            CandlePattern::DarkCloudCover => one_hot[12] = Decimal::ONE,
224            CandlePattern::Harami => one_hot[13] = Decimal::ONE,
225            CandlePattern::HaramiCross => one_hot[14] = Decimal::ONE,
226            CandlePattern::SpinningTop => one_hot[15] = Decimal::ONE,
227        }
228
229        one_hot
230    }
231}
232
233#[derive(Serialize, Deserialize, Clone, Debug, Default)]
234pub struct DebugLog {
235    pub input_1: Decimal,
236    pub input_2: Decimal,
237    pub input_3: Decimal,
238    pub input_4: Decimal,
239    pub input_5: Decimal,
240    pub input_6: Decimal,
241    pub input_7: Decimal,
242    pub input_8: Decimal,
243    pub input_9: Decimal,
244    pub input_10: Decimal,
245    pub input_11: Decimal,
246    pub input_12: Decimal,
247    pub input_13: Decimal,
248    pub input_14: Decimal,
249    pub input_15: Decimal,
250    pub input_16: Decimal,
251    pub input_17: Decimal,
252    pub input_18: Decimal,
253    pub input_19: Decimal,
254    pub input_20: Decimal,
255    pub input_21: Decimal,
256    pub input_22: Decimal,
257    pub input_23: Decimal,
258    pub input_24: Decimal,
259    pub input_25: Decimal,
260    pub input_26: Decimal,
261    pub input_27: Decimal,
262    pub input_28: Decimal,
263    pub input_29: Decimal,
264    pub input_30: CandlePattern,
265    pub input_31: CandlePattern,
266    pub input_32: CandlePattern,
267    pub input_33: CandlePattern,
268    pub input_34: CandlePattern,
269    pub input_35: CandlePattern,
270    pub input_36: CandlePattern,
271    pub input_37: CandlePattern,
272    pub input_38: CandlePattern,
273    pub input_39: CandlePattern,
274    pub output_1: Decimal,
275    pub output_2: Decimal,
276    pub output_3: Option<Decimal>,
277    pub output_4: Option<Decimal>,
278    pub output_5: Option<Decimal>,
279}
280
281#[derive(Serialize, Deserialize, Clone, Debug, Default)]
282pub struct PositionLog {
283    pub id: Option<u32>,
284    pub fund_name: String,
285    pub order_id: String,
286    pub ordered_price: Decimal,
287    pub state: String,
288    pub token_name: String,
289    pub open_time_str: String,
290    pub open_timestamp: i64,
291    pub close_time_str: String,
292    pub average_open_price: Decimal,
293    pub position_type: String,
294    pub close_price: Decimal,
295    pub asset_in_usd: Decimal,
296    pub pnl: Decimal,
297    pub fee: Decimal,
298    pub debug: DebugLog,
299}
300
301#[derive(Serialize, Deserialize)]
302pub struct SerializableModel {
303    pub model: Vec<u8>,
304}
305
306impl HasId for PositionLog {
307    fn id(&self) -> Option<u32> {
308        self.id
309    }
310}
311
312pub struct TransactionLog {
313    counter: Counter,
314    db_r_name: String,
315    db_w_name: String,
316    client_holder: Arc<Mutex<ClientHolder>>,
317}
318
319impl TransactionLog {
320    pub async fn new(
321        max_position_counter: Option<u32>,
322        max_price_counter: Option<u32>,
323        max_pnl_counter: Option<u32>,
324        mongodb_uri: &str,
325        db_r_name: &str,
326        db_w_name: &str,
327        back_test: bool,
328    ) -> Self {
329        // Set up the DB client holder
330        let mut client_options = match ClientOptions::parse(mongodb_uri).await {
331            Ok(client_options) => client_options,
332            Err(e) => {
333                panic!("{:?}", e);
334            }
335        };
336        let tls_options = TlsOptions::builder().build();
337        client_options.tls = Some(Tls::Enabled(tls_options));
338        let client_holder = Arc::new(Mutex::new(ClientHolder::new(client_options)));
339
340        let db = shared_mongodb::database::get(&client_holder, &db_w_name)
341            .await
342            .unwrap();
343
344        create_unique_index(&db)
345            .await
346            .expect("Error creating unique index");
347
348        if back_test {
349            if let Err(e) = Self::delete_all_positions(&db).await {
350                panic!("delete_all_positions failed: {:?}", e);
351            }
352            if let Err(e) = Self::delete_app_state(&db).await {
353                panic!("delete_app_state failed: {:?}", e);
354            }
355        }
356
357        let last_position_counter =
358            TransactionLog::get_last_transaction_id(&db, CounterType::Position).await;
359        let last_price_counter =
360            TransactionLog::get_last_transaction_id(&db, CounterType::Price).await;
361        let last_pnl_counter = TransactionLog::get_last_transaction_id(&db, CounterType::Pnl).await;
362
363        let counter = Counter::new(
364            max_position_counter,
365            max_price_counter,
366            max_pnl_counter,
367            last_position_counter,
368            last_price_counter,
369            last_pnl_counter,
370        );
371
372        log::warn!(
373            "position = {}/{:?}, price = {}/{:?}, pnl = {}/{:?}",
374            last_position_counter,
375            max_position_counter,
376            last_price_counter,
377            max_price_counter,
378            last_pnl_counter,
379            max_pnl_counter,
380        );
381
382        TransactionLog {
383            counter,
384            db_r_name: db_r_name.to_owned(),
385            db_w_name: db_w_name.to_owned(),
386            client_holder,
387        }
388    }
389
390    pub fn increment_counter(&self, counter_type: CounterType) -> u32 {
391        self.counter.increment(counter_type)
392    }
393
394    pub async fn get_last_transaction_id(db: &Database, counter_type: CounterType) -> u32 {
395        match counter_type {
396            CounterType::Position => get_last_id::<PositionLog>(db).await,
397            CounterType::Price => get_last_id::<PriceLog>(db).await,
398            CounterType::Pnl => get_last_id::<PnlLog>(db).await,
399        }
400    }
401
402    pub async fn get_w_db(&self) -> Option<Database> {
403        self.get_db(false).await
404    }
405
406    pub async fn get_r_db(&self) -> Option<Database> {
407        self.get_db(true).await
408    }
409
410    async fn get_db(&self, read: bool) -> Option<Database> {
411        let db_name = if read {
412            &self.db_r_name
413        } else {
414            &self.db_w_name
415        };
416        let db = match database::get(&self.client_holder, db_name).await {
417            Ok(db) => Some(db),
418            Err(e) => {
419                log::error!("get_db: {:?}", e);
420                None
421            }
422        };
423        db
424    }
425
426    pub async fn update_transaction(
427        db: &Database,
428        item: &PositionLog,
429    ) -> Result<(), Box<dyn error::Error>> {
430        update_item(db, item).await?;
431        Ok(())
432    }
433
434    pub async fn update_price(db: &Database, item: PriceLog) -> Result<(), Box<dyn error::Error>> {
435        update_item(db, &item).await?;
436        Ok(())
437    }
438
439    pub async fn copy_price(db_r: &Database, db_w: &Database, limit: Option<u32>) {
440        let item = PriceLog::default();
441        let items = {
442            match search_items(db_r, &item, SearchMode::Ascending, limit, None, Some("id")).await {
443                Ok(items) => items,
444                Err(e) => {
445                    log::error!("get price: {:?}", e);
446                    return;
447                }
448            }
449        };
450        log::debug!("get prices: num = {}", items.len());
451
452        for item in &items {
453            match insert_item(db_w, item).await {
454                Ok(_) => {}
455                Err(e) => {
456                    log::error!("write price: {:?}", e);
457                    return;
458                }
459            }
460        }
461    }
462
463    pub async fn copy_position(db_r: &Database, db_w: &Database, limit: Option<u32>) {
464        let item = PositionLog::default();
465        let items = {
466            match search_items(db_r, &item, SearchMode::Ascending, limit, None, Some("id")).await {
467                Ok(items) => items,
468                Err(e) => {
469                    log::error!("get position: {:?}", e);
470                    return;
471                }
472            }
473        };
474        log::debug!("get position: num = {}", items.len());
475
476        for item in &items {
477            match insert_item(db_w, item).await {
478                Ok(_) => {}
479                Err(e) => {
480                    log::error!("write position: {:?}", e);
481                    return;
482                }
483            }
484        }
485    }
486
487    pub async fn get_price_market_data(
488        db: &Database,
489        limit: Option<u32>,
490        id: Option<u32>,
491        is_ascend: bool,
492    ) -> HashMap<String, HashMap<String, Vec<PricePoint>>> {
493        let search_mode = if is_ascend {
494            SearchMode::Ascending
495        } else {
496            SearchMode::Descending
497        };
498        let sort_key = Some("price_point.timestamp");
499        let item = PriceLog::default();
500
501        let items = match id {
502            Some(id) => search_item(db, &item, Some(id), sort_key)
503                .await
504                .map(|item| vec![item]),
505            None => search_items(db, &item, search_mode, limit, None, sort_key).await,
506        };
507
508        let Ok(mut items) = items else {
509            log::warn!("get_price_market_data: search failed");
510            return HashMap::new();
511        };
512
513        items.sort_by_key(|p| p.price_point.timestamp);
514
515        let mut result = HashMap::new();
516        for price_log in items {
517            result
518                .entry(price_log.name)
519                .or_insert_with(HashMap::new)
520                .entry(price_log.token_name)
521                .or_insert_with(Vec::new)
522                .push(price_log.price_point);
523        }
524
525        result
526    }
527
528    pub async fn get_all_positions(
529        db: &Database,
530        limit: Option<u32>,
531        id: Option<u32>,
532        is_ascend: bool,
533    ) -> Vec<PositionLog> {
534        let search_mode = if is_ascend {
535            SearchMode::Ascending
536        } else {
537            SearchMode::Descending
538        };
539        let sort_key = Some("open_timestamp");
540        let item = PositionLog::default();
541
542        let items = if let Some(id) = id {
543            match search_item(db, &item, Some(id), sort_key).await {
544                Ok(position) => vec![position],
545                Err(e) => {
546                    log::warn!("get_all_positions: {:?}", e);
547                    vec![]
548                }
549            }
550        } else {
551            match search_items(db, &item, search_mode, limit, None, sort_key).await {
552                Ok(positions) => positions,
553                Err(e) => {
554                    log::warn!("get_all_positions: {:?}", e);
555                    vec![]
556                }
557            }
558        };
559
560        items
561    }
562
563    async fn delete_all_positions(db: &Database) -> Result<(), Box<dyn error::Error>> {
564        let item = PositionLog::default();
565        delete_item_all(db, &item).await
566    }
567
568    pub async fn insert_pnl(db: &Database, item: PnlLog) -> Result<(), Box<dyn error::Error>> {
569        insert_item(db, &item).await?;
570        Ok(())
571    }
572
573    pub async fn get_app_state(db: &Database) -> AppState {
574        let item = AppState::default();
575        match search_item(db, &item, Some(1), Some("id")).await {
576            Ok(item) => item,
577            Err(e) => {
578                log::warn!("get_app_state: {:?}", e);
579                item
580            }
581        }
582    }
583
584    async fn delete_app_state(db: &Database) -> Result<(), Box<dyn error::Error>> {
585        let item = AppState::default();
586        delete_item_all(db, &item).await
587    }
588
589    pub async fn update_app_state(
590        db: &Database,
591        last_execution_time: Option<SystemTime>,
592        last_equity: Option<Decimal>,
593        ave_dd: Option<Decimal>,
594        max_dd: Option<Decimal>,
595        cumulative_return: Option<Decimal>,
596        cumulative_dd: Option<Decimal>,
597        score: Option<Decimal>,
598        score_2: Option<Decimal>,
599        score_3: Option<Decimal>,
600        curcuit_break: bool,
601        error_time: Option<String>,
602        max_invested_amount: Option<Decimal>,
603        fund_configs: Option<Vec<FundConfig>>,
604    ) -> Result<(), Box<dyn error::Error>> {
605        let item = AppState::default();
606        let mut item = match search_item(db, &item, Some(1), Some("id")).await {
607            Ok(prev_item) => prev_item,
608            Err(_) => item,
609        };
610
611        if last_execution_time.is_some() {
612            item.last_execution_time = last_execution_time;
613        }
614
615        if let Some(last_equity) = last_equity {
616            item.last_equity = Some(last_equity.round());
617        }
618
619        if let Some(ave_dd) = ave_dd {
620            item.ave_dd = Some(ave_dd.round());
621        }
622
623        if let Some(max_dd_val) = max_dd {
624            if item
625                .max_dd
626                .map_or(true, |item_max_dd| max_dd_val > item_max_dd)
627            {
628                item.max_dd = Some(max_dd_val.round());
629            }
630        }
631
632        if let Some(cumulative_return) = cumulative_return {
633            item.cumulative_return += cumulative_return.round();
634        }
635
636        if let Some(cumulative_dd) = cumulative_dd {
637            item.cumulative_dd += cumulative_dd.round();
638        }
639
640        if score.is_some() {
641            item.score = score;
642        }
643
644        if score_2.is_some() {
645            item.score_2 = score_2;
646        }
647
648        if score_3.is_some() {
649            item.score_3 = score_3;
650        }
651
652        item.curcuit_break = curcuit_break;
653
654        if let Some(error_time) = error_time {
655            item.error_time.push(error_time);
656        }
657
658        if let Some(max_invested_amount) = max_invested_amount {
659            item.max_invested_amount = max_invested_amount.round();
660        }
661
662        if let Some(fund_configs) = fund_configs {
663            item.fund_configs = Some(fund_configs);
664        }
665
666        update_item(db, &item).await?;
667        Ok(())
668    }
669}
670
671#[derive(Clone)]
672pub struct ModelParams {
673    db_name: String,
674    client_holder: Arc<Mutex<ClientHolder>>,
675    collection_name: String,
676    save_to_db: bool,
677    file_path: Option<String>,
678}
679
680impl ModelParams {
681    pub async fn new(
682        mongodb_uri: &str,
683        db_name: &str,
684        save_to_db: bool,
685        file_path: Option<String>,
686    ) -> Self {
687        // Set up the DB client holder
688        let mut client_options = match ClientOptions::parse(mongodb_uri).await {
689            Ok(client_options) => client_options,
690            Err(e) => {
691                panic!("{:?}", e);
692            }
693        };
694        let tls_options = TlsOptions::builder().build();
695        client_options.tls = Some(Tls::Enabled(tls_options));
696        let client_holder = Arc::new(Mutex::new(ClientHolder::new(client_options)));
697
698        ModelParams {
699            db_name: db_name.to_owned(),
700            client_holder,
701            collection_name: "model_params".to_owned(),
702            save_to_db,
703            file_path,
704        }
705    }
706
707    async fn get_db(&self) -> Option<Database> {
708        let db = match database::get(&self.client_holder, &self.db_name).await {
709            Ok(db) => Some(db),
710            Err(e) => {
711                log::error!("get_db: {:?}", e);
712                None
713            }
714        };
715        db
716    }
717
718    pub async fn save_model(
719        &self,
720        key: &str,
721        model: &SerializableModel,
722    ) -> Result<(), Box<dyn std::error::Error>> {
723        if self.save_to_db {
724            self.save_model_to_db(key, model).await
725        } else {
726            self.save_model_to_file(key, model).await
727        }
728    }
729
730    pub async fn load_model(
731        &self,
732        key: &str,
733    ) -> Result<SerializableModel, Box<dyn std::error::Error>> {
734        if self.save_to_db {
735            self.load_model_from_db(key).await
736        } else {
737            self.load_model_from_file(key).await
738        }
739    }
740
741    async fn save_model_to_db(
742        &self,
743        key: &str,
744        model: &SerializableModel,
745    ) -> Result<(), Box<dyn std::error::Error>> {
746        let db = self.get_db().await.ok_or("no db")?;
747        let collection: Collection<Document> = db.collection(&self.collection_name);
748        let serialized_model = bincode::serialize(model)?;
749
750        let document = doc! {
751            "key": key,
752            "model": Bson::Binary(mongodb::bson::Binary {
753                subtype: mongodb::bson::spec::BinarySubtype::Generic,
754                bytes: serialized_model
755            })
756        };
757
758        collection
759            .update_one(
760                doc! { "key": key },
761                doc! { "$set": document },
762                mongodb::options::UpdateOptions::builder()
763                    .upsert(true)
764                    .build(),
765            )
766            .await?;
767        Ok(())
768    }
769
770    async fn load_model_from_db(
771        &self,
772        key: &str,
773    ) -> Result<SerializableModel, Box<dyn std::error::Error>> {
774        let db = self.get_db().await.ok_or("no db")?;
775        let collection: Collection<Document> = db.collection(&self.collection_name);
776
777        let filter = doc! { "key": key };
778        let document = collection
779            .find_one(filter, None)
780            .await?
781            .ok_or("No model found in the collection")?;
782
783        if let Some(Bson::Binary(model_bytes)) = document.get("model") {
784            let model: SerializableModel = bincode::deserialize(&model_bytes.bytes)?;
785            Ok(model)
786        } else {
787            Err("Invalid data format".into())
788        }
789    }
790
791    async fn save_model_to_file(
792        &self,
793        key: &str,
794        model: &SerializableModel,
795    ) -> Result<(), Box<dyn std::error::Error>> {
796        let serialized_model = bincode::serialize(model)?;
797        let file_name = format!("{}.bin", key);
798
799        let file_path = if let Some(ref dir) = self.file_path {
800            Path::new(dir).join(file_name)
801        } else {
802            Path::new(&file_name).to_path_buf()
803        };
804
805        let mut file = File::create(&file_path)?;
806        file.write_all(&serialized_model)?;
807        Ok(())
808    }
809
810    async fn load_model_from_file(
811        &self,
812        key: &str,
813    ) -> Result<SerializableModel, Box<dyn std::error::Error>> {
814        let file_name = format!("{}.bin", key);
815
816        let file_path = if let Some(ref dir) = self.file_path {
817            Path::new(dir).join(file_name)
818        } else {
819            Path::new(&file_name).to_path_buf()
820        };
821
822        let mut file = File::open(&file_path)?;
823        let mut buffer = Vec::new();
824        file.read_to_end(&mut buffer)?;
825        let model: SerializableModel = bincode::deserialize(&buffer)?;
826        Ok(model)
827    }
828}