Repository: yasstake/rbot (language: python)

Issue:
Warm up loopを回数ではなくて状態初期化がおわるまでまつ
Warmupは現在回数で初期化終了を判断しているが、LastUpdatePriceが初期化おわるまでループするように変更する。


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index b920471f..f413c646 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -135,4 +135,4 @@ once_cell = { version = "1.19.0" }
 actix-web={version="4.5"}
 actix-rt={version="2.9"}
 
-#pyo3 = {version="0.20.2"}
+indicatif = {version="0.17"}
diff --git a/README.md b/README.md
index 35ff4dd8..1688aed5 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,9 @@
 `rbot` is a python crypt trading bot framework written in Rust.
 
 The feature is;
-* Store historical transaction data(tick) in sqlite database.
-* Calculate OHLCV data with any window sizefrom historical tick data
-* Provide bot framework that enables `backtest`, `dry_run` and `production`.
+* Your Bot can be executed in three mode `backtest`, `dry_run` and `production` without modification.
+* Turuly TICK BASED backtesting.
+* Written in Rust and Provided as Python plugin. So you can write your own bot in Python with super efficent Rust back end.
 
 ### the execution sample
 
diff --git a/exchanges/bybit/src/config.rs b/exchanges/bybit/src/config.rs
index 11062ae0..21dd530d 100644
--- a/exchanges/bybit/src/config.rs
+++ b/exchanges/bybit/src/config.rs
@@ -133,9 +133,9 @@ impl BybitConfig {
             "linear",
             "BTC",
             "USDT",
-            dec![0.1],
+            0.1,
             PriceType::Home,
-            dec![0.001],
+            0.001,
             200,
             0.1,
             0.00_01,
@@ -145,6 +145,7 @@ impl BybitConfig {
                 "publicTrade.BTCUSDT".to_string(),
                 "orderbook.200.BTCUSDT".to_string(),
             ],
+            None,
         )
     }
 
@@ -155,9 +156,9 @@ impl BybitConfig {
             "linear",
             "BTC",
             "USDC",
-            dec![0.1],
+            0.1,
             PriceType::Home,
-            dec![0.001],
+            0.001,
             200,
             0.1,
             0.00_01,
@@ -167,6 +168,7 @@ impl BybitConfig {
                 "publicTrade.BTCPERP".to_string(),
                 "orderbook.200.BTCPERP".to_string(),
             ],
+            None,
         );
 
         config.trade_symbol = "BTCPERP".to_string();
@@ -181,9 +183,9 @@ impl BybitConfig {
             "linear",
             "ETH",
             "USDT",            
-            dec![0.01],
+            0.01,
             PriceType::Home,
-            dec![0.01],
+            0.01,
             200,
             0.1,
             0.00_01,
@@ -193,6 +195,7 @@ impl BybitConfig {
                 "publicTrade.ETHUSDT".to_string(),
                 "orderbook.200.ETHUSDT".to_string(),
             ],
+            None
         );
 
         config
@@ -205,9 +208,9 @@ impl BybitConfig {
             "linear",
             "ETH",
             "USDC",            
-            dec![0.01],
+            0.01,
             PriceType::Home,
-            dec![0.01],
+            0.01,
             200,
             0.1,
             0.00_01,
@@ -217,9 +220,8 @@ impl BybitConfig {
                 "publicTrade.ETHPERP".to_string(),
                 "orderbook.200.ETHPERP".to_string(),
             ],
+            Some("ETHPERP")
         );
-
-        config.trade_symbol = "ETHPERP".to_string();
         config
     }
 
@@ -230,9 +232,9 @@ impl BybitConfig {
             "linear",
             "MNT",
             "USDT",            
-            dec![0.0001],
+            0.0001,
             PriceType::Home,
-            dec![1],
+            1.0,
             200,
             0.1,
             0.00_01,
@@ -242,6 +244,7 @@ impl BybitConfig {
                 "publicTrade.MNTUSDT".to_string(),
                 "orderbook.200.MNTUSDT".to_string(),
             ],
+            None,
         )
     }
 
@@ -252,9 +255,9 @@ impl BybitConfig {
             "linear",
             "MNT",
             "USDT",            
-            dec![0.0001],
+            0.0001,
             PriceType::Home,
-            dec![0.1],
+            0.1,
             200,
             0.1,
             0.00_01,
@@ -264,6 +267,7 @@ impl BybitConfig {
                 "publicTrade.MNT-PERP".to_string(),
                 "orderbook.200.MNT-PERP".to_string(),
             ],
+            Some("MNT-PERP")
         )
     }
 
@@ -275,9 +279,9 @@ impl BybitConfig {
             "linear",
             "SOL",
             "USDT",            
-            dec![0.0001],
+            0.0001,
             PriceType::Home,
-            dec![0.1],
+            0.1,
             200,
             0.1,
             0.00_01,
@@ -287,6 +291,7 @@ impl BybitConfig {
                 "publicTrade.SOLUSDT".to_string(),
                 "orderbook.200.SOLUSDT".to_string(),
             ],
+            None
         )
     }
 
@@ -297,9 +302,9 @@ impl BybitConfig {
             "linear",
             "SOL",
             "USDC",            
-            dec![0.01],
+            0.01,
             PriceType::Home,
-            dec![0.1],
+            0.1,
             200,
             0.1,
             0.00_01,
@@ -309,6 +314,7 @@ impl BybitConfig {
                 "publicTrade.SOLPERP".to_string(),
                 "orderbook.200.SOLPERP".to_string(),
             ],
+            Some("SOLPERP")
         )
     }
 
@@ -320,9 +326,9 @@ impl BybitConfig {
             "linear",
             "USDC",
             "USDT",            
-            dec![0.0001],
+            0.0001,
             PriceType::Home,
-            dec![0.1],
+            0.1,
             200,
             0.1,
             0.00_01,
@@ -332,6 +338,7 @@ impl BybitConfig {
                 "publicTrade.USDCUSDT".to_string(),
                 "orderbook.200.USDCUSDT".to_string(),
             ],
+            None,
         )
     }
 
@@ -367,9 +374,9 @@ mod test_bybit_config {
             "linear",
             "BTC",
             "USDT",
-            dec![0.1],
+            0.1,
             PriceType::Home,
-            dec![0.001],
+            0.001,
             200,
             0.1,
             0.00_01,
@@ -379,6 +386,7 @@ mod test_bybit_config {
                 "publicTrade.BTCUSDT".to_string(),
                 "orderbook.200.BTCUSDT".to_string(),
             ],
+            None
         );
 
         assert_eq!(config, new_config);
diff --git a/exchanges/bybit/src/market.rs b/exchanges/bybit/src/market.rs
index 0ebf2d58..5de711bc 100644
--- a/exchanges/bybit/src/market.rs
+++ b/exchanges/bybit/src/market.rs
@@ -246,6 +246,11 @@ impl BybitMarket {
         MarketImpl::get_cache_duration(self)
     }
 
+    #[getter]
+    fn get_archive_info(&self) -> (MicroSec, MicroSec) {
+        MarketImpl::get_archive_info(self)
+    }
+
     fn reset_cache_duration(&mut self) {
         MarketImpl::reset_cache_duration(self)
     }
@@ -358,7 +363,7 @@ impl BybitMarket {
         &mut self,
         time_from: MicroSec,
         time_to: MicroSec,
-    ) -> anyhow::Result<MarketStream> {
+    ) -> anyhow::Result<(MicroSec, MicroSec, MarketStream)> {
         MarketImpl::open_backtest_channel(self, time_from, time_to)
     }
 
diff --git a/rbot/README.md b/rbot/README.md
index 35ff4dd8..d6e7ee89 100644
--- a/rbot/README.md
+++ b/rbot/README.md
@@ -1,5 +1,27 @@
 # rbot (Rusty bot framwrok for crypto trading)
 
+## version 0.4.0:
+    This release is beta release
+
+## Change:
+	OHLCVのTimestampの型をi64からDateTimeへ変更(->Agentに影響あり）
+	約定ログの保存方式をSQLiteからparquetへ変更（ファイルサイズが５分の１へ縮小）
+	注意：過去にダウンロードした約定ログは使えません。DBディレクトリごと削除し、再度ダウンロードしてください。
+	DownloadとBackテストにProgressBarを導入
+
+
+## Fix:
+	#244 立ち上げ直後に最初のTickでon_clockがよびだされていたのを修正
+	#255 Session.ohlcvで指定した本数よりも多く足が計算されていたのを修正。
+	
+### Limitations:
+	バックテスト以外のDryRun,RealRunは修正後テストしていません。
+	一旦このβリリースでは動かないものと考えてください。    
+
+
+
+
+
 `rbot` is a python crypt trading bot framework written in Rust.
 
 The feature is;
diff --git a/rbot_lib/Cargo.toml b/rbot_lib/Cargo.toml
index 0a8217ee..722bd923 100644
--- a/rbot_lib/Cargo.toml
+++ b/rbot_lib/Cargo.toml
@@ -67,6 +67,9 @@ zip = {workspace=true}
 
 tempfile={workspace=true}
 
+indicatif = {workspace=true}
+
+
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
 version = "0.21.2"
diff --git a/rbot_lib/src/common/config.rs b/rbot_lib/src/common/config.rs
index a55b9e57..520117e8 100644
--- a/rbot_lib/src/common/config.rs
+++ b/rbot_lib/src/common/config.rs
@@ -46,15 +46,10 @@ pub struct MarketConfig {
     #[pyo3(set, get)]    
     pub trade_symbol: String,
 
-    #[pyo3(set)]
     pub price_unit: Decimal,
-    
-    #[pyo3(set)]
     pub size_unit: Decimal,
 
-    #[pyo3(set)]
     pub maker_fee: Decimal,
-    #[pyo3(set)]    
     pub taker_fee: Decimal,
 
     #[pyo3(set)]
@@ -109,30 +104,37 @@ impl MarketConfig {
         round(self.size_unit, size)
     }
 
-
     #[new]
     pub fn new(
         exchange_name: &str,
         trade_category: &str,
         foreign_currency: &str,
         home_currency: &str,
-        price_unit: Decimal,
+        price_unit: f64,
         price_type: PriceType,
-        size_unit: Decimal,
+        size_unit: f64,
         board_depth: u32,
         market_order_price_slip: f64,
         maker_fee: f64,
         taker_fee: f64,
         fee_type: FeeType,
         public_subscribe_channel: Vec<String>,
+        trade_symbol: Option<&str>
     ) -> Self {
         let maker_fee = Decimal::from_f64(maker_fee).unwrap();
         let taker_fee = Decimal::from_f64(taker_fee).unwrap();
 
+        let symbol = if let Some(symbol) = trade_symbol {
+            symbol.to_string()
+        }
+        else {
+            format!("{}{}", foreign_currency, home_currency)
+        };
+
         Self {
             exchange_name: exchange_name.to_string(),
-            price_unit: price_unit,
-            size_unit: size_unit,
+            price_unit: Decimal::from_f64(price_unit).unwrap(),
+            size_unit: Decimal::from_f64(size_unit).unwrap(),
             maker_fee,
             taker_fee,
             price_type,
@@ -142,11 +144,53 @@ impl MarketConfig {
             market_order_price_slip: Decimal::from_f64(market_order_price_slip).unwrap(),
             board_depth,
             trade_category: trade_category.to_string(),
-            trade_symbol: format!("{}{}", foreign_currency, home_currency),
-            public_subscribe_channel: public_subscribe_channel
+            public_subscribe_channel: public_subscribe_channel,
+            trade_symbol: symbol
         }
     }
 
+    #[setter]
+    pub fn set_price_unit(&mut self, unit: f64) {
+        self.price_unit = Decimal::from_f64(unit).unwrap();
+    }
+
+    #[getter]
+    pub fn get_price_unit(&self) -> Decimal {
+        self.price_unit.clone()
+    }
+
+    #[setter]
+    pub fn set_size_unit(&mut self, unit: f64) {
+        self.size_unit = Decimal::from_f64(unit).unwrap();
+    }
+
+    #[getter]
+    pub fn get_size_unit(&self) -> Decimal {
+        self.size_unit.clone()
+    }
+
+    #[setter]
+    pub fn set_maker_fee(&mut self, fee: f64) {
+        self.maker_fee = Decimal::from_f64(fee).unwrap();
+    }
+
+    #[getter]
+    pub fn get_maker_fee(&mut self) -> Decimal {
+        self.maker_fee.clone()
+    }
+
+    #[setter]
+    pub fn set_taker_fee(&mut self, fee: f64) {
+        self.taker_fee = Decimal::from_f64(fee).unwrap();
+    }
+
+    #[getter]
+    pub fn get_taker_fee(&mut self) -> Decimal {
+        self.taker_fee.clone()
+    }
+
+
+
     pub fn key_string(&self, production: bool) -> String {
         if production {
             format!("{}/{}/{}", self.exchange_name, self.trade_category, self.trade_symbol)
@@ -164,15 +208,16 @@ impl Default for MarketConfig {
             "default_trade_category",
             "default_foreign_currency",
             "default_home_currency",
-            Decimal::from_f64(0.01).unwrap(),
+            0.01,
             PriceType::Home,
-            Decimal::from_f64(0.01).unwrap(),
+            0.01,
             10,
             0.0,
             0.0,
             0.0,
             FeeType::Home,
             vec![],
+            None,
         )
     }
 }
@@ -221,5 +266,16 @@ mod test_market_config {
 
         Ok(())
     }
+
+    #[test]
+    fn test_price_size_unit() {
+        let mut config = MarketConfig::default();
+
+        config.set_price_unit(0.123);
+        assert_eq!(config.get_price_unit(), dec![0.123]);
+
+        config.set_size_unit(1.23);
+        assert_eq!(config.get_size_unit(), dec![1.23]);
+    }
 }
 
diff --git a/rbot_lib/src/common/order.rs b/rbot_lib/src/common/order.rs
index 761214e7..dca0f423 100644
--- a/rbot_lib/src/common/order.rs
+++ b/rbot_lib/src/common/order.rs
@@ -1401,15 +1401,16 @@ mod order_tests {
             "SPOT",
             "BTC",
             "USDT",
-            dec![0.01],
+            0.01,
             PriceType::Home,
-            dec![0.001],
+            0.001,
             1000,
             0.0001,
             0.001,
             0.001,
             FeeType::Home,
             vec!["".to_string()],
+            None,
         );
 
         config
diff --git a/rbot_lib/src/common/time.rs b/rbot_lib/src/common/time.rs
index 1f017cdc..5d557e42 100644
--- a/rbot_lib/src/common/time.rs
+++ b/rbot_lib/src/common/time.rs
@@ -20,6 +20,10 @@ pub fn msec_to_microsec(t: i64) -> MicroSec {
     return (t as i64) * 1_000;
 }
 
+pub fn microsec_to_sec(t: MicroSec) -> i64 {
+    t / MICRO_SECOND
+}
+
 pub fn to_seconds(microsecond: MicroSec) -> f64 {
     return (microsecond as f64) / (MICRO_SECOND as f64);
 }
@@ -91,6 +95,14 @@ pub fn date_string(t: MicroSec) -> String {
     return datetime.format("%Y%m%d").to_string();
 }
 
+#[pyfunction]
+pub fn date_time_string(t: MicroSec) -> String {
+    let datetime = to_naive_datetime(t);
+
+    return datetime.format("%Y/%m/%dT%H:%M").to_string();
+}
+
+
 ///  convert YYYYMMDD format date to MicroSec,
 ///  return 0 in error.
 #[pyfunction]
diff --git a/rbot_lib/src/db/archive.rs b/rbot_lib/src/db/archive.rs
index 7928abb2..49a0ac5d 100644
--- a/rbot_lib/src/db/archive.rs
+++ b/rbot_lib/src/db/archive.rs
@@ -1,17 +1,15 @@
 use crate::{
     common::{
-        date_string, parse_date, time_string, MarketConfig, MicroSec, OrderSide, Trade, DAYS,
-        FLOOR_DAY, MIN, NOW, TODAY,
+        date_string, date_time_string, parse_date, time_string, MarketConfig, MicroSec, OrderSide,
+        Trade, DAYS, FLOOR_DAY, MIN, NOW, TODAY,
     },
     db::{append_df, csv_to_df, df_to_parquet, parquet_to_df, KEY},
     net::{check_exist, RestApi},
 };
 use anyhow::{anyhow, Context};
 use futures::StreamExt;
-use parquet::{
-    file::reader::{FileReader, SerializedFileReader},
-    record::RowAccessor,
-};
+use indicatif::{HumanBytes, MultiProgress, ProgressBar, ProgressStyle};
+use parquet::{file::reader::SerializedFileReader, record::RowAccessor};
 use reqwest::Client;
 use rust_decimal::{prelude::FromPrimitive, Decimal};
 use tokio::io::{AsyncWriteExt as _, BufWriter};
@@ -57,6 +55,8 @@ pub struct TradeArchive {
     latest_archive_date: MicroSec,
     start_time: MicroSec,
     end_time: MicroSec,
+
+    download_per_cent: i64,
 }
 
 impl Clone for TradeArchive {
@@ -68,6 +68,7 @@ impl Clone for TradeArchive {
             latest_archive_date: self.latest_archive_date.clone(),
             start_time: self.start_time.clone(),
             end_time: self.end_time.clone(),
+            download_per_cent: 0,
         };
 
         let r = archive.analyze();
@@ -88,6 +89,7 @@ impl TradeArchive {
             latest_archive_date: 0,
             start_time: 0,
             end_time: 0,
+            download_per_cent: 0,
         };
 
         let r = my.analyze();
@@ -147,22 +149,62 @@ impl TradeArchive {
     {
         let mut date = FLOOR_DAY(NOW());
 
+        if verbose {
+            println!(
+                "downloading web archvie from [{}]days before. force=[{}]",
+                ndays, force
+            );
+        }
+
         log::debug!("download log from {:?}({:?})", date_string(date), date);
 
         let mut count = 0;
-        let mut i = 0;
+        let mut total_files = -1;
+        let m = MultiProgress::new();
+        let mut text_bar = ProgressBar::new_spinner();
+        text_bar = m.add(text_bar);
+
+        let mut progress_bar = ProgressBar::new_spinner();
+
+        for i in 0..ndays {
+            if force || (! self.has_local_archive(date) && date < self.latest_archive_date(api).await?)  {
+                if total_files == -1 {
+                    total_files = ndays - i;
+
+                    progress_bar = ProgressBar::new((total_files * 100) as u64);
+                    progress_bar.set_style(
+                        ProgressStyle::with_template(
+                            "[{elapsed_precise}]({percent:>3}%){bar:56}[ETA:{eta_precise}]"
+                    ).unwrap()
+                    );
+                    progress_bar = m.add(progress_bar);
+                }
 
-        while i <= ndays {
-            count += self
-                .web_archive_to_parquet(api, date, force, verbose)
-                .await?;
-            date -= DAYS(1);
+                let url = api.history_web_url(&self.config, date);
+                count += self
+                    .web_archive_to_parquet(api, date, force, verbose, |count, content_len| {
+                        if verbose {
+                            let percent = count * 100 / content_len;                           
 
-            i += 1;
-        }
+                            //progress_bar.set_position((i * 100 + percent) as u64);
+                           progress_bar.set_position( 
+                                ((i - (ndays - total_files))*100 + percent) as u64);
 
+                            text_bar.set_message(format!("{}({:2}%)[{:12}/{}]", url, percent, HumanBytes(count as u64), HumanBytes(content_len as u64)));
+                        }
+                        // let p =
+                    })
+                    .await?;
+            } else {
+                if verbose {
+                    text_bar.set_message(format!("skip download [{}]", date_time_string(date)));
+                }
+            }
+            date -= DAYS(1);
+        }
         self.analyze()?;
 
+
         Ok(count)
     }
 
@@ -369,6 +411,14 @@ impl TradeArchive {
 
         let all_dates = self.list_dates()?;
 
+        if all_dates.len() == 0 {
+            return Err(anyhow!(
+                "no data in archive from {} -> to {}",
+                date_string(start_time),
+                date_string(end_time)
+            ));
+        }
+
         let mut dates: Vec<MicroSec> = all_dates
             .into_iter()
             .filter(|date| start_time <= *date && *date <= end_time)
@@ -431,6 +481,7 @@ impl TradeArchive {
                 }
 
                 if detect_gap {
+                    log::warn!("Delete too old log {:?}", date_string(d));
                     self.delete(d)?;
                 }
             }
@@ -513,28 +564,25 @@ impl TradeArchive {
         Ok(())
     }
 
-    pub async fn web_archive_to_parquet<T>(
+    pub async fn web_archive_to_parquet<T, F>(
         &mut self,
         api: &T,
         date: MicroSec,
         force: bool,
         verbose: bool,
+        f: F,
     ) -> anyhow::Result<i64>
     where
         T: RestApi,
+        F: FnMut(i64, i64),
     {
+        self.download_per_cent = 0;
         let config = &self.config.clone();
 
-        let date = FLOOR_DAY(date);
-
         let has_csv_file = self.has_local_archive(date);
 
         if has_csv_file && !force {
-            if verbose {
-                log::debug!("archive csv file exist {}", time_string(date));
-                println!("archive csv file exist {}", time_string(date));
-                return Ok(0);
-            }
+            return Ok(0);
         }
 
         let latest = { self.latest_archive_date(api).await? };
@@ -555,11 +603,9 @@ impl TradeArchive {
 
         let url = api.history_web_url(config, date);
 
-        log::debug!("Downloading ...[{}]", url);
-
         let tmp_dir = tempdir().with_context(|| "create tmp dir error")?;
 
-        let file_path = log_download_tmp(&url, tmp_dir.path())
+        let file_path = log_download_tmp(&url, tmp_dir.path(), f)
             .await
             .with_context(|| format!("log_download_tmp error {}->{:?}", url, tmp_dir))?;
 
@@ -583,9 +629,16 @@ impl TradeArchive {
     }
 }
 
-const BUFFER_SIZE: usize = 8 * 1024 * 1024; 
+const BUFFER_SIZE: usize = 8 * 1024 * 1024;
 
-pub async fn log_download_tmp(url: &str, tmp_dir: &Path) -> anyhow::Result<PathBuf> {
+pub async fn log_download_tmp<F>(
+    url: &str,
+    tmp_dir: &Path,
+    mut progress: F,
+) -> anyhow::Result<PathBuf>
+where
+    F: FnMut(i64, i64),
+{
     let client = Client::new();
 
     let response = client
@@ -596,10 +649,12 @@ pub async fn log_download_tmp(url: &str, tmp_dir: &Path) -> anyhow::Result<PathB
         .await
         .with_context(|| format!("URL get error {}", url))?;
 
+    let content_length = response.content_length().unwrap_or_default();
+
     log::debug!(
         "Response code = {} / download size {}",
         response.status().as_str(),
-        response.content_length().unwrap_or_default() // if error, return 0
+        content_length // if error, return 0
     );
 
     if !response.status().is_success() {
@@ -620,8 +675,21 @@ pub async fn log_download_tmp(url: &str, tmp_dir: &Path) -> anyhow::Result<PathB
 
     log::debug!("start reading from web");
 
+    let mut count: i64 = 0;
+    let mut last_count = 0;
+    let count_interval = (content_length / 100) as i64;
+
     while let Some(item) = stream.next().await {
         let chunk = item?;
+        let len = chunk.len() as i64;
+        count += len;
+        last_count += len;
+
+        if count_interval < last_count {
+            progress(count, content_length as i64);
+            last_count = 0;
+        }
+
         file_buffer.write_all(&chunk).await?;
     }
 
@@ -630,8 +698,6 @@ pub async fn log_download_tmp(url: &str, tmp_dir: &Path) -> anyhow::Result<PathB
     Ok(path)
 }
 
-
-
 /// check if achive date is avairable at specified date
 async fn has_web_archive<T>(api: &T, config: &MarketConfig, date: MicroSec) -> anyhow::Result<bool>
 where
@@ -665,12 +731,17 @@ mod archive_test {
         log::debug!("start download");
         let now = NOW();
         let file = log_download_tmp(
-            "https://public.bybit.com/trading/BTCUSDT/BTCUSDT2024-07-16.csv.gz"
-            , path).await?;
+            "https://public.bybit.com/trading/BTCUSDT/BTCUSDT2024-07-16.csv.gz",
+            path,
+            |count, content_len| {
+                println!("{}", count);
+            },
+        )
+        .await?;
+
+        log::debug!("done");
+        log::debug!("file={:?} :  {}[msec]", file, (NOW() - now) / 1_000);
 
-            log::debug!("done");
-            log::debug!("file={:?} :  {}[msec]", file, (NOW()-now)/1_000);
-    
         Ok(())
     }
 }
diff --git a/rbot_lib/src/db/sqlite.rs b/rbot_lib/src/db/sqlite.rs
index a9a4dcc3..5b202647 100644
--- a/rbot_lib/src/db/sqlite.rs
+++ b/rbot_lib/src/db/sqlite.rs
@@ -19,12 +19,10 @@ use tokio::task::JoinHandle;
 use crate::common::MarketConfig;
 use crate::common::TimeChunk;
 use crate::common::FLOOR_DAY;
-use crate::db::TradeDataFrame;
 
 use crossbeam_channel::unbounded;
 use crossbeam_channel::Sender;
 
-use crate::common::flush_log;
 use crate::common::LogStatus;
 use crate::common::OrderSide;
 use crate::common::SEC;
@@ -403,7 +401,7 @@ impl TradeDb {
         return path.exists();
     }
 
-
+    /*
     /// check if table is exsit by sql.
     fn is_table_exsit(&mut self) -> bool {
         log::debug!("is_table_exsit");
@@ -435,6 +433,7 @@ impl TradeDb {
             }
         }
     }
+    */
 
     fn create_table_if_not_exists(&mut self) -> anyhow::Result<()> {
         self.connection.execute(
@@ -457,6 +456,7 @@ impl TradeDb {
         Ok(())
     }
 
+    /*
     fn drop_table(&self) -> anyhow::Result<()> {
         println!("database is dropped. To use database, restart the program.");
         println!("To delete completely, delete the database file from OS.");
@@ -470,6 +470,7 @@ impl TradeDb {
 
         Ok(())
     }
+    */
 
     pub fn vacuum(&self) -> anyhow::Result<()> {
         log::debug!("vacuum db");
@@ -481,6 +482,7 @@ impl TradeDb {
         Ok(())
     }
 
+    /* 
     fn recreate_table(&mut self) -> anyhow::Result<()> {
         self.create_table_if_not_exists()?;
         self.drop_table()?;
@@ -488,6 +490,7 @@ impl TradeDb {
 
         Ok(())
     }
+    */
 
     /// select  cachedf from database
     pub fn select_cachedf(
diff --git a/rbot_lib/src/db/tradedf.rs b/rbot_lib/src/db/tradedf.rs
index ee109ede..7d3ef80e 100644
--- a/rbot_lib/src/db/tradedf.rs
+++ b/rbot_lib/src/db/tradedf.rs
@@ -10,7 +10,10 @@ use pyo3_polars::PyDataFrame;
 
 use crate::{
     common::{time_string, MarketConfig, MicroSec, Trade, DAYS, FLOOR_DAY, HHMM, NOW},
-    db::{append_df, end_time_df, make_empty_ohlcvv, merge_df, ohlcv_start, ohlcvv_df, select_df, start_time_df, TradeBuffer, KEY},
+    db::{
+        append_df, end_time_df, make_empty_ohlcvv, merge_df, ohlcv_start, ohlcvv_df, select_df,
+        start_time_df, TradeBuffer, KEY,
+    },
     net::RestApi,
 };
 
@@ -89,8 +92,8 @@ impl TradeDataFrame {
         Ok(trade_data_frame)
     }
 
-    pub fn vacuum(&self) -> anyhow::Result<()>{
-        self.db.vacuum()        
+    pub fn vacuum(&self) -> anyhow::Result<()> {
+        self.db.vacuum()
     }
 
     pub fn get_archive(&self) -> TradeArchive {
@@ -105,8 +108,8 @@ impl TradeDataFrame {
         }
 
         let db_start = self.db.start_time();
-        
-        if let Ok(start)  = db_start {
+
+        if let Ok(start) = db_start {
             return Ok(start);
         }
 
@@ -177,7 +180,7 @@ impl TradeDataFrame {
 
         self.cache_ohlcvv = df;
 
-        Ok(())    
+        Ok(())
     }
 
     // TODO: rename to open_channel
@@ -198,7 +201,6 @@ impl TradeDataFrame {
         self.archive.download(api, ndays, force, verbose).await
     }
 
-
     /*
     pub async fn download_latest(&mut self, verbose: bool) -> anyhow::Result<i64> {
         if verbose {
@@ -261,21 +263,30 @@ impl TradeDataFrame {
         self.cache_duration = 0;
     }
 
+    pub fn archive_start_time(&self) -> MicroSec {
+        let archive_start = self.archive.start_time().unwrap_or_default();
+
+        archive_start
+    }
+
     pub fn archive_end_time(&self) -> MicroSec {
         let archive_end = self.archive.end_time().unwrap_or_default();
 
         archive_end
     }
 
-    pub fn select_cachedf(&mut self, start_time: MicroSec, end_time: MicroSec) -> anyhow::Result<DataFrame> {
+    pub fn select_cachedf(
+        &mut self,
+        start_time: MicroSec,
+        end_time: MicroSec,
+    ) -> anyhow::Result<DataFrame> {
         let archive_end = self.archive_end_time();
 
         let df = if start_time < archive_end {
             let df1 = self.archive.select_cachedf(start_time, end_time)?;
             let df2 = self.db.select_cachedf(archive_end, end_time)?;
             append_df(&df1, &df2)
-        }
-        else {
+        } else {
             self.db.select_cachedf(start_time, end_time)
         };
 
@@ -297,7 +308,7 @@ impl TradeDataFrame {
     }
 
     pub fn expire_cache_df(&mut self, forget_before: MicroSec) {
-        let forget_before = FLOOR_DAY(forget_before);       // expire by date.
+        let forget_before = FLOOR_DAY(forget_before); // expire by date.
         log::debug!("Expire cache {}", time_string(forget_before));
         let cache_timing = ohlcv_start(forget_before);
         self.cache_df = select_df(&self.cache_df, cache_timing, 0);
@@ -398,8 +409,7 @@ impl TradeDataFrame {
 
         if df_end_time < end_time {
             // 3日先までキャッシュを先読み
-            let df2 = &self
-                .select_cachedf(df_end_time, FLOOR_DAY(end_time + DAYS(4)))?;
+            let df2 = &self.select_cachedf(df_end_time, FLOOR_DAY(end_time + DAYS(4)))?;
 
             log::debug!(
                 "load data after cache(2days) df1={:?} df2={:?}",
diff --git a/rbot_market/src/market.rs b/rbot_market/src/market.rs
index 7fe7dd27..fb0d6736 100644
--- a/rbot_market/src/market.rs
+++ b/rbot_market/src/market.rs
@@ -204,6 +204,8 @@ pub trait MarketInterface {
     fn reset_cache_duration(&mut self);
     fn stop_db_thread(&mut self);
     fn cache_all_data(&mut self);
+    fn get_archive_info(&self) -> (MicroSec, MicroSec);
+
     fn select_trades(
         &mut self,
         start_time: MicroSec,
@@ -354,6 +356,16 @@ where
         lock.update_cache_all()
     }
 
+    fn get_archive_info(&self) -> (MicroSec, MicroSec) {
+        let db = self.get_db();
+        let lock = db.lock().unwrap();
+
+        let start_time = lock.archive_start_time();
+        let end_time = lock.archive_end_time();
+
+        (start_time, end_time)
+    }
+
     fn select_trades(
         &mut self,
         start_time: MicroSec,
@@ -544,11 +556,15 @@ where
     // fn download_latest(&mut self, verbose: bool) -> i64;
     fn start_market_stream(&mut self) -> anyhow::Result<()>;
 
+    /// open back test channel
+    /// returns:
+    ///     actual date to start
+    ///     actual date to end.
     fn open_backtest_channel(
         &mut self,
         time_from: MicroSec,
         time_to: MicroSec,
-    ) -> anyhow::Result<MarketStream> {
+    ) -> anyhow::Result<(MicroSec, MicroSec, MarketStream)> {
         let (sender, market_stream) = MarketStream::open();
 
         let archive = {
@@ -557,6 +573,11 @@ where
             trade_dataframe.get_archive()
         };
 
+        let dates = archive.select_dates(time_from, time_to)?;
+
+        let actual_start = dates[0];
+        let actual_end = dates[dates.len() -1];
+
         std::thread::spawn(move || {
             let result = archive.foreach(time_from, time_to, &mut |trade| {
                 let message: MarketMessage = trade.into();
@@ -570,10 +591,9 @@ where
             }
         });
 
-        return Ok(market_stream);
+        return Ok((actual_start, actual_end, market_stream));
     }
 
-
     async fn async_download_archive   
     (
         &self,
diff --git a/rbot_session/Cargo.toml b/rbot_session/Cargo.toml
index fc0046ab..bb57d7db 100644
--- a/rbot_session/Cargo.toml
+++ b/rbot_session/Cargo.toml
@@ -36,6 +36,8 @@ async-stream = {workspace = true}
 
 tokio={workspace = true}
 
+indicatif = {workspace=true}
+
 # https://pyo3.rs/v0.13.2/faq
 [dependencies.pyo3]
 version = "0.21.2"
diff --git a/rbot_session/src/runner.rs b/rbot_session/src/runner.rs
index ca0a26f2..366e2043 100644
--- a/rbot_session/src/runner.rs
+++ b/rbot_session/src/runner.rs
@@ -1,21 +1,20 @@
 // Copyright(c) 2022-2024. yasstake. All rights reserved.
 
 use crossbeam_channel::Receiver;
+use indicatif::{HumanCount, MultiProgress, ProgressBar, ProgressStyle};
 use pyo3::{
     pyclass, pymethods,
     types::{IntoPyDict, PyAnyMethods},
-    Bound, Py, PyAny, PyErr, PyObject, Python,
+    Bound, Py, PyAny, PyErr, Python,
 };
-use rust_decimal::prelude::ToPrimitive;
+use rust_decimal::{prelude::ToPrimitive, Decimal};
 
 use super::{has_method, ExecuteMode, Session};
-use async_stream::stream;
-use futures::Stream;
 
 use rbot_lib::{
     common::{
-        flush_log, time_string, AccountCoins, MarketConfig, MarketMessage, MarketStream, MicroSec,
-        Order, Trade, FLOOR_SEC, MARKET_HUB, NOW, SEC,
+        date_time_string, flush_log, microsec_to_sec, time_string, AccountCoins, MarketConfig,
+        MarketMessage, MarketStream, MicroSec, Order, Trade, FLOOR_SEC, MARKET_HUB, NOW, SEC,
     },
     net::{UdpReceiver, UdpSender},
 };
@@ -50,6 +49,9 @@ pub struct Runner {
     last_print_loop_count: i64,
     last_print_real_time: MicroSec,
 
+    backtest_start_time: MicroSec,
+    backtest_end_time: MicroSec,
+
     execute_mode: ExecuteMode,
     agent_id: String,
 
@@ -85,6 +87,9 @@ impl Runner {
             last_print_loop_count: 0,
             last_print_real_time: 0,
 
+            backtest_start_time: 0,
+            backtest_end_time: 0,
+
             agent_id: "".to_string(),
             config: MarketConfig::default(),
             exchange_name: "".to_string(),
@@ -109,7 +114,7 @@ impl Runner {
         self.last_print_real_time = 0;
     }
 
-    #[pyo3(signature = (*, exchange, market, agent, start_time=0, end_time=0, execute_time=0, verbose=false, log_file=None))]
+    #[pyo3(signature = (*, exchange, market, agent, start_time=0, end_time=0, execute_time=0, verbose=false, log_memory=false, log_file=None))]
     pub fn back_test(
         &mut self,
         exchange: &Bound<PyAny>,
@@ -119,18 +124,40 @@ impl Runner {
         end_time: MicroSec,
         execute_time: i64,
         verbose: bool,
+        log_memory: bool,
         log_file: Option<String>,
     ) -> anyhow::Result<Py<Session>> {
-        self.update_market_info(market)?;
-        self.update_agent_info(agent)?;
-
         self.execute_time = execute_time;
         self.print_interval = SEC(60 * 60);
         self.verbose = verbose;
         self.execute_mode = ExecuteMode::BackTest;
 
-        let receiver = Self::open_backtest_receiver(market, start_time, end_time)?;
-        self.run(exchange, market, &receiver, agent, false, true, log_file)
+        self.update_market_info(market)?;
+        self.update_agent_info(agent)?;
+
+        let (start_time, end_time, receiver) =
+            Self::open_backtest_receiver(market, start_time, end_time)?;
+
+        self.backtest_start_time = start_time;
+        self.backtest_end_time = end_time;
+
+        if verbose {
+            self.print_archive_info(market);
+        }
+
+        // let bar = ProgressBar::new()
+        let self_ref = self;
+
+        self_ref.run(
+            exchange,
+            market,
+            &receiver,
+            agent,
+            false,
+            log_memory,
+            log_file,
+            &mut |_, _remain_time| {},
+        )
     }
 
     #[pyo3(signature = (*, exchange, market, agent, log_memory=false, execute_time=0, verbose=false, log_file=None, client=false, no_download=false))]
@@ -163,7 +190,14 @@ impl Runner {
                 UdpReceiver::open_channel(&exchange_name, &category, &symbol, &agent_id)?;
 
             self.run(
-                exchange, market, &receiver, agent, client, log_memory, log_file,
+                exchange,
+                market,
+                &receiver,
+                agent,
+                client,
+                log_memory,
+                log_file,
+                &mut |_, _| {},
             )
         } else {
             self.prepare_data(exchange, &market, no_download)?;
@@ -171,7 +205,14 @@ impl Runner {
             let receiver = MARKET_HUB.subscribe(&exchange_name, &category, &symbol, &agent_id)?;
 
             self.run(
-                exchange, market, &receiver, agent, client, log_memory, log_file,
+                exchange,
+                market,
+                &receiver,
+                agent,
+                client,
+                log_memory,
+                log_file,
+                &mut |_, _| {},
             )
         }
     }
@@ -206,14 +247,28 @@ impl Runner {
                 UdpReceiver::open_channel(&exchange_name, &category, &symbol, &agent_id)?;
 
             self.run(
-                exchange, market, &receiver, agent, client, log_memory, log_file,
+                exchange,
+                market,
+                &receiver,
+                agent,
+                client,
+                log_memory,
+                log_file,
+                &mut |_s, _rt| {},
             )
         } else {
             self.prepare_data(exchange, market, no_download)?;
             let receiver = MARKET_HUB.subscribe(&exchange_name, &category, &symbol, &agent_id)?;
 
             self.run(
-                exchange, market, &receiver, agent, client, log_memory, log_file,
+                exchange,
+                market,
+                &receiver,
+                agent,
+                client,
+                log_memory,
+                log_file,
+                &mut |_s, _rt| {},
             )
         }
     }
@@ -236,13 +291,19 @@ impl Runner {
     }
 }
 
-const WARMUP_STEPS: i64 = 5;
+const MAX_WARMUP_STEPS: i64 = 500;
 
 impl Runner {
     pub fn agent_id(&self) -> String {
         "".to_string()
     }
 
+    pub fn print_archive_info(&self, market: &Bound<PyAny>) {
+        let info = self.archive_status(market);
+
+        println!("ARCHIVE has data [{}]", info);
+    }
+
     pub fn prepare_data(
         &self,
         exchange: &Bound<PyAny>,
@@ -284,7 +345,7 @@ impl Runner {
                 } else {
                     vec![("verbose", false)]
                 };
-                
+
                 //let kwargs = kwargs.into_py_dict_bound(py);
                 let kwargs = kwargs.into_py_dict_bound(py);
                 market.call_method("download_latest", (), Some(&kwargs))?;
@@ -301,6 +362,10 @@ impl Runner {
             Ok::<(), anyhow::Error>(()) // Add type annotation for Result
         })?;
 
+        if self.verbose {
+            self.print_archive_info(market);
+        }
+
         Ok(())
     }
 
@@ -443,7 +508,7 @@ impl Runner {
         Ok(())
     }
 
-    pub fn run(
+    pub fn run<F>(
         &mut self,
         exchange: &Bound<PyAny>,
         market: &Bound<PyAny>,
@@ -453,7 +518,11 @@ impl Runner {
         client_mode: bool,
         log_memory: bool,
         log_file: Option<String>,
-    ) -> anyhow::Result<Py<Session>> {
+        print_progress: &mut F,
+    ) -> anyhow::Result<Py<Session>>
+    where
+        F: FnMut(&Py<Session>, i64),
+    {
         self.start_timestamp = 0;
 
         let object = exchange.as_borrowed();
@@ -466,6 +535,7 @@ impl Runner {
             } else {
                 println!("--------- connect test net  ----------------");
             }
+
             match self.execute_mode {
                 ExecuteMode::Real => println!("************   REAL MODE   ****************"),
                 ExecuteMode::Dry => println!("------------   dry run mode   -------------"),
@@ -476,6 +546,34 @@ impl Runner {
             print!("agent_id: {}, ", self.agent_id);
             print!("log_memory: {}, ", log_memory);
             println!("duration: {}[sec], ", self.execute_time);
+            if let Some(file) = log_file.clone() {
+                println!("logfile: {}", file);
+            }
+            if client_mode {
+                println!("Client mode");
+            }
+
+            match self.execute_mode {
+                ExecuteMode::Real => {
+                    println!("************      START     ****************");
+                }
+                ExecuteMode::Dry => {
+                    println!("------------      START        -------------");
+                }
+                ExecuteMode::BackTest => {
+                    let days = microsec_to_sec(self.backtest_end_time - self.backtest_start_time)
+                        / 24
+                        / 60
+                        / 60;
+                    println!(
+                        "backtest from={} to={}[{}days]",
+                        date_time_string(self.backtest_start_time),
+                        date_time_string(self.backtest_end_time),
+                        days
+                    );
+                    println!("///////////       START        ////////////");
+                }
+            }
 
             flush_log();
         }
@@ -487,37 +585,57 @@ impl Runner {
         let interval_sec = self.get_clock_interval(&py_session)?;
 
         // warm up loop
-        let mut warm_up_loop: i64 = WARMUP_STEPS;
+        let mut warm_up_step: i64 = 1;
         while let Ok(message) = receiver.recv() {
             self.execute_message_update_session(&py_session, &message)?;
 
-            if let MarketMessage::Trade(_trade) = &message {
-                warm_up_loop = warm_up_loop - 1;
-                if warm_up_loop <= 0 {
-                    break;
-                }
+            log::debug!("warm up loop {:?}:{:?}", warm_up_step, message);
+
+            if self.is_session_initialized(&py_session) {
+                break;
             }
 
-            if self.verbose {
-                println!("warm up loop {:?}:{:?}", warm_up_loop, message);
+            if MAX_WARMUP_STEPS <= warm_up_step {
+                break;
             }
+
+            warm_up_step += 1;
         }
 
-        println!("------- warm up end ---------");
+        let m = MultiProgress::new();
+
+        let progress_bar = ProgressBar::new_spinner();
+        progress_bar.set_style(ProgressStyle::with_template("{msg}").unwrap());
+        let progress_bar = m.add(progress_bar);
+
+        let session_bar = ProgressBar::new_spinner();
+        let session_bar = m.add(session_bar);
+
+        let duration = microsec_to_sec(self.backtest_end_time - self.backtest_start_time);
+        let mut total_bar = ProgressBar::new(duration as u64);
+        total_bar.set_style(
+            ProgressStyle::with_template("[{elapsed_precise}]({percent:>3}%){bar:56}[ETA:{eta}]")
+                .unwrap(),
+        );
+
+        if self.execute_mode == ExecuteMode::BackTest {
+            total_bar = m.add(total_bar);
+        }
 
+        // main loop
         let mut remain_time: i64 = 0;
 
         let loop_start_time = NOW();
         while let Ok(message) = receiver.recv() {
+            //------- MAIN LOOP ---------
             self.execute_message(&py_session, agent, &message, interval_sec)?;
             self.loop_count += 1;
 
+            //-------print status etc.
             // break if the running time exceeceds the loop_duration
             if self.start_timestamp == 0 {
                 self.start_timestamp = self.last_timestamp;
-                if self.verbose {
-                    println!("start_timestamp: {}", time_string(self.start_timestamp));
-                }
+                log::debug!("start_timestamp: {}", time_string(self.start_timestamp));
             } else if 0 < self.execute_time {
                 remain_time = self.start_timestamp + SEC(self.execute_time) - self.last_timestamp;
 
@@ -527,9 +645,27 @@ impl Runner {
                 }
             }
 
-            // print progress if verbose flag is set.
-            if self.verbose {
-                self.print_progress(remain_time);
+            if self.print_interval < self.last_timestamp - self.last_print_tick_time
+                || self.last_print_tick_time == 0
+            {
+                if self.verbose {
+                    // self.print_progress(&py_session, remain_time);
+                    print_progress(&py_session, remain_time);
+
+                    let progress = self.progress_string(remain_time);
+                    progress_bar.set_message(progress);
+
+                    if self.execute_mode == ExecuteMode::BackTest {
+                        let sec_processed =
+                            microsec_to_sec(self.last_timestamp - self.start_timestamp);
+                        total_bar.set_position(sec_processed as u64);
+                    }
+
+                    let profit = self.get_profit(&py_session);
+
+                    session_bar.set_message(format!("  Psudo Profit(no fee) = {:>6.2}", profit));
+                }
+                self.last_print_tick_time = self.last_timestamp;
             }
         }
         self.print_run_result(loop_start_time);
@@ -537,6 +673,16 @@ impl Runner {
         Ok(py_session)
     }
 
+    fn get_profit(&self, py_session: &Py<Session>) -> Decimal {
+        let profit = Python::with_gil(|py| {
+            let profit = py_session.getattr(py, "profit").unwrap();
+            let profit: Decimal = profit.extract(py).unwrap();
+            profit
+        });
+
+        profit
+    }
+
     fn print_run_result(&self, loop_start_time: i64) {
         if self.verbose {
             println!(
@@ -567,19 +713,64 @@ impl Runner {
         }
     }
 
-    pub fn print_progress(&mut self, remain_time: i64) -> bool {
-        if self.last_timestamp - self.last_print_tick_time < self.print_interval
-            && self.last_print_tick_time != 0
-        {
-            return false;
-        }
+    fn progress_string(&self, remain_time: MicroSec) -> String {
+        let time = format!(
+            "[{:<.19}]  {:>10}[rec]",
+            time_string(self.last_timestamp),
+            HumanCount(self.loop_count as u64),
+        );
 
+        let on_tick = if self.on_tick_count != 0 {
+            format!("  {:>8}[tk]", HumanCount(self.on_tick_count as u64))
+        } else {
+            ",  **** [tk]".to_string()
+        };
+
+        let on_clock = if self.on_clock_count != 0 {
+            format!("  {:>8}[cl]", HumanCount(self.on_clock_count as u64))
+        } else {
+            ",  **** [cl]".to_string()
+        };
+
+        let on_update = if self.on_update_count != 0 {
+            format!("  {:>6}[up]", HumanCount(self.on_update_count as u64))
+        } else {
+            ",  **** [up]".to_string()
+        };
+
+        let remain_time = if 0 < remain_time {
+            format!("  remain={:>4}[S]", remain_time / 1_000_000)
+        } else {
+            "".to_string()
+        };
+
+        format!(
+            "{}{}{}{}{}",
+            time, on_tick, on_clock, on_update, remain_time
+        )
+    }
+
+    fn archive_status(&self, market: &Bound<PyAny>) -> String {
+        let obj = market.getattr("archive_info").unwrap();
+        let (start_time, end_time): (MicroSec, MicroSec) = obj.extract().unwrap();
+
+        let s = format!(
+            "[{} => {}]",
+            date_time_string(start_time),
+            date_time_string(end_time),
+        );
+
+        s
+    }
+
+    pub fn print_progress(&self, _py_session: &Py<Session>, remain_time: i64) {
         let mode = match self.execute_mode {
             ExecuteMode::Dry => "[Dry run ]",
             ExecuteMode::Real => "[Real run]",
             ExecuteMode::BackTest => "[BackTest]",
         };
 
+
         print!(
             "\r{}{:<.19}, {:>6}[rec]",
             mode,
@@ -603,6 +794,7 @@ impl Runner {
             print!(", {:>4}[ETA]", remain_time / 1_000_000);
         }
 
+        /*
         if self.execute_mode == ExecuteMode::BackTest {
             let count = self.loop_count - self.last_print_loop_count;
             self.last_print_loop_count = self.loop_count;
@@ -619,12 +811,9 @@ impl Runner {
 
             print!(", {:>7}[rec/s]({:>5} X)", rec_per_sec, speed,);
         }
+        */
 
         flush_log();
-
-        self.last_print_tick_time = self.last_timestamp;
-
-        return true;
     }
 
     pub fn on_message(
@@ -728,6 +917,13 @@ impl Runner {
         })
     }
 
+    fn is_session_initialized(&self, py_session: &Py<Session>) -> bool {
+        Python::with_gil(|py| {
+            let session = py_session.borrow_mut(py);
+            session.is_initialized()
+        })
+    }
+
     fn call_agent_on_init(
         self: &mut Self,
         agent: &Bound<PyAny>,
@@ -847,10 +1043,54 @@ impl Runner {
         market: &Bound<PyAny>,
         time_from: MicroSec,
         time_to: MicroSec,
-    ) -> anyhow::Result<Receiver<MarketMessage>> {
+    ) -> anyhow::Result<(MicroSec, MicroSec, Receiver<MarketMessage>)> {
         let stream = market.call_method1("open_backtest_channel", (time_from, time_to))?;
-        let stream = stream.extract::<MarketStream>()?;
+        let (start_time, end_time, stream) =
+            stream.extract::<(MicroSec, MicroSec, MarketStream)>()?;
+
+        Ok((start_time, end_time, stream.reciver))
+    }
+}
+
+#[cfg(test)]
+mod test_runner {
+    use std::{thread::sleep, time::Duration};
+
+    use indicatif::{MultiProgress, ProgressStyle};
+
+    #[test]
+    fn test_progress_bar() {
+        // https://docs.rs/indicatif/latest/indicatif/index.html#
+
+        use indicatif::ProgressBar;
+
+        let m = MultiProgress::new();
+
+        let text_bar = m.add(ProgressBar::new_spinner());
+
+        let main_bar = ProgressBar::new(100);
+        main_bar.set_style(
+            ProgressStyle::with_template("[{elapsed}] {bar:40.cyan/bule} {msg}").unwrap(),
+        );
+        let main_bar = m.add(main_bar);
+
+        let sub_bar = ProgressBar::new(100);
+        let sub_bar = m.add(sub_bar);
+
+        for i in 0..100 {
+            text_bar.set_message(format!("process {}", i));
+
+            for _ in 0..100 {
+                sleep(Duration::from_millis(10));
+                sub_bar.inc(1);
+            }
+            sub_bar.reset();
+
+            // sub_bar.finish_with_message("DONE");
+            main_bar.inc(1);
+        }
 
-        Ok(stream.reciver)
+        sub_bar.finish();
+        main_bar.finish();
     }
 }
diff --git a/rbot_session/src/session.rs b/rbot_session/src/session.rs
index 8ca57f52..027afbc2 100644
--- a/rbot_session/src/session.rs
+++ b/rbot_session/src/session.rs
@@ -77,7 +77,8 @@ pub struct Session {
 
     psudo_position: Decimal,
     average_price: Decimal,
-    profit: Decimal,
+    #[pyo3(get)]
+    pub profit: Decimal,
 
     commission_home_sum: Decimal,
     commission_foreign_sum: Decimal,
@@ -118,7 +119,7 @@ impl Session {
         log_memory: bool,
     ) -> Self {
         log::info!("Session::new: exchange={:?}, market={:?}, execute_mode={:?}, client_mode={:?}, session_name={:?}, log_memory={:?}", exchange, market, execute_mode, client_mode, session_name, log_memory);
-        println!("new session");
+
         let session_name = match session_name {
             Some(name) => name.to_string(),
             None => {
@@ -787,6 +788,22 @@ impl Session {
         TradeDataFrame::get(&market_config, true) // always use production other than primary market.
     }
 
+    pub fn is_initialized(&self)  -> bool {
+        if self.current_timestamp == 0 {
+            return false;
+        }
+
+        if self.ask_edge == dec![0.0] {
+            return false;
+        }
+
+        if self.bid_edge == dec![0.0] {
+            return false;
+        }
+
+        true
+    }
+
     /// Message処理
     /// Dummyのときは、Tradeで約定情報を受け取り、約定キューに追加する。
     pub fn on_message(&mut self, message: &MarketMessage) -> Vec<Order> {
@@ -871,12 +888,12 @@ impl Session {
         if tick.order_side == OrderSide::Buy {
             self.ask_edge = tick.price;
             if self.ask_edge <= self.bid_edge {
-                self.bid_edge = self.ask_edge - self.market_config.price_unit;
+                self.bid_edge = self.ask_edge - self.market_config.get_price_unit();
             }
         } else if tick.order_side == OrderSide::Sell {
             self.bid_edge = tick.price;
             if self.ask_edge <= self.bid_edge {
-                self.ask_edge = self.bid_edge + self.market_config.price_unit;
+                self.ask_edge = self.bid_edge + self.market_config.get_price_unit();
             }
         }
 


Test patch:
diff --git a/exchanges/bybit/src/archive_test.rs b/exchanges/bybit/src/archive_test.rs
index 99302db9..caf66584 100644
--- a/exchanges/bybit/src/archive_test.rs
+++ b/exchanges/bybit/src/archive_test.rs
@@ -80,7 +80,7 @@ mod archive_test {
 
         let tmp_dir = PathBuf::from_str("/tmp/")?;
 
-        let file = log_download_tmp(&url, &tmp_dir).await?;    
+        let file = log_download_tmp(&url, &tmp_dir, |_, _|{}).await?;    
 
         log::debug!("download complete {:?}", file);
 
@@ -96,7 +96,7 @@ mod archive_test {
         let api = BybitRestApi::new(&server_config);
 
 
-        archive.web_archive_to_parquet(&api, NOW() - DAYS(2), false, true).await.unwrap();
+        archive.web_archive_to_parquet(&api, NOW() - DAYS(2), false, true, |_, _|{}).await.unwrap();
     }
 
     #[test]
@@ -135,10 +135,10 @@ mod archive_test {
         let api = BybitRestApi::new(&server_config);
 
 
-        archive.web_archive_to_parquet(&api, NOW() - DAYS(2), false, true).await?;
-        archive.web_archive_to_parquet(&api, NOW() - DAYS(3), false, true).await?;
+        archive.web_archive_to_parquet(&api, NOW() - DAYS(2), false, true, |_, _|{}).await?;
+        archive.web_archive_to_parquet(&api, NOW() - DAYS(3), false, true, |_, _|{}).await?;
         archive
-            .web_archive_to_parquet(&api, NOW() - DAYS(10), false, true)
+            .web_archive_to_parquet(&api, NOW() - DAYS(10), false, true, |_, _|{})
             .await?;
 
         log::debug!(
diff --git a/rbot/test/bybit_test/nullagent.py b/rbot/test/bybit_test/nullagent.py
index b1a7052e..7571fd81 100644
--- a/rbot/test/bybit_test/nullagent.py
+++ b/rbot/test/bybit_test/nullagent.py
@@ -40,9 +40,10 @@ def on_clock(self, session, clock):
         """
         # 現在の時刻をプリントします。
 #        print("on_clock: ", clock, ": ", time_string(clock))
-        #session.market_order("Buy", 0.001)
-        pass
-    
+        session.market_order("Buy", 0.001)
+        session.market_order("Sell", 0.001)
+
+
     def on_update(self, session, updated_order):
         """自分の注文状態が変化した場合に呼び出される処理です。
         Args:
@@ -52,7 +53,6 @@ def on_update(self, session, updated_order):
         """
         # 注文状態が変化した注文情報をプリントします。オーダーを発行しない限り呼び出されません。
 #        print("on_update", updated_order)    
-        pass
 
     
 from rbot import Bybit, BybitConfig
@@ -81,6 +81,7 @@ def on_update(self, session, updated_order):
                 exchange=bybit,
                 market=market,
                 agent=agent, 
+                log_memory=False,
 #                execute_time = 10,
                 verbose=True,
                 log_file="skelton_bot.log",