Repository: yasstake/rbot (language: python)

Issue:
Psudo_profitの計算

Session Logの実装。
オーダーのステータスがかわるごとに１レコード追加する。

・ New
・Cancel
・Filled
・PartiallyFilled




Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 45a3f1fe..bd5f150b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rbot"
-version = "0.2.6"
+version = "0.2.7"
 edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -24,6 +24,8 @@ serde = {version="1.0"}
 serde_json = {version = "1.0"}
 serde_derive = {version = "1.0"}
 
+serde_with = {version = "3.4.0"}
+
 log = {version = "0.4"}
 env_logger = {version = "0.10.0"}
 
diff --git a/src/common/ch.rs b/src/common/ch.rs
index de4aabf6..d40026ef 100644
--- a/src/common/ch.rs
+++ b/src/common/ch.rs
@@ -112,6 +112,8 @@ impl MultiChannel {
             let result = channel.sender.send(message.clone());
 
             if result.is_err() {
+                log::warn!("Send ERROR: {:?}. remove channel", result);                
+                channel.valid = false;
                 has_error = true;
             }
         }
diff --git a/src/common/mod.rs b/src/common/mod.rs
index b32f7117..b47b435c 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -6,6 +6,8 @@ mod config;
 mod account;
 
 
+use std::io::Write;
+
 pub use time::*;
 pub use order::*;
 pub use ch::*;
@@ -14,3 +16,7 @@ pub use order::*;
 pub use config::*;
 pub use account::*;
 
+pub fn flush_log() {
+    let _ = std::io::stdout().flush();
+    let _ = std::io::stderr().flush();
+}
diff --git a/src/common/order.rs b/src/common/order.rs
index 96b51b4a..5ea1a522 100644
--- a/src/common/order.rs
+++ b/src/common/order.rs
@@ -408,19 +408,29 @@ pub struct AccountStatus {
 impl Default for AccountStatus {
     fn default() -> Self {
         return AccountStatus {
-            home: Decimal::new(0, 0),
-            home_free: Decimal::new(0, 0),
-            home_locked: Decimal::new(0, 0),
+            home: dec![0.0],
+            home_free: dec![0.0],
+            home_locked: dec![0.0],
 
-            foreign: Decimal::new(0, 0),
-            foreign_free: Decimal::new(0, 0),
-            foreign_locked: Decimal::new(0, 0),
+            foreign: dec![0.0],
+            foreign_free: dec![0.0],
+            foreign_locked: dec![0.0]
         };
     }
 }
 
 #[pymethods]
 impl AccountStatus {
+    pub fn apply_order(&mut self, order: &Order) {
+        self.foreign += order.foreign_change;
+        self.foreign_free += order.free_foreign_change;
+        self.foreign_locked += order.lock_foreign_change;
+
+        self.home += order.home_change;
+        self.home_free += order.free_home_change;
+        self.home_locked += order.lock_home_change;
+    }
+
     pub fn __str__(&self) -> String {
         self.__repr__()
     }
@@ -454,6 +464,11 @@ impl AccountStatus {
     }
 }
 
+
+
+
+
+
 #[pyclass]
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct Order {
@@ -506,9 +521,12 @@ pub struct Order {
     pub free_foreign_change: Decimal,
     pub lock_home_change: Decimal,
     pub lock_foreign_change: Decimal,
+    pub log_id: i64,
 }
 
+#[pymethods]
 impl Order {
+    #[new]
     pub fn new(
         symbol: String,
         create_time: MicroSec,
@@ -548,12 +566,10 @@ impl Order {
             free_foreign_change: dec![0.0],
             lock_home_change: dec![0.0],
             lock_foreign_change: dec![0.0],
+            log_id: 0,
         };
     }
-}
 
-#[pymethods]
-impl Order {
     pub fn __str__(&self) -> String {
         self.__repr__()
     }
@@ -670,6 +686,7 @@ impl Into<MarketMessage> for &Order {
 }
 
 pub fn ordervec_to_dataframe(orders: Vec<Order>) -> DataFrame {
+    let mut log_id = Vec::<i64>::new();
     let mut symbol = Vec::<String>::new();
     let mut create_time = Vec::<MicroSec>::new();
     let mut status = Vec::<String>::new();
@@ -699,6 +716,7 @@ pub fn ordervec_to_dataframe(orders: Vec<Order>) -> DataFrame {
     let mut lock_foreign_change = Vec::<f64>::new();
 
     for order in orders {
+        log_id.push(order.log_id);
         symbol.push(order.symbol.clone());
         create_time.push(order.create_time);
         status.push(order.status.to_string());
@@ -729,6 +747,7 @@ pub fn ordervec_to_dataframe(orders: Vec<Order>) -> DataFrame {
         lock_foreign_change.push(order.lock_foreign_change.to_f64().unwrap());
     }
 
+    let log_id = Series::new("log_id", log_id);
     let symbol = Series::new("symbol", symbol);
     let create_time = Series::new("create_time", create_time);
     let status = Series::new("status", status);
@@ -759,7 +778,9 @@ pub fn ordervec_to_dataframe(orders: Vec<Order>) -> DataFrame {
     let lock_foreign_change = Series::new("lock_foreign_change", lock_foreign_change);
 
     let mut df = DataFrame::new(vec![
+        log_id,
         symbol,
+        update_time,
         create_time,
         status,
         order_id,
@@ -770,7 +791,6 @@ pub fn ordervec_to_dataframe(orders: Vec<Order>) -> DataFrame {
         order_size,
         remain_size,
         transaction_id,
-        update_time,
         execute_price,
         execute_size,
         quote_vol,
@@ -815,38 +835,91 @@ impl Order {
         }
     }
 
-    /// in order book, accout locked the size of order
-    fn update_balance_new(&mut self, _config: &MarketConfig) {
-        if !self.is_maker {
-            return;
+    fn calc_quote_vol(&self) -> Decimal {
+        return self.order_price * self.order_size;
+    }
+
+    fn calc_home_lock_size_new(&self) -> Decimal {
+        match self.is_maker {
+            true => self.calc_quote_vol() + self.commission_home,
+            false => dec![0.0],
         }
+    }
 
-        let order_size = self.order_size;
-        let order_quote_vol = self.order_size * self.order_price;
+    fn calc_foreign_lock_size_new(&self) -> Decimal {
+        match self.is_maker {
+            true => self.order_size + self.commission_foreign,
+            false => dec![0.0],
+        }
+    }
 
-        if self.order_side == OrderSide::Buy {
-            // move home to foregin
-            self.commission_home = dec![0.0];
-            self.commission_foreign = dec![0.0];
-            self.home_change = dec![0.0];
-            self.foreign_change = dec![0.0];
-            self.free_home_change = -order_quote_vol;
-            self.free_foreign_change = dec![0.0];
-            self.lock_home_change = order_quote_vol;
-            self.lock_foreign_change = dec![0.0];
-        } else {
-            // Sell
-            self.commission_home = dec![0.0];
-            self.commission_foreign = dec![0.0];
-            self.home_change = dec![0.0];
-            self.foreign_change = dec![0.0];
-            self.free_home_change = dec![0.0];
-            self.free_foreign_change = -order_size;
-            self.lock_home_change = dec![0.0];
-            self.lock_foreign_change = order_size;
+    /*
+    fn calc_execute_vol(&self) -> Decimal {
+        return self.execute_price * self.execute_size;
+    }
+    */
+
+    /*
+    fn calc_home_lock_change(&self) -> Decimal {
+        match self.is_maker {
+            true => {
+                match self.order_side {
+                    OrderSide::Buy => {
+                        return -self.calc_quote_vol();
+                    }
+                    OrderSide::Sell => {
+                        dec![0,0]
+                    }
+                    _ => {
+                        log::warn!("Order.calc_home_lock_change: Unknown order side. order={:?}", self);
+                        return dec![0.0];
+                    }
+
+                }
+            }
+            false => {
+                return dec![0.0];
+            }
         }
     }
 
+    fn calc_home_change(&self) -> Decimal {
+        let change = self.calc_execute_vol() - self.commission_home;
+        match self.order_side {
+            OrderSide::Buy => {
+                -change
+            }
+            OrderSide::Sell => {
+                change
+            }
+            _ => {
+                log::warn!("Order.calc_home_change: Unknown order side. order={:?}", self);
+                dec![0.0]
+            }
+        }
+    }
+
+    fn calc_foreign_change(&self) -> Decimal {
+        let change = self.execute_size - self.commission_foreign;
+
+        match self.order_side {
+            OrderSide::Buy => {
+                change
+            }
+            OrderSide::Sell => {
+                -change
+            }
+            _ => {
+                log::warn!("Order.calc_foreign_change: Unknown order side. order={:?}", self);
+                return dec![0.0]
+            }
+        }
+    }
+    */
+
+    // TODO: BackTest時の計算を追加する。
+    // 通常は、約定時にサーバからのデータで確定するが、
+    // BackTest時は、ローカルで計算する必要がある。
     fn update_commision(&mut self, config: &MarketConfig) {
         let commission = self.commission;
         let commission_asset = self.commission_asset.clone();
@@ -860,74 +933,88 @@ impl Order {
         }
     }
 
-    /// The lock is freed and shift the balance.
-    fn update_balance_filled(&mut self, config: &MarketConfig) {
+    /// in order book, accout locked the size of order
+    /// order bookでは、オーダーのサイズがロックされる。
+    /// このロックサイズは、約定時に解除される。
+    /// home, foreignは変化しない。
+    fn update_balance_new(&mut self, config: &MarketConfig) {
         self.update_commision(config);
 
-        let filled_size = self.execute_size;
-        let filled_quote_vol = self.execute_size * self.execute_price;
+        let home_lock_size = self.calc_home_lock_size_new();
+        let foreign_lock_size = self.calc_foreign_lock_size_new();
+
+        //let home_change = self.calc_quote_vol();
+        //let foreign_change = self.execute_size;
 
         if self.order_side == OrderSide::Buy {
-            // move home to foregin
-            self.home_change = (-filled_quote_vol) - self.commission_home;
-            self.foreign_change = filled_size - self.commission_foreign;
+            //self.commission_home = dec![0.0];
+            //self.commission_foreign = dec![0.0];
+            self.home_change = dec![0.0];
+            self.free_home_change = -home_lock_size;
+            self.lock_home_change =  home_lock_size;            
 
-            // update foreign
-            self.free_foreign_change = self.foreign_change;
+            self.foreign_change = dec![0.0];
+            self.free_foreign_change = dec![0.0];
             self.lock_foreign_change = dec![0.0];
-
-            if self.is_maker {
-                // Maker -> homeでlockしたものがforeignに変化する。freeは変化しない。
-                self.lock_home_change = -filled_quote_vol;
-                self.free_home_change = dec![0.0];
-            } else {
-                // Taker -> lockは０。freeだったものからforeignに変化する。
-                self.lock_home_change = dec![0.0];
-                self.free_home_change = self.home_change;
-            }
-        } else {
-            // Sell
-            // move foreign to home
-            self.foreign_change = (-filled_size) - self.commission_foreign;
-            self.home_change = filled_quote_vol - self.commission_home;
-
-            // update home
-            self.free_home_change = self.home_change;
+        }
+        else {
+            //self.commission_home = dec![0.0];
+            //self.commission_foreign = dec![0.0];
+            self.home_change = dec![0.0];
+            self.free_home_change = dec![0.0];
             self.lock_home_change = dec![0.0];
 
-            if self.is_maker {
-                self.lock_foreign_change = -filled_size;
-                self.free_foreign_change = dec![0.0];
-            } else {
-                self.lock_foreign_change = dec![0.0];
-                self.free_foreign_change = self.foreign_change;
-            }
+            self.foreign_change = dec![0.0];
+            self.free_foreign_change = -foreign_lock_size;
+            self.lock_foreign_change = foreign_lock_size;
         }
+    }
+
+    /// The lock is freed and shift the balance.
+    /// Lockが解除され、残高が移動する。
+    fn update_balance_filled(&mut self, config: &MarketConfig) {
+        self.update_commision(config);
+
+        let home_lock = self.calc_home_lock_size_new();
+        let foreign_lock = self.calc_foreign_lock_size_new();
 
-        // home_change = home_free_change + home_locked_change - commission_home
-        if self.home_change
-            != (self.free_home_change + self.lock_home_change - self.commission_home)
-        {
-            println!(
-                "Order.update_balance_filled: home_change != (free_home_change + lock_home_change). order={:?}",
-                self
-            )
+        let filled_ratio = self.execute_size / self.order_size;
+
+        let home_lock_size_change = home_lock * filled_ratio;
+        let foreign_lock_size_change = foreign_lock * filled_ratio;
+
+        let home_change = self.execute_price * self.execute_size;
+        let foreign_change = self.execute_size;
+
+        if self.order_side == OrderSide::Buy {
+            //self.commission_home = dec![0.0];
+            //self.commission_foreign = dec![0.0];
+            self.home_change = -home_change;
+            self.free_home_change = dec![0.0];            
+            self.lock_home_change = -home_lock_size_change;            
+
+            self.foreign_change = foreign_change;
+            self.free_foreign_change = foreign_change;
+            self.lock_foreign_change = dec![0.0];
         }
+        else {
+            //self.commission_home = dec![0.0];
+            //self.commission_foreign = dec![0.0];
+            self.home_change = home_change;
+            self.free_home_change = home_change;
+            self.lock_home_change = dec![0.0];
 
-        if self.foreign_change
-            != (self.free_foreign_change + self.lock_foreign_change - self.commission_foreign)
-        {
-            println!(
-                "Order.update_balance_filled: foreign_change != (free_foreign_change + lock_foreign_change). order={:?}",
-                self
-            )
+            self.foreign_change = -foreign_change;
+            self.free_foreign_change = -foreign_change;
+            self.lock_foreign_change = -foreign_lock_size_change;
         }
     }
 
     //
-    fn update_balance_canceled(&mut self, _config: &MarketConfig) {
-        let order_size = self.order_size;
-        let order_quote_vol = self.order_size * self.order_price;
+    fn update_balance_canceled(&mut self, config: &MarketConfig) {
+        self.update_commision(config);
+        let remain_size = self.remain_size;
+        let order_quote_vol = self.order_price * remain_size;
 
         if self.order_side == OrderSide::Buy {
             // move home to foregin
@@ -946,9 +1033,166 @@ impl Order {
             self.home_change = dec![0.0];
             self.foreign_change = dec![0.0];
             self.free_home_change = dec![0.0];
-            self.free_foreign_change = order_size;
+            self.free_foreign_change = remain_size;
             self.lock_home_change = dec![0.0];
-            self.lock_foreign_change = -order_size;
+            self.lock_foreign_change = -remain_size;
         }
     }
 }
+
+
+#[cfg(test)]
+mod order_tests {
+    use crate::exchange::binance::BinanceConfig;
+
+    use super::*;
+
+    fn create_order() -> Order {
+        let mut order = Order::new(
+            "".to_string(),
+            0,
+            "".to_string(),
+            "".to_string(),
+            OrderSide::Buy,
+            OrderType::Limit,
+            OrderStatus::New,
+            dec![1234.5],
+            dec![0.0001],
+        );
+        order.update_time = 0;
+        order
+    }
+
+    #[test]
+    fn test_update_balance_filled() {
+        let mut order = create_order();
+        let config = BinanceConfig::BTCUSDT();
+        let market_config = config.market_config.clone();
+
+        //// ＊＊＊＊Limit Orderの場合＊＊＊＊
+        // Newの場合は、Lockが増える。
+        order.order_side = OrderSide::Buy;
+        order.status = OrderStatus::New;
+        order.is_maker = true;
+        order.update_balance(&market_config);
+
+        println!("order={:?}", order);
+        assert_eq!(order.home_change, dec![0.0], "Incorrect home_lock value");
+        assert_eq!(order.lock_home_change,
+            dec![0.12345],
+            "Incorrect home_lock value"
+        );
+        assert_eq!(
+            order.foreign_change,
+            dec![0.0],
+            "Incorrect home_lock value"
+        );
+        assert_eq!(
+            order.lock_foreign_change,
+            dec![0.0],
+            "Incorrect lock-home_lock value"
+        );
+
+        order.order_side = OrderSide::Sell;
+        order.status = OrderStatus::New;
+        order.is_maker = true;
+        order.update_balance(&market_config);
+
+        println!("order={:?}", order);
+        assert_eq!(order.home_change, dec![0.0], "Incorrect home_lock value");
+        assert_eq!(order.lock_home_change,
+            dec![0.0],
+            "Incorrect home_lock value"
+        );
+        assert_eq!(
+            order.foreign_change,
+            dec![0.0],
+            "Incorrect home_lock value"
+        );
+        assert_eq!(
+            order.lock_foreign_change,
+            dec![0.0001],
+            "Incorrect lock-home_lock value"
+        );
+    }
+
+    #[test]
+    fn test_order_fill_update_balance() {
+        let mut order = create_order();
+        let config = BinanceConfig::BTCUSDT();
+        let market_config = config.market_config.clone();
+
+        //------------------FILLED-----------------
+        // Filledの場合は、残高が移動する。
+        // &Makerの場合は、Lockが減り、Freeが増える。
+
+        // buy side
+        order.status = OrderStatus::Filled;
+        order.order_side = OrderSide::Buy;
+        order.execute_price = dec![1234.5];
+        order.execute_size = dec![0.0001];
+        order.is_maker = true;
+        order.update_balance(&market_config);
+
+        println!("order={:?}", order);
+        assert_eq!(order.home_change, dec![-0.12345]);
+        assert_eq!(order.foreign_change, dec![0.0001]);
+
+        assert_eq!(order.lock_home_change, dec![-0.12345]);
+        assert_eq!(order.lock_foreign_change, dec![0.0]);
+
+        // sell side
+        order.status = OrderStatus::Filled;
+        order.order_side = OrderSide::Sell;
+        order.execute_price = dec![1234.5];
+        order.execute_size = dec![0.0001];
+        order.is_maker = true;
+        order.update_balance(&market_config);
+
+        println!("order={:?}", order);
+        assert_eq!(order.home_change, dec![0.12345]);
+        assert_eq!(order.foreign_change, dec![-0.0001]);
+
+        assert_eq!(order.lock_home_change, dec![0.0]);
+        assert_eq!(order.lock_foreign_change, dec![-0.0001]);
+
+        // maker ではない場合はLockしないのでLockの数は変化しない。
+        order.is_maker = false;
+        order.update_balance(&market_config);        
+        assert_eq!(order.lock_home_change, dec![0.0]);
+        assert_eq!(order.lock_foreign_change, dec![0.0]);
+    }
+
+    #[test]
+    fn test_order_calncel_update_balance() {
+        let mut order = create_order();
+        let config = BinanceConfig::BTCUSDT();
+        let market_config = config.market_config.clone();
+
+        order.status = OrderStatus::Canceled;
+        order.order_side = OrderSide::Buy;
+        order.order_price = dec![1234.5];
+        order.order_size = dec![0.0001];
+        order.is_maker = true;
+        order.update_balance(&market_config);
+
+        assert_eq!(order.home_change, dec![0.0]);
+        assert_eq!(order.foreign_change, dec![0.0]);        
+
+        assert_eq!(order.free_home_change, dec![0.12345]);
+        assert_eq!(order.free_foreign_change, dec![0.0]);
+
+        assert_eq!(order.lock_home_change, dec![-0.12345]);
+        assert_eq!(order.lock_foreign_change, dec![0.0]);
+
+
+        order.order_side = OrderSide::Sell;
+        order.update_balance(&market_config);        
+        assert_eq!(order.free_home_change, dec![0.0]);
+        assert_eq!(order.free_foreign_change, dec![0.0001]);
+
+        assert_eq!(order.lock_home_change, dec![0.0]);
+        assert_eq!(order.lock_foreign_change, dec![-0.0001]);
+
+    }
+}
diff --git a/src/db/df.rs b/src/db/df.rs
index 05b442ab..a15d9075 100644
--- a/src/db/df.rs
+++ b/src/db/df.rs
@@ -20,7 +20,7 @@ use polars_time::ClosedWindow;
 #[allow(non_upper_case_globals)]
 #[allow(non_snake_case)]
 pub mod KEY {
-    pub const time_stamp: &str = "time_stamp";
+    pub const time_stamp: &str = "timestamp";
 
     // for trade
     pub const price: &str = "price";
diff --git a/src/db/sqlite.rs b/src/db/sqlite.rs
index 0eee2c2f..5d7ae228 100644
--- a/src/db/sqlite.rs
+++ b/src/db/sqlite.rs
@@ -16,6 +16,7 @@ use rust_decimal::Decimal;
 use crate::common::LogStatus;
 use crate::common::OrderSide;
 use crate::common::SEC;
+use crate::common::flush_log;
 use crate::common::{time_string, MicroSec, CEIL, DAYS, FLOOR_DAY, FLOOR_SEC, NOW};
 use crate::common::{TimeChunk, Trade};
 use crate::db::df::merge_df;
@@ -248,7 +249,34 @@ impl TradeTableDb {
         }
     }
 
+    fn is_table_exsit(&self) -> bool {
+        let sql = "select count(*) from sqlite_master where type='table' and name='trades'";
+
+        let result = self.connection.query_row(sql, [], |row| {
+            let count: i64 = row.get(0)?;
+            Ok(count)
+        });
+
+        match result {
+            Ok(count) => {
+                if count == 0 {
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+            Err(e) => {
+                log::error!("is_table_exsit error {:?}", e);
+                return false;
+            }
+        }
+    }
+
     fn create_table_if_not_exists(&self) -> Result<(), Error> {
+        if self.is_table_exsit() {
+            return Ok(());
+        }
+
         let _r = self.connection.execute(
             "CREATE TABLE IF NOT EXISTS trades (
             time_stamp    INTEGER,
@@ -280,16 +308,17 @@ impl TradeTableDb {
     }
 
     fn drop_table(&self) -> Result<(), Error> {
+        println!("database is dropped. To use database, restart the program.");
+        println!("To delete completely, delete the database file from OS.");
+        println!("DB file = {}", self.file_name);
+        flush_log();
+
         let r = self.connection.execute("drop table trades", ());
 
         if r.is_err() {
             log::error!("drop table error {:?}", r);
         }
 
-        print!("database is dropped. To use database, restart the program.");
-        print!("to delete completely, delete the database file from OS.");
-        print!("DB file = {}", self.file_name);
-
         self.vacuum()?;
 
         Ok(())
@@ -1225,7 +1254,8 @@ impl TradeTable {
 
     pub fn drop_table(&self) -> Result<(), Error> {
         if self.is_thread_running() {
-            println!("WARNING: thread is running, may cause dead lock. Execute drop_table() before start_market_stream().");
+            println!("WARNING: thread is running, may cause dead lock");
+            println!("Execute drop_table() before download() or start_market_stream().");
         }
 
         self.connection.drop_table()
@@ -1399,7 +1429,7 @@ mod test_transaction_table {
 
     #[test]
     fn test_select_gap_chunks() {
-        let db_name = db_full_path("FTX", "SPOT", "BTC-PERP");
+        let db_name = db_full_path("FTX", "SPOT", "BTC-PERP", "");
         let db = TradeTable::open(db_name.to_str().unwrap()).unwrap();
 
         let chunks = db.select_gap_chunks(NOW() - DAYS(1), NOW(), 1_000_000 * 13);
@@ -1419,7 +1449,7 @@ mod test_transaction_table {
 
     #[test]
     fn test_select_time_chunk_from() {
-        let db_name = db_full_path("FTX", "SPOT", "BTC-PERP");
+        let db_name = db_full_path("FTX", "SPOT", "BTC-PERP", "");
         let db = TradeTable::open(db_name.to_str().unwrap()).unwrap();
 
         let chunks = db.find_time_chunk_from(NOW() - DAYS(1), NOW(), 1_000_000 * 10);
@@ -1433,7 +1463,7 @@ mod test_transaction_table {
 
     #[test]
     fn test_select_time_chunk_to() {
-        let db_name = db_full_path("FTX", "SPOT", "BTC-PERP");
+        let db_name = db_full_path("FTX", "SPOT", "BTC-PERP", "/tmp");
         let db = TradeTable::open(db_name.to_str().unwrap()).unwrap();
 
         let chunks = db.find_time_chunk_to(NOW() - DAYS(1), NOW(), 1_000_000 * 120);
@@ -1447,7 +1477,7 @@ mod test_transaction_table {
 
     #[test]
     fn test_select_time_chunks() {
-        let db_name = db_full_path("FTX", "SPOT", "BTC-PERP");
+        let db_name = db_full_path("FTX", "SPOT", "BTC-PERP", "/tmp");
         let db = TradeTable::open(db_name.to_str().unwrap()).unwrap();
 
         let chunks = db.select_time_chunks_in_db(NOW() - DAYS(1), NOW(), 1_000_000 * 10);
@@ -1482,7 +1512,7 @@ mod test_transaction_table {
     #[test]
     fn test_select_ohlcv_df() {
         init_log();
-        let db_name = db_full_path("BN", "SPOT", "BTCBUSD");
+        let db_name = db_full_path("BN", "SPOT", "BTCBUSD", "/tmp");
 
         let mut db = TradeTable::open(db_name.to_str().unwrap()).unwrap();
 
@@ -1522,7 +1552,7 @@ mod test_transaction_table {
     fn test_select_print() {
         init_log();
 
-        let db_name = db_full_path("BN", "SPOT", "BTCBUSD");
+        let db_name = db_full_path("BN", "SPOT", "BTCBUSD", "/tmp");
         let mut db = TradeTable::open(db_name.to_str().unwrap()).unwrap();
 
         let start = NOW();
@@ -1533,7 +1563,7 @@ mod test_transaction_table {
 
     #[test]
     fn test_select_df() {
-        let db_name = db_full_path("BN", "SPOT", "BTCBUSD");
+        let db_name = db_full_path("BN", "SPOT", "BTCBUSD", "/tmp");
         let mut db = TradeTable::open(db_name.to_str().unwrap()).unwrap();
 
         let df = db.select_df_from_db(NOW() - DAYS(2), NOW());
@@ -1544,7 +1574,7 @@ mod test_transaction_table {
     #[test]
     fn test_update_cache() {
         init_log();
-        let db_name = db_full_path("BN", "SPOT", "BTCBUSD");
+        let db_name = db_full_path("BN", "SPOT", "BTCBUSD", "/tmp");
         let mut db = TradeTable::open(db_name.to_str().unwrap()).unwrap();
 
         db.update_cache_df(NOW() - DAYS(2), NOW());
@@ -1553,7 +1583,7 @@ mod test_transaction_table {
     #[test]
     fn test_start_thread() {
         let mut table =
-            TradeTable::open(db_full_path("BN", "SPOT", "BTCBUSD").to_str().unwrap()).unwrap();
+            TradeTable::open(db_full_path("BN", "SPOT", "BTCBUSD", "/tmp").to_str().unwrap()).unwrap();
         let tx = table.start_thread();
 
         let v = vec![Trade {
@@ -1595,6 +1625,23 @@ mod test_transaction_table {
     fn test_wal_mode() {
         //let table = TradeTable::open(db_full_path("BN", "SPOT", "BTCBUSD").to_str().unwrap()).unwrap();
 
-        TradeTableDb::set_wal_mode(db_full_path("BN", "SPOT", "BTCBUSD").to_str().unwrap());
+        TradeTableDb::set_wal_mode(db_full_path("BN", "SPOT", "BTCBUSD", "/tmp").to_str().unwrap());
+    }
+
+    #[test]
+    fn test_table_is_exsit() {
+        //let db_name = db_full_path("BN", "SPOT", "BTCUSDT");
+        
+        let db = TradeTableDb::open("/tmp/rbottest.db").unwrap();
+
+        if db.is_table_exsit() {
+            print!("table is exist");
+        }
+        else {
+            print!("table is not exist");
+        }
     }
+
+
+
 }
diff --git a/src/exchange/bb/mod.rs b/src/exchange/bb/mod.rs
index a112113c..2617e974 100644
--- a/src/exchange/bb/mod.rs
+++ b/src/exchange/bb/mod.rs
@@ -63,7 +63,7 @@ impl BBMarket {
 
     #[staticmethod]
     pub fn db_path(market_name: &str) -> PyResult<String> {
-        let db_name = db_full_path("BB", "trade", market_name);
+        let db_name = db_full_path("BB", "trade", market_name, "");
 
         return Ok(db_name.as_os_str().to_str().unwrap().to_string());
     }
diff --git a/src/exchange/binance/config.rs b/src/exchange/binance/config.rs
index ca31afed..e6636f16 100644
--- a/src/exchange/binance/config.rs
+++ b/src/exchange/binance/config.rs
@@ -40,7 +40,10 @@ pub struct BinanceConfig {
     pub api_secret: String,
 
     #[pyo3(get)]
-    pub market_config: MarketConfig
+    pub market_config: MarketConfig,
+
+    #[pyo3(get, set)]
+    pub db_base_dir: String,
 }
 
 #[pymethods]
@@ -103,13 +106,15 @@ impl BinanceConfig {
             "".to_string()
         };
 
-        let market_config = MarketConfig::new(
+        let mut market_config = MarketConfig::new(
             home_symbol,
             foreign_symbol,
             2,
             4,
         );
 
+        market_config.taker_fee = dec![0.0001];
+        market_config.maker_fee = dec![0.0001];
 
         return BinanceConfig {
             test_net: false,
@@ -145,6 +150,7 @@ impl BinanceConfig {
             api_key,
             api_secret,
             market_config,
+            db_base_dir: "".to_string(),
         };
     }
 
@@ -156,7 +162,7 @@ impl BinanceConfig {
             exchange_name = format!("{}-TESTNET", exchange_name);
         }
 
-        let db_path = db_full_path(&exchange_name, &self.trade_category, &self.trade_symbol);
+        let db_path = db_full_path(&exchange_name, &self.trade_category, &self.trade_symbol, &self.db_base_dir);
 
         return db_path.to_str().unwrap().to_string();
     }
diff --git a/src/exchange/binance/market.rs b/src/exchange/binance/market.rs
index 2bfe8dda..ca4e2966 100644
--- a/src/exchange/binance/market.rs
+++ b/src/exchange/binance/market.rs
@@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex};
 use std::thread::{sleep, JoinHandle, self};
 use std::time::Duration;
 
-use crate::common::{convert_pyresult_vec, MarketMessage, time_string, OrderType, OrderStatus, LogStatus, FLOOR_DAY};
+use crate::common::{convert_pyresult_vec, MarketMessage, time_string, OrderType, OrderStatus, LogStatus, FLOOR_DAY, flush_log};
 use crate::common::DAYS;
 use crate::common::{convert_pyresult, MarketStream};
 use crate::common::{to_naive_datetime, MicroSec};
@@ -232,6 +232,7 @@ impl BinanceMarket {
                 log::info!("downloaded: {}", download_rec);
                 if verbose {
                     println!("downloaded: {}", download_rec);
+                    flush_log();
                 }
                 Ok(download_rec)
             }
@@ -248,11 +249,12 @@ impl BinanceMarket {
         }
     }
 
-    #[pyo3(signature = (ndays, force = false, verbose=false))]
+    #[pyo3(signature = (ndays, *, force = false, verbose=true))]
     pub fn download(&mut self, ndays: i64, force: bool, verbose: bool) -> i64 {
         log::info!("log download: {} days", ndays);
         if verbose {
             println!("log download: {} days", ndays);
+            flush_log();
         }
 
         let latest_date;
@@ -267,6 +269,7 @@ impl BinanceMarket {
         log::info!("archive latest_date: {}", time_string(latest_date));
         if verbose {
             println!("archive latest_date: {}", time_string(latest_date));
+            flush_log();
         }
 
         let mut download_rec: i64 = 0;
@@ -279,6 +282,7 @@ impl BinanceMarket {
                 
                 if verbose {
                     println!("{} skip download", time_string(date));
+                    flush_log();
                 }
                 continue;
             }
@@ -303,13 +307,14 @@ impl BinanceMarket {
         download_rec
     }
 
-    #[pyo3(signature = (force=false, verbose = false))]
+    #[pyo3(signature = (force=false, verbose = true))]
     pub fn download_latest(&mut self, force:bool, verbose: bool) -> i64 {
         if verbose {
             println!("start download from rest API");
+            flush_log();
         }
 
-        let mut start_id: BinanceMessageId = 0;
+        let start_id: u64;
 
         if force {
             let (fix_id, _fix_time) = self.latest_fix_time();
@@ -320,6 +325,14 @@ impl BinanceMarket {
             start_id = stable_id + 1;
         }
 
+        if start_id == 1 {
+            println!("ERROR: no record in database");
+            println!(" Try to reboot your program and download(days=2, force=True).");            
+            flush_log();
+
+            return 0;
+        }
+        
         let ch = self.db.start_thread();
 
         let record_number = download_historical_trades_from_id(&BinanceConfig::BTCUSDT(), start_id, verbose,&mut |row|
@@ -330,7 +343,8 @@ impl BinanceMarket {
         }).unwrap();
 
         if verbose {
-            println!("REST downloaded: {}", record_number);
+            println!("\nREST downloaded: {}[rec]", record_number);
+            flush_log();
         }
 
         return record_number;
@@ -338,7 +352,7 @@ impl BinanceMarket {
 
     #[pyo3(signature = (verbose = false))]
     pub fn latest_stable_time(&mut self, verbose: bool) -> (BinanceMessageId, MicroSec) {
-        let sql = r#"select time_stamp, action, price, size, status, id from trades where $1 < time_stamp and status = "E" or status = "e" order by time_stamp desc"#;
+        let sql = r#"select time_stamp, action, price, size, status, id from trades where $1 < time_stamp and (status = "E" or status = "e") order by time_stamp desc"#;
 
         let r = self.db.connection.select_query(sql, vec![NOW()-DAYS(4)]);
 
@@ -1205,8 +1219,8 @@ let mut market = BinanceMarket::new(&BinanceConfig::BTCUSDT());
         let (stable_id, stable_time)= market.latest_stable_time(true);
         println!("STABLETIME: {:?}", time_string(stable_time));
 
-        let rec_no = download_historical_trades_from_id(&BinanceConfig::BTCUSDT(), stable_id, true,&mut |row|Ok({
-            // println!("{:?}", row);
+        let rec_no = download_historical_trades_from_id(&BinanceConfig::BTCUSDT(), stable_id, true,&mut |_row|Ok({
+            // println!("{:?}", _row);
         })).unwrap();
 
         println!("{}", rec_no);
diff --git a/src/exchange/binance/rest.rs b/src/exchange/binance/rest.rs
index 5a0df85f..6f4c9ac7 100644
--- a/src/exchange/binance/rest.rs
+++ b/src/exchange/binance/rest.rs
@@ -1,8 +1,5 @@
 // Copyright(c) 2022-2023. yasstake. All rights reserved.
 
-use std::io;
-use std::io::Write;
-use std::io::stdout;
 use std::thread::sleep;
 use std::time::Duration;
 
@@ -19,6 +16,7 @@ use crate::common::MicroSec;
 use crate::common::OrderSide;
 use crate::common::Trade;
 use crate::common::NOW;
+use crate::common::flush_log;
 use crate::common::time_string;
 use crate::exchange::rest_delete;
 use crate::exchange::rest_get;
@@ -174,8 +172,6 @@ where
     }
 }
 
-const PAGE_SIZE: u64 = 1000;
-const API_INTERVAL_LIMIT: i64 = 100 * 1000;
 /*
 pub fn insert_trade_db(
     config: &BinanceConfig,
@@ -198,11 +194,6 @@ pub fn download_historical_trades_from_id<F>(
 where
     F: FnMut(&Vec<Trade>) -> Result<(), String>,
 {
-    let s_id : BinanceMessageId = 0;
-    let s_time: MicroSec = 0;
-    let e_id: BinanceMessageId = 0;
-    let e_time: MicroSec = 0;    
-
     let duration = Duration::from_millis(100);
 
     let mut records: i64 = 0;
@@ -220,7 +211,7 @@ where
 
         if verbose {
             print!("Download Historical API {} - {}\r", time_string(s_time), time_string(e_time));
-            io::stdout().flush().unwrap();
+            flush_log();
         }
 
         sleep(duration);
@@ -748,7 +739,7 @@ mod tests {
     use rust_decimal::prelude::FromPrimitive;
 
     use super::*;
-    use crate::common::{init_debug_log, time_string, Order, HHMM};
+    use crate::common::{init_debug_log, time_string, Order};
 
     #[test]
     fn test_trade_list() {
diff --git a/src/exchange/orderbook.rs b/src/exchange/orderbook.rs
index 5432f939..bf4d781c 100644
--- a/src/exchange/orderbook.rs
+++ b/src/exchange/orderbook.rs
@@ -12,7 +12,7 @@ use pyo3::pyclass;
 use rust_decimal::prelude::*;
 use rust_decimal_macros::dec;
 use serde::{de, Deserialize, Deserializer};
-use serde_derive::{Deserialize, Serialize};
+use serde_derive::Serialize;
 
 use crate::common::MarketConfig;
 
diff --git a/src/exchange/rest.rs b/src/exchange/rest.rs
index 703e28be..baf9a130 100644
--- a/src/exchange/rest.rs
+++ b/src/exchange/rest.rs
@@ -7,7 +7,7 @@ use std::{
     path::Path,
 };
 
-use crate::common::{LogStatus, Trade};
+use crate::common::{LogStatus, Trade, flush_log};
 use crossbeam_channel::Sender;
 use csv::{self, StringRecord};
 use flate2::bufread::GzDecoder;
@@ -334,6 +334,7 @@ where
 {
     if verbose {
         print!("log download (url = {})", url);
+        flush_log();
     }
     let mut download_rec = 0;
 
@@ -394,7 +395,7 @@ where
     log::debug!("download rec = {}", download_rec);
     if verbose {
         println!(" download complete rec = {}", download_rec);
-        let _ = stdout().flush();
+        flush_log();
     }
 
     return Ok(download_rec);
diff --git a/src/fs/mod.rs b/src/fs/mod.rs
index ccd56e61..e37005fd 100644
--- a/src/fs/mod.rs
+++ b/src/fs/mod.rs
@@ -12,8 +12,14 @@ pub fn project_dir() -> PathBuf {
 }
 
 
-pub fn db_full_path(exchange_name: &str, category: &str, symbol: &str) -> PathBuf {
-    let project_dir = project_dir();
+pub fn db_full_path(exchange_name: &str, category: &str, symbol: &str, base_dir: &str) -> PathBuf {
+    let project_dir = if base_dir == "" {
+        project_dir()
+    }
+    else {
+        PathBuf::from(base_dir)
+    };
+
     let db_dir = project_dir.join("DB");
     let exchange_dir = db_dir.join(exchange_name);
     let _ = fs::create_dir_all(&exchange_dir);
@@ -39,8 +45,14 @@ mod test_fs {
 
     #[test]
     fn test_db_full_path() {
-        let db = db_full_path("FTX", "SPOT", "BTC-PERP");
+        let db = db_full_path("FTX", "SPOT", "BTC-PERP", "/tmp");
+        println!("{:?}", db);
 
+        let db = db_full_path("FTX", "SPOT", "BTC-PERP", "/tmp/");
         println!("{:?}", db);
+
+        let db = db_full_path("FTX", "SPOT", "BTC-PERP", "./");
+        println!("{:?}", db);
+
     }
 }
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 7da99127..063c3b17 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,7 +20,7 @@ use exchange::binance::{BinanceMarket, BinanceConfig};
 use exchange::bb::BBMarket;
 
 use common::*;
-use session::{Session, ExecuteMode};
+use session::{Session, ExecuteMode, Logger};
 use session::Runner;
 //use sim::back::BackTester;
 //use sim::session::DummySession;
@@ -50,6 +50,8 @@ fn rbot(_py: Python, m: &PyModule) -> PyResult<()> {
     m.add_class::<AccountStatus>()?;
     m.add_class::<MarketMessage>()?;
 
+    m.add_class::<Logger>()?;
+
     m.add_class::<Order>()?;
     m.add_class::<OrderSide>()?;
     m.add_class::<OrderType>()?;
diff --git a/src/session/logger.rs b/src/session/logger.rs
new file mode 100644
index 00000000..f26f3895
--- /dev/null
+++ b/src/session/logger.rs
@@ -0,0 +1,1116 @@
+use std::{
+    collections::HashMap,
+    fs::{File, OpenOptions},
+    io::{BufRead, BufReader, Write},
+};
+
+use polars_core::{datatypes::TimeUnit, frame::DataFrame, prelude::NamedFrom, series::Series, export::num::ToPrimitive};
+use polars_ops::frame::{DataFrameJoinOps, JoinArgs, JoinType};
+use pyo3::{pyclass, pymethods, PyResult};
+use pyo3_polars::PyDataFrame;
+use serde_derive::{Deserialize, Serialize};
+use serde_with::skip_serializing_none;
+
+use crate::common::{ordervec_to_dataframe, AccountStatus, MicroSec, Order};
+
+#[skip_serializing_none]
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
+pub struct Indicator {
+    #[serde(rename = "i")]
+    pub id: i64,
+    #[serde(rename = "k")]
+    pub name: String,
+    #[serde(rename = "ID")]
+    pub order_id: Option<String>,
+    #[serde(rename = "id")]
+    pub transaction_id: Option<String>,
+    #[serde(rename = "V")]
+    pub value: f64,
+    #[serde(rename = "v")]
+    pub value2: Option<f64>,
+}
+
+#[pyclass]
+#[skip_serializing_none]
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
+pub struct TimeIndicator {
+    #[serde(rename = "i")]
+    pub log_id: i64,
+    #[serde(rename = "t")]
+    pub timestamp: MicroSec,
+    #[serde(rename = "ID")]
+    pub order_id: Option<String>,
+    #[serde(rename = "id")]
+    pub transaction_id: Option<String>,
+    #[serde(rename = "V")]
+    pub value: f64,
+    #[serde(rename = "v")]
+    pub value2: Option<f64>
+}
+
+pub struct TimeIndicatorVec(Vec<TimeIndicator>);
+
+impl Default for TimeIndicatorVec {
+    fn default() -> Self {
+        TimeIndicatorVec(vec![])
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
+pub struct Position {
+    #[serde(rename = "s")]
+    pub size: f64,
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
+pub struct Profit {
+    pub log_id: i64,
+    pub open_position: f64,
+    pub close_position: f64,
+    pub position: f64,
+    pub profit: f64,
+    pub fee: f64,
+    pub total_profit: f64,
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
+pub enum LogMessage {
+    #[serde(rename = "O")]
+    Order(Order),
+    #[serde(rename = "A")]
+    Account(AccountStatus),
+    #[serde(rename = "i")]
+    UserIndicator(Indicator),
+    #[serde(rename = "I")]
+    SystemIndicator(Indicator),
+    #[serde(rename = "P")]
+    Profit(Profit)
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
+pub struct LogRecord {
+    #[serde(rename = "t")]
+    pub timestamp: MicroSec,
+    #[serde(rename = "d")]
+    pub data: Vec<LogMessage>,
+}
+
+impl LogRecord {
+    pub fn new(t: MicroSec) -> Self {
+        Self {
+            timestamp: t,
+            data: vec![],
+        }
+    }
+
+    pub fn to_string(&self) -> String {
+        let r = serde_json::to_string(self);
+
+        if r.is_err() {
+            return "".to_string();
+        }
+
+        r.unwrap()
+    }
+
+    pub fn from_string(s: &str) -> Result<LogRecord, serde_json::Error> {
+        serde_json::from_str(s)
+    }
+
+    pub fn append_message(&mut self, msg: &LogMessage) {
+        self.data.push(msg.clone());
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
+pub struct SingleLogRecord {
+    pub timestamp: MicroSec,
+    pub data: LogMessage,
+}
+
+impl SingleLogRecord {
+    pub fn new(timestamp: MicroSec, msg: &LogMessage) -> Self {
+        Self {
+            timestamp,
+            data: msg.clone(),
+        }
+    }
+}
+
+impl Into<Vec<SingleLogRecord>> for LogRecord {
+    fn into(self) -> Vec<SingleLogRecord> {
+        let mut result = vec![];
+
+        for msg in self.data {
+            result.push(SingleLogRecord {
+                timestamp: self.timestamp,
+                data: msg,
+            });
+        }
+
+        result
+    }
+}
+
+pub fn account_logrec_to_df(accounts: Vec<SingleLogRecord>) -> DataFrame {
+    let mut timestamp = Vec::<MicroSec>::new();
+
+    let mut home = Vec::<f64>::new();
+    let mut home_free = Vec::<f64>::new();
+    let mut home_locked = Vec::<f64>::new();
+
+    let mut foreign = Vec::<f64>::new();
+    let mut foreign_free = Vec::<f64>::new();
+    let mut foreign_locked = Vec::<f64>::new();
+
+    for rec in accounts {
+        match rec.data {
+            LogMessage::Account(account) => {
+                timestamp.push(rec.timestamp);                            
+                home.push(account.home.to_f64().unwrap());
+                home_free.push(account.home_free.to_f64().unwrap());
+                home_locked.push(account.home_locked.to_f64().unwrap());
+                foreign.push(account.foreign.to_f64().unwrap());
+                foreign_free.push(account.foreign_free.to_f64().unwrap());
+                foreign_locked.push(account.foreign_locked.to_f64().unwrap());
+            }
+            _ => {
+                panic!("not supported message type");
+            }
+        }
+    }
+
+    let timestamp = Series::new("timestamp", timestamp);
+
+    let home = Series::new("home", home);
+    let home_free = Series::new("home_free", home_free);
+    let home_locked = Series::new("home_locked", home_locked);
+
+    let foreign = Series::new("foreign", foreign);
+    let foreign_free = Series::new("foreign_free", foreign_free);
+    let foreign_locked = Series::new("foreign_locked", foreign_locked);
+
+    let mut df = DataFrame::new(vec![
+        timestamp,
+        home,
+        home_free,
+        home_locked,
+        foreign,
+        foreign_free,
+        foreign_locked,
+    ]).unwrap();
+
+    let time = df.column("timestamp").unwrap().i64().unwrap().clone();
+    let date_time = time.into_datetime(TimeUnit::Microseconds, None);
+    let df = df.with_column(date_time).unwrap();
+
+    return df.clone();
+}
+
+
+impl Into<LogRecord> for Vec<SingleLogRecord> {
+    fn into(self) -> LogRecord {
+        let mut result = LogRecord::new(0);
+        let timestamp: MicroSec = self[0].timestamp;
+
+        for msg in self {
+            if timestamp != msg.timestamp {
+                log::error!("timestamp is not same");
+            }
+            result.append_message(&msg.data);
+        }
+
+        result
+    }
+}
+
+#[pyclass]
+#[derive(Debug)]
+pub struct Logger {
+    on_memory: bool,
+    current_time: MicroSec,
+    order_serial: i64,
+    order: Vec<SingleLogRecord>,
+    user_indicator: HashMap<String, Vec<TimeIndicator>>,
+    system_indicator: HashMap<String, Vec<TimeIndicator>>,
+    account: Vec<SingleLogRecord>,
+    profit: Vec<SingleLogRecord>,
+    log_file: Option<File>,
+    log_buffer: Option<LogRecord>,
+}
+
+#[pymethods]
+impl Logger {
+    #[new]
+    pub fn new(on_memory: bool) -> Self {
+        Self {
+            on_memory,
+            order_serial: 0,
+            current_time: 0,
+            order: vec![],
+            user_indicator: HashMap::new(),
+            system_indicator: HashMap::new(),
+            account: vec![],
+            profit: vec![],
+            log_file: None,
+            log_buffer: None,
+        }
+    }
+
+    pub fn clear(&mut self) {
+        log::debug!("clear");
+        self.current_time = 0;
+        self.on_memory = true;
+        self.order.clear();
+        self.user_indicator.clear();
+        self.system_indicator.clear();
+        self.account.clear();
+    }
+
+    pub fn open_log(&mut self, path: &str) -> Result<(), std::io::Error> {
+        if self.log_file.is_some() {
+            log::debug!("close log file {:?}", self.log_file);
+            self.close_log()?;
+        }
+
+        let log_file = Logger::log_path(path);
+
+        self.log_file = Some(
+            OpenOptions::new()
+                .write(true)
+                .truncate(true)
+                .create(true)
+                .open(log_file)?,
+        );
+
+        log::debug!("open log file success. {:?}", self.log_file);
+
+        Ok(())
+    }
+
+    pub fn close_log(&mut self) -> Result<(), std::io::Error> {
+        self.flush_buffer()?;
+
+        if self.log_file.is_some() {
+            self.log_file.as_mut().unwrap().sync_all()?;
+            self.log_file = None;
+        }
+
+        Ok(())
+    }
+
+    pub fn dump(&mut self, path: &str) -> Result<(), std::io::Error> {
+        log::debug!("save({})", path);
+
+        self.open_log(path)?;
+
+        self.save_log_records(&self.order.clone())?;
+
+        self.save_indicator(&self.user_indicator.clone(), |i| {
+            LogMessage::UserIndicator(i)
+        })?;
+        self.save_indicator(&self.system_indicator.clone(), |i| {
+            LogMessage::SystemIndicator(i)
+        })?;
+
+        // save account status
+        self.save_log_records(&self.account.clone())?;
+
+        self.save_log_records(&self.profit.clone())?;
+
+        self.flush_buffer()?;
+
+        Ok(())
+    }
+
+    pub fn restore(&mut self, file_name: String) -> Result<(), std::io::Error> {
+        self.clear();
+        let file_name = Logger::log_path(&file_name);
+
+        let file = File::open(file_name)?;
+        let mut reader = BufReader::new(file);
+
+        loop {
+            let mut buf = String::new();
+            let count = reader.read_line(&mut buf)?;
+            if count == 0 {
+                break;
+            }
+            let log_record = LogRecord::from_string(&buf)?;
+
+            log::debug!("LINE: {:?}", log_record);
+
+            for msg in log_record.data {
+                log::debug!("restore: {:?}", msg);
+                self.store_memory(log_record.timestamp, &msg)?;
+            }
+        }
+
+        Ok(())
+    }
+
+    pub fn log_order(&mut self, timestamp: MicroSec, order: &Order) -> Result<(), std::io::Error> {
+        self.log_message(timestamp, &LogMessage::Order(order.clone()))
+    }
+
+
+    pub fn log_account(
+        &mut self,
+        timestamp: MicroSec,
+        status: &AccountStatus,
+    ) -> Result<(), std::io::Error> {
+        self.log_message(timestamp, &LogMessage::Account(status.clone()))
+    }
+
+    pub fn log_position(
+        &mut self,
+        timestamp: MicroSec,
+        log_id: i64,
+        position_change: f64,                
+        position: f64,
+        order_id: String,        
+        transaction_id: String,
+    ) -> Result<(), std::io::Error> {
+        self.log_system_indicator(timestamp, "position", position_change, Some(position), Some(order_id), Some(transaction_id), Some(log_id))             
+    }
+
+    pub fn log_profit(
+        &mut self,
+        timestamp: MicroSec,
+        log_id: i64,
+        open_position: f64,
+        close_position: f64,
+        position: f64,
+        profit: f64,
+        fee: f64,
+        profit_sum: f64,
+    ) -> Result<(), std::io::Error> {
+        let profit = Profit{
+            log_id: log_id,
+            open_position: open_position,
+            close_position: close_position,
+            position: position,
+            profit: profit,
+            fee: fee,
+            total_profit: profit_sum,
+        };
+        
+        self.log_message(timestamp, &LogMessage::Profit(profit))
+    }
+
+
+
+
+    #[getter]
+    pub fn get_orders(&self) -> PyResult<PyDataFrame> {
+        let orders = self
+            .order
+            .iter()
+            .map(|x| match &x.data {
+                LogMessage::Order(order) => order.clone(),
+                _ => {
+                    panic!("not supported message type");
+                }
+            })
+            .collect();
+
+        let orders = ordervec_to_dataframe(orders);
+        let profit = Self::profit_to_df(self.profit.clone());
+
+        let all = orders.join(&profit, ["log_id"], ["log_id"], JoinArgs::new(JoinType::Left));
+
+        Ok(PyDataFrame(all.unwrap()))
+    }
+
+    /*
+    #[getter]
+    pub fn get_position(&self) -> PyResult<PyDataFrame> {
+        let df = Self::indicator_to_df(self.system_indicator.get("position"), "position_change", Some("position"), true, true);
+
+        Ok(PyDataFrame(df))
+    }
+    */
+
+    #[getter]
+    pub fn get_account(&self) -> PyResult<PyDataFrame> {
+        let df = account_logrec_to_df(self.account.clone());
+
+        Ok(PyDataFrame(df))
+    }
+
+    #[getter]
+    pub fn get_profit(&self) -> PyResult<PyDataFrame> {
+        let df = Self::profit_to_df(self.profit.clone());
+
+        Ok(PyDataFrame(df))
+    }
+
+    pub fn __getitem__(&self, key: &str) -> PyResult<PyDataFrame> {
+        let df = Self::indicator_to_df(self.user_indicator.get(key), key, None, false, false);
+
+        Ok(PyDataFrame(df))
+    }
+}
+
+impl Logger {
+    pub fn profit_to_df(records: Vec<SingleLogRecord>) -> DataFrame {
+        let mut log_id = Vec::<i64>::new();
+        //let mut timestamp: Vec<MicroSec> = vec![];
+        let mut open_position: Vec<f64> = vec![];
+        let mut close_position: Vec<f64> = vec![];
+        let mut position: Vec<f64> = vec![];
+        let mut profit: Vec<f64> = vec![];
+        let mut fee: Vec<f64> = vec![];
+        let mut total_profit: Vec<f64> = vec![];
+        let mut sum_profit_acc: f64 = 0.0;
+        let mut sum_profit: Vec<f64> = vec![];
+
+        for rec in records {
+            match rec.data {
+                LogMessage::Profit(p) => {
+                    log_id.push(p.log_id);
+        //            timestamp.push(rec.timestamp);
+                    open_position.push(p.open_position);
+                    close_position.push(p.close_position);
+                    position.push(p.position);
+                    profit.push(p.profit);
+                    fee.push(p.fee);
+                    total_profit.push(p.total_profit);
+                    sum_profit_acc += p.total_profit;
+                    sum_profit.push(sum_profit_acc);
+                }
+                _ => {
+                    panic!("not supported message type");
+                }
+            }
+        }
+
+        let log_id  = Series::new("log_id", log_id);
+        //let timestamp_series = Series::new("timestamp", timestamp);
+        let open_position_series = Series::new("open_position", open_position);
+        let close_position_series = Series::new("close_position", close_position);
+        let position_series = Series::new("position", position);
+        let profit_series = Series::new("profit", profit);
+        let fee_series = Series::new("fee", fee);
+        let total_profit_series = Series::new("total_profit", total_profit);
+        let sum_profit_series = Series::new("sum_profit", sum_profit);
+
+
+        let df = DataFrame::new(vec![
+            log_id,
+        //    timestamp_series,
+            open_position_series,
+            close_position_series,
+            position_series,
+            profit_series,
+            fee_series,
+            total_profit_series,
+            sum_profit_series,
+        ]).unwrap();
+
+        //let time = df.column("timestamp").unwrap().i64().unwrap().clone();
+        //let date_time = time.into_datetime(TimeUnit::Microseconds, None);
+        //let df = df.with_column(date_time).unwrap();
+
+        df.clone()
+    }
+
+    pub fn indicator_to_df(indicator: Option<&Vec<TimeIndicator>>, value_name: &str, value_name2: Option<&str>, has_transaction_id: bool, has_log_id: bool) -> DataFrame {
+        let mut timestamp: Vec<MicroSec> = vec![];
+        let mut value: Vec<f64> = vec![];
+        let mut value2: Vec<f64> = vec![];
+        let mut order_id: Vec<String> = vec![];        
+        let mut transaction_id: Vec<String> = vec![];        
+        let mut log_id:Vec<i64> = vec![];
+
+        if indicator.is_some() {
+            let indicator = indicator.unwrap();
+            for i in indicator {
+                timestamp.push(i.timestamp);
+                value.push(i.value);
+
+                if i.value2.is_some() {
+                    value2.push(i.value2.unwrap());
+                }
+                else {
+                    value2.push(0.0);
+                }
+
+                if has_transaction_id {
+                    if i.order_id.is_some() {
+                        order_id.push(i.order_id.clone().unwrap());
+                    }
+                    else {
+                        order_id.push("".to_string());
+                    }
+
+                    if i.transaction_id.is_some() {
+                        transaction_id.push(i.transaction_id.clone().unwrap());
+                    }
+                    else {
+                        transaction_id.push("".to_string());
+                    }
+                }
+
+                log_id.push(i.log_id);
+            }
+        }
+
+        let timestamp_series = Series::new("timestamp", timestamp);
+        let value_series = Series::new(value_name, value);
+
+        let mut column = vec![timestamp_series, value_series];
+
+        if value_name2.is_some() {
+            let value_series2 = Series::new(value_name2.unwrap_or(""), value2);
+            column.push(value_series2);
+        }
+
+        if has_transaction_id {
+            let order_id_series = Series::new("order_id", order_id);
+            let transaction_id_series = Series::new("transaction_id", transaction_id);
+
+            column.push(order_id_series);
+            column.push(transaction_id_series);
+        }
+
+        if has_log_id {
+            let log_id_series = Series::new("log_id", log_id);
+            column.push(log_id_series);
+        }
+
+        let mut df = DataFrame::new(column).unwrap();
+
+        let time = df.column("timestamp").unwrap().i64().unwrap().clone();
+        let date_time = time.into_datetime(TimeUnit::Microseconds, None);
+        let df = df.with_column(date_time).unwrap();
+
+        df.clone()
+    }
+
+    pub fn log_indicator(
+        &mut self,
+        timestamp: MicroSec,
+        key: &str,
+        value: f64,        
+        value2: Option<f64>,
+        order_id: Option<String>,
+        transaction_id: Option<String>,
+        id: Option<i64>
+    ) -> Result<(), std::io::Error> {
+        let indicator = Indicator {
+            name: key.to_string(),
+            id: id.unwrap_or_default(),
+            order_id: order_id,
+            transaction_id: transaction_id,
+            value: value,
+            value2: value2,
+        };
+        self.log_message(timestamp, &LogMessage::UserIndicator(indicator))
+    }
+
+    pub fn log_system_indicator(
+        &mut self,
+        timestamp: MicroSec,
+        key: &str,
+        value: f64,        
+        value2: Option<f64>,
+        order_id: Option<String>,
+        transaction_id: Option<String>,
+        id: Option<i64>
+    ) -> Result<(), std::io::Error> {
+        let indicator = Indicator {
+            name: key.to_string(),
+            id: id.unwrap_or_default(),            
+            order_id: order_id,
+            transaction_id: transaction_id,
+            value: value,
+            value2: value2,
+        };
+        self.log_message(timestamp, &LogMessage::SystemIndicator(indicator))
+    }
+
+    pub fn save_indicator<F>(
+        &mut self,
+        indicator: &HashMap<String, Vec<TimeIndicator>>,
+        f: F,
+    ) -> Result<(), std::io::Error>
+    where
+        F: Fn(Indicator) -> LogMessage,
+    {
+        // Save indicator
+        for (key, time_indicator) in indicator {
+            log::debug!("save indicator KEY= {:?}", key);
+            for i in time_indicator.iter() {
+                log::debug!("save indicator value= {:?}", i);
+
+                let indicator = Indicator {
+                    name: key.to_string(),
+                    id: i.log_id,
+                    value: i.value,
+                    order_id: i.order_id.clone(),
+                    transaction_id: i.transaction_id.clone(),
+                    value2: i.value2,
+                };
+
+                self.write_file(i.timestamp, &f(indicator))?;
+            }
+        }
+
+        Ok(())
+    }
+
+    pub fn clone(&self) -> Self {
+        Self {
+            on_memory: true,
+            current_time: self.current_time,
+            order_serial: self.order_serial,
+            order: self.order.clone(),
+            user_indicator: self.user_indicator.clone(),
+            system_indicator: self.system_indicator.clone(),
+            account: self.account.clone(),
+            profit: self.profit.clone(),
+            log_file: None,
+            log_buffer: None,
+        }
+    }
+
+    // TODO: implement
+    pub fn log_message(
+        &mut self,
+        timestamp: MicroSec,
+        msg: &LogMessage,
+    ) -> Result<(), std::io::Error> {
+        if self.on_memory {
+            self.store_memory(timestamp, msg)?;
+        }
+
+        if self.log_file.is_some() {
+            self.write_file(timestamp, msg)?;
+        }
+
+        Ok(())
+    }
+
+    pub fn store_memory(
+        &mut self,
+        timestamp: MicroSec,
+        msg: &LogMessage,
+    ) -> Result<(), std::io::Error> {
+        let log_record = SingleLogRecord::new(timestamp, msg);
+
+        match log_record.data {
+            LogMessage::Order(_) => {
+                self.order.push(log_record);
+            }
+            LogMessage::UserIndicator(indicator) => {
+                log::debug!("store user indicator: {:?}", indicator);
+                let time_indicator = TimeIndicator {
+                    timestamp: timestamp,
+                    log_id: indicator.id,
+                    order_id: indicator.order_id,                    
+                    transaction_id: indicator.transaction_id,
+                    value: indicator.value,
+                    value2: indicator.value2,
+                };
+
+                if self.user_indicator.contains_key(&indicator.name) {
+                    let indicator = self.user_indicator.get_mut(&indicator.name).unwrap();
+                    indicator.push(time_indicator);
+                } else {
+                    let indicator_vec = vec![time_indicator];
+                    self.user_indicator
+                        .insert(indicator.name.clone(), indicator_vec);
+                }
+            }
+            LogMessage::SystemIndicator(indicator) => {
+                log::debug!("store SYSTEM indicator: {:?}", indicator);
+                let time_indicator = TimeIndicator {
+                    timestamp: timestamp,
+                    log_id: indicator.id,
+                    order_id: indicator.order_id,
+                    transaction_id: indicator.transaction_id,
+                    value: indicator.value,
+                    value2: indicator.value2,                    
+                };
+
+                if self.system_indicator.contains_key(&indicator.name) {
+                    let indicator = self.system_indicator.get_mut(&indicator.name).unwrap();
+                    indicator.push(time_indicator);
+                } else {
+                    let indicator_vec = vec![time_indicator];
+                    self.system_indicator
+                        .insert(indicator.name.clone(), indicator_vec);
+                }
+            }
+            LogMessage::Account(_) => {
+                self.account.push(log_record);
+            }
+            LogMessage::Profit(_) => {
+                self.profit.push(log_record);
+            }
+            /*
+              _ => {
+                  log::error!("not supported message type");
+              }
+              */
+        }
+
+        Ok(())
+    }
+
+    pub fn write_file(
+        &mut self,
+        timestamp: MicroSec,
+        msg: &LogMessage,
+    ) -> Result<(), std::io::Error> {
+        if timestamp != self.current_time {
+            self.flush_buffer()?;
+        }
+
+        if self.log_buffer.is_none() {
+            self.log_buffer = Some(LogRecord::new(timestamp));
+            self.current_time = timestamp;
+        }
+
+        let log_buffer = self.log_buffer.as_mut().unwrap();
+        log_buffer.append_message(msg);
+
+        Ok(())
+    }
+
+    pub fn flush_buffer(&mut self) -> Result<(), std::io::Error> {
+        if self.log_buffer.is_none() {
+            return Ok(());
+        }
+
+        // write to file
+        if self.log_file.is_some() {
+            let log_file = self.log_file.as_mut().unwrap();
+            let json = self.log_buffer.as_ref().unwrap().to_string();
+            log_file.write_all(json.as_bytes())?;
+            log_file.write_all("\n".as_bytes())?;
+        }
+
+        self.log_buffer = None;
+
+        Ok(())
+    }
+
+    pub fn save_log_records(
+        &mut self,
+        records: &Vec<SingleLogRecord>,
+    ) -> Result<(), std::io::Error> {
+        println!("save log records");
+        for rec in records.iter() {
+            println!("save log record: {:?}", rec);
+            self.write_file(rec.timestamp, &rec.data)?;
+        }
+
+        Ok(())
+    }
+
+    fn log_path(file_name: &str) -> String {
+        let file_name = if file_name.ends_with(".log") {
+            file_name.to_string()
+        } else {
+            file_name.to_string() + ".log"
+        };
+
+        file_name
+    }
+}
+
+impl Drop for Logger {
+    fn drop(&mut self) {
+        let _ = self.close_log();
+    }
+}
+
+#[cfg(test)]
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::common::Order;
+    use crate::common::OrderSide;
+    use crate::common::OrderStatus;
+    use crate::common::OrderType;
+    use rust_decimal_macros::dec;
+
+    #[test]
+    fn test_log_record_order() {
+        let order = Order::new(
+            "BTCUSD".to_string(),
+            1,
+            "order-1".to_string(),
+            "clientid".to_string(),
+            OrderSide::Buy,
+            OrderType::Limit,
+            OrderStatus::New,
+            dec![10.0],
+            dec![10.0],
+        );
+
+        let mut log_record = LogRecord::new(1);
+
+        log_record.append_message(&LogMessage::Order(order.clone()));
+
+        let indicator = Indicator {
+            name: "test".to_string(),
+            id: 1,
+            order_id: Some("order-1".to_string()),
+            transaction_id: Some("transaction-1".to_string()),
+            value: 1.0,
+            value2: None,
+        };
+        log_record.append_message(&LogMessage::UserIndicator(indicator));
+
+        let indicator = Indicator {
+            name: "test2".to_string(),
+            id: 2,
+            order_id: Some("order-1".to_string()),
+            transaction_id: Some("transaction-1".to_string()),
+            value: 2.0,
+            value2: None,
+        };
+        log_record.append_message(&LogMessage::UserIndicator(indicator));
+
+        println!("{:?}", log_record);
+        println!("{}", log_record.to_string());
+
+        let json = log_record.to_string();
+
+        let log_record2 = LogRecord::from_string(&json).unwrap();
+
+        assert_eq!(log_record2, log_record);
+
+        let extract: Vec<SingleLogRecord> = log_record2.into();
+
+        println!("{:?}", extract);
+
+        let convert: LogRecord = extract.into();
+        println!("{:?}", convert);
+    }
+
+    /*
+    #[test]
+    fn test_logger() {
+        init_debug_log();
+        let mut logger = Logger::new(true);
+
+        logger.open_log("/tmp/test").unwrap();
+
+        let order = Order::new(
+            "BTCUSD".to_string(),
+            1,
+            "order-1".to_string(),
+            "clientid".to_string(),
+            OrderSide::Buy,
+            OrderType::Limit,
+            OrderStatus::New,
+            dec![10.0],
+            dec![10.0],
+        );
+
+        logger.log_order(1, &order).unwrap();
+
+        let order = Order::new(
+            "BTCUSD".to_string(),
+            2,
+            "order-2".to_string(),
+            "clientid".to_string(),
+            OrderSide::Buy,
+            OrderType::Limit,
+            OrderStatus::New,
+            dec![10.0],
+            dec![10.0],
+        );
+
+        logger.log_order(2, &order).unwrap();
+
+        let order = Order::new(
+            "BTCUSD".to_string(),
+            1,
+            "order-3".to_string(),
+            "clientid".to_string(),
+            OrderSide::Buy,
+            OrderType::Limit,
+            OrderStatus::New,
+            dec![10.0],
+            dec![10.0],
+        );
+
+        logger.log_order(3, &order).unwrap();
+
+        let order = Order::new(
+            "BTCUSD".to_string(),
+            1,
+            "order-4".to_string(),
+            "clientid".to_string(),
+            OrderSide::Buy,
+            OrderType::Limit,
+            OrderStatus::New,
+            dec![10.0],
+            dec![10.0],
+        );
+
+        logger.log_order(4, &order).unwrap();
+
+        let order = Order::new(
+            "BTCUSD".to_string(),
+            1,
+            "order-5".to_string(),
+            "clientid".to_string(),
+            OrderSide::Buy,
+            OrderType::Limit,
+            OrderStatus::New,
+            dec![10.0],
+            dec![10.0],
+        );
+
+        logger.log_order(5, &order).unwrap();
+        logger.log_indicator(5, "test-key0", Some("order01".to_string()),
+            Some("tr01".to_string()),  1.0, Some(1.0)).unwrap();
+
+        let order = Order::new(
+            "BTCUSD".to_string(),
+            1,
+            "order-6".to_string(),
+            "clientid".to_string(),
+            OrderSide::Buy,
+            OrderType::Limit,
+            OrderStatus::New,
+            dec![10.0],
+            dec![10.0],
+        );
+        logger.log_order(6, &order).unwrap();
+
+        logger.log_indicator(6, "test-key", 1.0).unwrap();
+        logger.log_indicator(6, "test-key2", 1.0).unwrap();
+        logger.log_indicator(7, "test-key2", 1.1).unwrap();
+        logger.log_indicator(7, "test-key3", 1.1).unwrap();
+        logger.log_indicator(8, "test-key-SUPER", 1.1).unwrap();
+
+        logger.dump("/tmp/dump").unwrap();
+        let mut l = Logger::new(true);
+        l.restore("/tmp/dump".to_string()).unwrap();
+
+        assert_eq!(logger.order, l.order);
+        log::debug!(
+            "indicator: {:?} / {:?}",
+            logger.user_indicator.len(),
+            l.user_indicator.len()
+        );
+        log::debug!(
+            "indicator: {:?} / {:?}",
+            logger.user_indicator.keys(),
+            l.user_indicator.keys()
+        );
+        assert!(logger.user_indicator.len() == l.user_indicator.len());
+    }
+
+    #[test]
+    fn test_dump_restore() {
+        init_debug_log();
+        let mut logger = Logger::new(true);
+
+        logger.open_log("/tmp/test").unwrap();
+
+        logger.log_indicator(1, "test-key", 1.0).unwrap();
+
+        let order = Order::new(
+            "BTCUSD".to_string(),
+            1,
+            "order-6".to_string(),
+            "clientid".to_string(),
+            OrderSide::Buy,
+            OrderType::Limit,
+            OrderStatus::New,
+            dec![10.0],
+            dec![10.0],
+        );
+
+        logger.log_order(1, &order).unwrap();
+
+        logger.log_indicator(2, "test-key2", 1.0).unwrap();
+        logger.log_indicator(2, "test-key3", 1.0).unwrap();
+        logger.flush_buffer().unwrap();
+
+        let mut logger2 = Logger::new(true);
+        logger2.restore("/tmp/test".to_string()).unwrap();
+
+        println!(
+            "{:?} / {:?}",
+            logger.user_indicator.keys(),
+            logger2.user_indicator.keys()
+        );
+        assert!(logger.user_indicator.len() == logger2.user_indicator.len());
+
+        logger.dump("/tmp/dump").unwrap();
+        let mut logger3 = Logger::new(true);
+        logger3.restore("/tmp/dump".to_string()).unwrap();
+
+        println!(
+            "{:?} / {:?}",
+            logger.user_indicator.keys(),
+            logger3.user_indicator.keys()
+        );
+        assert!(logger.user_indicator.len() == logger3.user_indicator.len());
+    }
+    */
+    /*
+        #[test]
+        fn test_logger_new() {
+            let logger = Logger::new(true);
+            assert_eq!(logger.on_memory, true);
+            assert_eq!(logger.memory, vec![]);
+            assert_eq!(logger.log_file_name, "");
+            assert_eq!(logger.log_file, None);
+        }
+
+        #[test]
+        fn test_logger_open_log() {
+            let mut logger = Logger::new(true);
+            let path = "/path/to/log";
+            assert!(logger.open_log(path).is_ok());
+            assert_eq!(logger.log_file_name, path);
+            assert!(logger.log_file.is_some());
+        }
+
+        #[test]
+        fn test_logger_log_on_memory() {
+            let mut logger = Logger::new(true);
+            let order = Order {
+                // Initialize order fields here
+            };
+            assert!(logger.log(&order).is_ok());
+            assert_eq!(logger.memory, vec![order]);
+        }
+
+        #[test]
+        fn test_logger_log_to_file() {
+            let mut logger = Logger::new(false);
+            let order = Order {
+                // Initialize order fields here
+            };
+            let mut file = File::create("/path/to/log.log").unwrap();
+            logger.log_file = Some(file);
+            assert!(logger.log(&order).is_ok());
+            // Assert that the order is written to the log file
+        }
+
+        #[test]
+        fn test_logger_get() {
+            let logger = Logger::new(true);
+            let order = Order {
+                // Initialize order fields here
+            };
+            assert_eq!(logger.get(), vec![]);
+            logger.memory.push(order.clone());
+            assert_eq!(logger.get(), vec![order]);
+        }
+    */
+}
+
+#[cfg(test)]
+mod test_logger {}
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 82859aea..6d5d783e 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -2,6 +2,7 @@ mod orderlist;
 mod session;
 mod runner;
 mod python_if;
+mod logger;
 
 
 #[cfg(test)]
@@ -11,3 +12,5 @@ pub use orderlist::*;
 pub use session::*;
 pub use runner::*;
 pub use python_if::*;
+pub use logger::*;
+
diff --git a/src/session/orderlist.rs b/src/session/orderlist.rs
index 1d4305db..fafef35d 100644
--- a/src/session/orderlist.rs
+++ b/src/session/orderlist.rs
@@ -56,6 +56,19 @@ impl OrderList {
         }
     }
 
+    pub fn get_old_orders(&self, expire_sec: i64) -> Vec<Order> {
+        let now = crate::common::NOW();
+        let mut old_orders: Vec<Order> = Vec::new();
+
+        for order in self.list.iter() {
+            if order.create_time + expire_sec * crate::common::MICRO_SECOND < now {
+                old_orders.push(order.clone());
+            }
+        }
+
+        old_orders
+    }
+
     /// Clears the list of orders.
     pub fn clear(&mut self) {
         self.list.clear();
@@ -188,7 +201,7 @@ impl OrderList {
                 self.list[0].status = OrderStatus::PartiallyFilled;
                 self.list[0].execute_size = remain_size;
                 self.list[0].remain_size -= remain_size;
-                self.list[0].execute_price = trade.price;
+                self.list[0].execute_price = self.list[0].order_price;
                 self.list[0].quote_vol = self.list[0].execute_price * self.list[0].execute_size;
 
                 filled_orders.push(self.list[0].clone());
@@ -201,7 +214,7 @@ impl OrderList {
                 self.list[0].status = OrderStatus::Filled;
                 self.list[0].execute_size = self.list[0].remain_size;
                 self.list[0].remain_size = 0.into();                
-                self.list[0].execute_price = trade.price;
+                self.list[0].execute_price = self.list[0].order_price;
                 self.list[0].quote_vol = self.list[0].execute_price * self.list[0].execute_size;                
 
                 remain_size -= self.list[0].remain_size;
diff --git a/src/session/runner.rs b/src/session/runner.rs
index f11922f2..8d6f722c 100644
--- a/src/session/runner.rs
+++ b/src/session/runner.rs
@@ -6,8 +6,8 @@ use pyo3::{pyclass, pymethods, Py, PyAny, PyErr, PyObject, Python};
 use rust_decimal::prelude::ToPrimitive;
 
 use crate::common::{
-    time_string, AccountStatus, MarketMessage, MarketStream, MicroSec, Order, Trade, FLOOR_SEC,
-    NOW, SEC,
+    flush_log, time_string, AccountStatus, MarketMessage, MarketStream, MicroSec, Order, Trade,
+    FLOOR_SEC, NOW, SEC,
 };
 use crossbeam_channel::Receiver;
 
@@ -16,12 +16,14 @@ use super::{has_method, ExecuteMode, Session};
 #[pyclass]
 #[derive(Debug, Clone)]
 pub struct Runner {
+    has_on_init: bool,
     has_on_clock: bool,
     has_on_tick: bool,
     has_on_update: bool,
+
     has_account_update: bool,
     start_timestamp: i64,
-    loop_duration: i64,
+    execute_time: i64,
     verbose: bool,
 
     print_interval: MicroSec,
@@ -45,12 +47,13 @@ impl Runner {
     #[new]
     pub fn new() -> Self {
         Self {
+            has_on_init: false,
             has_on_tick: false,
             has_on_clock: false,
             has_on_update: false,
             has_account_update: false,
             start_timestamp: 0,
-            loop_duration: -1, // -1 means infinite loop
+            execute_time: -1, // -1 means infinite loop
             print_interval: SEC(5),
             execute_mode: ExecuteMode::BackTest,
             current_timestamp: 0,
@@ -84,12 +87,17 @@ impl Runner {
     }
 
     pub fn update_agent_info(&mut self, agent: &PyAny) {
+        self.has_on_init = has_method(agent, "on_init");
         self.has_on_clock = has_method(agent, "on_clock");
         self.has_on_tick = has_method(agent, "on_tick");
         self.has_on_update = has_method(agent, "on_update");
         self.has_account_update = has_method(agent, "on_account_update");
 
         if self.verbose {
+            println!(
+                "has_on_init:        {}",
+                if self.has_on_init { "YES" } else { " no  " }
+            );
             println!(
                 "has_on_clock:       {}",
                 if self.has_on_clock { "YES" } else { " no  " }
@@ -110,42 +118,39 @@ impl Runner {
                     " no  "
                 }
             );
+            flush_log();
         }
     }
 
-    #[pyo3(signature = (market, agent, interval_sec, start_time=0, end_time=0, loop_duration=0, verbose=false))]
+    #[pyo3(signature = (market, agent, *, start_time=0, end_time=0, execute_time=0, verbose=false))]
     pub fn back_test(
         &mut self,
         market: PyObject,
         agent: &PyAny,
-        interval_sec: i64,
         start_time: MicroSec,
         end_time: MicroSec,
-        loop_duration: i64,
+        execute_time: i64,
         verbose: bool,
     ) -> Result<Py<Session>, PyErr> {
-        self.update_agent_info(agent);
         let stream = Self::open_backtest_stream(&market, start_time, end_time);
         let reciever = stream.reciver;
-        self.loop_duration = loop_duration;
+        self.execute_time = execute_time;
         self.print_interval = SEC(60 * 60);
         self.verbose = verbose;
         self.execute_mode = ExecuteMode::BackTest;
 
-        self.run(market, &reciever, agent, interval_sec, true)
+        self.run(market, &reciever, agent, true)
     }
 
-    #[pyo3(signature = (market, agent, interval_sec, log_memory=false, loop_duration=0, verbose=false))]
+    #[pyo3(signature = (market, agent, *, log_memory=false, execute_time=0, verbose=false))]
     pub fn dry_run(
         &mut self,
         market: PyObject,
         agent: &PyAny,
-        interval_sec: i64,
         log_memory: bool,
-        loop_duration: i64,
+        execute_time: i64,
         verbose: bool,
     ) -> Result<Py<Session>, PyErr> {
-        self.update_agent_info(agent);
         Python::with_gil(|py| {
             market.call_method0(py, "start_market_stream").unwrap();
         });
@@ -153,36 +158,31 @@ impl Runner {
         let stream = Self::get_market_stream(&market);
         let reciever = stream.reciver;
 
-        self.loop_duration = loop_duration;
+        self.execute_time = execute_time;
         self.verbose = verbose;
         self.execute_mode = ExecuteMode::Dry;
 
-        self.run(market, &reciever, agent, interval_sec, log_memory)
+        self.run(market, &reciever, agent, log_memory)
     }
 
-    #[pyo3(signature = (market, agent, interval_sec, log_memory=false, loop_duration=0, verbose=false))]
+    #[pyo3(signature = (market, agent, *,log_memory=false, execute_time=0, verbose=false))]
     pub fn real_run(
         &mut self,
         market: PyObject,
         agent: &PyAny,
-        interval_sec: i64,
         log_memory: bool,
-        loop_duration: i64,
+        execute_time: i64,
         verbose: bool,
     ) -> Result<Py<Session>, PyErr> {
-        self.update_agent_info(agent);
         let stream = Self::get_market_stream(&market);
         let reciever = stream.reciver;
 
-        Python::with_gil(|py| {
-            market.call_method0(py, "start_market_stream").unwrap();
-            market.call_method0(py, "start_user_stream").unwrap();
-        });
-        self.loop_duration = loop_duration;
+
+        self.execute_time = execute_time;
         self.verbose = verbose;
         self.execute_mode = ExecuteMode::Real;
 
-        self.run(market, &reciever, agent, interval_sec, log_memory)
+        self.run(market, &reciever, agent, log_memory)
     }
 }
 
@@ -194,9 +194,44 @@ impl Runner {
         market: PyObject,
         receiver: &Receiver<MarketMessage>,
         agent: &PyAny,
-        interval_sec: i64,
         log_memory: bool,
     ) -> Result<Py<Session>, PyErr> {
+        self.update_agent_info(agent);
+
+        // prepare market data
+        // 1. start market & user stream
+        // 2. download market data
+        let r = Python::with_gil(|py| {
+            if self.execute_mode == ExecuteMode::Real || self.execute_mode == ExecuteMode::Dry {    
+                let r = market.call_method0(py, "start_market_stream");
+                if r.is_err() {
+                    return Err(r.unwrap_err());
+                }
+            }
+
+            if self.execute_mode == ExecuteMode::Real {
+                let r = market.call_method0(py, "start_user_stream");
+                if r.is_err() {
+                    return Err(r.unwrap_err());
+                }
+            }
+
+            if self.execute_mode == ExecuteMode::Real || self.execute_mode == ExecuteMode::Dry {    
+                let r = market.call_method1(py, "download", (1,));
+                if r.is_err() {
+                    return Err(r.unwrap_err());
+                }
+            }
+
+            Ok(())
+        });
+
+        if r.is_err() {
+            return Err(r.unwrap_err());
+        }
+
+
+
         let result = Python::with_gil(|py| {
             if self.verbose {
                 println!("--- start run ---{:?} mode ---", self.execute_mode);
@@ -213,9 +248,18 @@ impl Runner {
             let mut warm_up_loop: i64 = WARMUP_STEPS;
 
             self.reset_count();
-
             let loop_start_time = NOW();
 
+            if self.has_on_init {
+                self.call_agent_on_init(&py, agent, &py_session)?;
+            }
+
+            let interval_sec = self.get_clock_interval(&py, &py_session)?;
+
+            if self.has_on_clock && interval_sec == 0 {
+                print!("*** WARNING: on_clock is set but clock_interval is 0. (on_clock will not be called ***");
+            }
+
             loop {
                 let message = receiver.recv();
 
@@ -230,7 +274,7 @@ impl Runner {
                     // in warm up loop, we don't call agent, just update session
                     let mut session = py_session.borrow_mut(py);
                     session.on_message(&message);
-                    self.current_timestamp = session.get_current_timestamp();
+                    self.current_timestamp = session.get_timestamp();
 
                     if message.trade.is_some() {
                         warm_up_loop = warm_up_loop - 1;
@@ -240,6 +284,8 @@ impl Runner {
                         print!("\rwarm up loop: {}", warm_up_loop)
                     }
 
+                    drop(session);
+
                     continue;
                 }
 
@@ -257,8 +303,8 @@ impl Runner {
                     if self.verbose {
                         println!("start_timestamp: {}", time_string(self.start_timestamp));
                     }
-                } else if 0 < self.loop_duration
-                    && SEC(self.loop_duration) < self.current_timestamp - self.start_timestamp
+                } else if 0 < self.execute_time
+                    && SEC(self.execute_time) < self.current_timestamp - self.start_timestamp
                 {
                     break;
                 }
@@ -288,7 +334,13 @@ impl Runner {
                 let elapsed = (NOW() - loop_start_time) / 1_000_000;
                 println!("Elapsed time: {} sec", elapsed);
                 println!("Loop count: {}", self.loop_count);
-                println!("Loop per sec: {}", self.loop_count / elapsed);
+                if 0 < elapsed {
+                    println!("Loop per sec: {}", self.loop_count / elapsed);
+                }
+                println!("on_tick count: {}", self.on_tick_count);
+                println!("on_clock count: {}", self.on_clock_count);
+                println!("on_update count: {}", self.on_update_count);
+                println!("on_account_update count: {}", self.on_account_update_count);
             }
 
             Ok(py_session)
@@ -357,7 +409,7 @@ impl Runner {
         // on clockはSession更新前に呼ぶ
         // こうすることでsession.curent_timestampより先の値でon_clockが呼ばれる.
         // これは、on_clockが呼ばれた時点で、ohlcの更新が終わっていることを保証するため.
-        if self.has_on_clock && message.trade.is_some() {
+        if self.has_on_clock && message.trade.is_some() && interval_sec != 0 {
             let trade = message.trade.as_ref().unwrap();
             let new_clock = FLOOR_SEC(trade.time, interval_sec);
 
@@ -370,15 +422,10 @@ impl Runner {
 
         // on_clockの後にsessionを更新する。
         let mut session = py_session.borrow_mut(*py);
-        session.on_message(&message);
-        self.current_timestamp = session.get_current_timestamp();
+        let orders = session.on_message(&message);
+        self.current_timestamp = session.get_timestamp();
         drop(session);
 
-        if self.has_account_update && message.account.is_some() {
-            let account = message.account.as_ref().unwrap();
-            self.call_agent_on_account_update(py, agent, py_session, account)?;
-        }
-
         if self.has_on_tick && message.trade.is_some() {
             let trade = message.trade.as_ref().unwrap();
             self.current_timestamp = trade.time;
@@ -387,88 +434,75 @@ impl Runner {
 
         if self.has_on_update && message.order.is_some() {
             let order = message.order.as_ref().unwrap();
-            self.call_agent_on_update(py, agent, py_session, order)?;
+            self.call_agent_on_update(py, agent, py_session, order)?;            
         }
 
-        Ok(())
-    }
+        // IN Real run, account message is from user stream.
+        // AccountUpdateはFilledかPartiallyFilledのみ発生。
+        if self.has_account_update && message.account.is_some() {
+            let account = message.account.as_ref().unwrap();
+            self.call_agent_on_account_update(py, agent, py_session, account)?;
+        }
 
-    /*
-    fn on_clock(
-        self: &mut Self,
-        py: &Python,
-        agent: &PyAny,
-        py_session: &Py<Session>,
-        message: &MarketMessage,
-        interval_sec: i64,
-    ) -> Result<(), PyErr> {
-        let trade = message.trade.as_ref().unwrap();
+        // otherwise, account message is created from session for simulation.
+        if orders.len() != 0
+            && (self.execute_mode == ExecuteMode::BackTest || self.execute_mode == ExecuteMode::Dry)
+        {
+            let mut account_change = false;
+
+            if self.has_on_update {
+                for order in &orders {
+                    if self.update_psudo_account_by_order(py, py_session, order) {
+                        account_change = true;
+                    }
 
-        let new_clock = FLOOR_SEC(trade.time, interval_sec);
+                    self.call_agent_on_update(py, agent, py_session, order)?;
+                }
+            }
 
-        if self.current_clock < new_clock {
-            self.current_clock = new_clock;
-            let session = py_session.borrow_mut(*py);
-            agent.call_method1("on_clock", (session, self.current_clock))?;
+            if account_change {
+                self.call_agent_on_account_update_dummy(py, agent, py_session)?;
+            }
         }
 
         Ok(())
     }
-    */
-    /*
-        pub fn dummy_on_message(
-            self: &mut Self,
-            py: &Python,
-            agent: &PyAny,
-            py_session: &Py<Session>,
-            message: &MarketMessage,
-            window_sec: i64,
-        ) -> Result<(), PyErr> {
-            if self.has_on_clock && message.trade.is_some() {
-                self.on_clock(py, agent, py_session, message, window_sec)?;
-            }
-
-            let mut session = py_session.borrow_mut(*py);
-            let orders = session.on_message(&message);
-            drop(session);
-
-            log::debug!("dummy_on_message: {:?}, {:?}", message, orders);
-
-            if message.account.is_some() {
-                let session = py_session.borrow_mut(*py);
 
-                let account = message.account.as_ref().unwrap();
-                let py_account = Py::new(*py, account.clone()).unwrap();
-
-                let result = agent.call_method1("on_account_update", (session, py_account));
+    pub fn update_psudo_account_by_order(
+        &mut self,
+        py: &Python,
+        py_session: &Py<Session>,
+        order: &Order,
+    ) -> bool {
+        let mut session = py_session.borrow_mut(*py);
 
-                if result.is_err() {
-                    return Err(result.unwrap_err());
-                }
-            }
+        session.update_psudo_account_by_order(order)
+    }
 
-            if self.has_on_tick && message.trade.is_some() {
-                let trade = message.trade.as_ref().unwrap();
+    fn get_clock_interval(
+        self: &mut Self,
+        py: &Python,
+        py_session: &Py<Session>,
+    ) -> Result<i64, PyErr> {
+        let session = py_session.borrow_mut(*py);
+        let interval_sec = session.get_clock_interval_sec();
 
-                let session = py_session.borrow_mut(*py);
+        Ok(interval_sec)
+    }
 
-                let price = trade.price.to_f64().unwrap();
-                let size = trade.size.to_f64().unwrap();
+    fn call_agent_on_init(
+        self: &mut Self,
+        py: &Python,
+        agent: &PyAny,
+        py_session: &Py<Session>,
+    ) -> Result<(), PyErr> {
+        let session = py_session.borrow_mut(*py);
 
-                agent.call_method1("on_tick", (session, trade.order_side, price, size))?;
-            }
+        agent.call_method1("on_init", (session,))?;
 
-            if self.has_on_update {
-                for order in orders {
-                    let py_order = Py::new(*py, order.clone()).unwrap();
-                    let session = py_session.borrow_mut(*py);
-                    agent.call_method1("on_update", (session, py_order))?;
-                }
-            }
+        Ok(())
+    }
 
-            Ok(())
-        }
-    */
     fn call_agent_on_tick(
         self: &mut Self,
         py: &Python,
@@ -508,7 +542,9 @@ impl Runner {
         py_session: &Py<Session>,
         clock: MicroSec,
     ) -> Result<(), PyErr> {
-        let session = py_session.borrow_mut(*py);
+        let mut session = py_session.borrow_mut(*py);
+
+        session.set_current_clock(self.current_clock);
 
         agent.call_method1("on_clock", (session, clock))?;
         self.on_clock_count += 1;
@@ -523,7 +559,13 @@ impl Runner {
         py_session: &Py<Session>,
         account: &AccountStatus,
     ) -> Result<(), PyErr> {
-        let session = py_session.borrow_mut(*py);
+        let mut session = py_session.borrow_mut(*py);
+        session.set_real_account(account);
+        if session.log_account(account).is_err() {
+            log::error!("call_agent_on_account_update: log_account failed");
+        }
+
+        log::debug!("call_agent_on_account_update: {:?}", &account);
         let py_account = Py::new(*py, account.clone()).unwrap();
 
         agent.call_method1("on_account_update", (session, py_account))?;
@@ -532,6 +574,28 @@ impl Runner {
         Ok(())
     }
 
+    /// When backtest, the account infomation is from session.
+    fn call_agent_on_account_update_dummy(
+        self: &mut Self,
+        py: &Python,
+        agent: &PyAny,
+        py_session: &Py<Session>,
+    ) -> Result<(), PyErr> {
+        let mut session = py_session.borrow_mut(*py);
+        let account = session.get_account();
+        if session.log_account(&account).is_err() {
+            log::error!("call_agent_on_account_update_dummy: log_account failed");
+        }
+        log::debug!("call_agent_on_account_update_dummy: {:?}", &account);
+
+        let py_account = Py::new(*py, account).unwrap();
+
+        agent.call_method1("on_account_update", (session, py_account))?;
+        self.on_account_update_count += 1;
+
+        Ok(())
+    }
+
     /// get market stream from Market object
     /// Every market object shold implement `channel` attribute
     /// TODO: retrun Error
diff --git a/src/session/session.rs b/src/session/session.rs
index 67155ff3..af1176dc 100644
--- a/src/session/session.rs
+++ b/src/session/session.rs
@@ -1,24 +1,21 @@
 // Copyright(c) 2022-2023. yasstake. All rights reserved.
 
 use std::collections::VecDeque;
-use std::io::Write;
 use std::sync::Mutex;
-use std::{fs::OpenOptions, path::Path};
 
 use pyo3::{pyclass, pymethods, PyAny, PyObject, Python};
-use pyo3_polars::PyDataFrame;
 
 use rust_decimal::{prelude::ToPrimitive, Decimal};
 use rust_decimal_macros::dec;
 
+use super::{Logger, OrderList};
 use crate::common::{
-    date_string, hour_string, min_string, AccountStatus, MarketConfig, MicroSec,
+    date_string, hour_string, min_string, time_string, AccountStatus, MarketConfig, MicroSec,
     OrderSide, OrderStatus, NOW,
 };
-use super::OrderList;
 use pyo3::prelude::*;
 
-use crate::common::{ordervec_to_dataframe, Trade};
+use crate::common::Trade;
 use crate::common::{MarketMessage, SEC};
 use crate::common::{Order, OrderType};
 
@@ -27,7 +24,7 @@ use crate::common::{Order, OrderType};
 pub enum ExecuteMode {
     Real,
     BackTest,
-    Dry
+    Dry,
 }
 
 #[pymethods]
@@ -40,63 +37,17 @@ impl ExecuteMode {
             "REAL" => ExecuteMode::Real,
             "DUMMY" => ExecuteMode::BackTest,
             "DRY" => ExecuteMode::Dry,
-            _ => ExecuteMode::BackTest
+            _ => ExecuteMode::BackTest,
         }
     }
 
-    pub fn __str__(&self)  -> String {
+    pub fn __str__(&self) -> String {
         match self {
             ExecuteMode::Real => "Real",
             ExecuteMode::BackTest => "Dummy",
-            ExecuteMode::Dry => "Dry"
-        }.to_string()
-    }
-}
-
-#[pyclass]
-#[derive(Debug)]
-pub struct ExecuteLog {
-    on_memory: bool,
-    memory: Vec<Order>,
-    log_file: Option<std::fs::File>,
-}
-
-impl ExecuteLog {
-    pub fn new(on_memory: bool, log_file: Option<std::fs::File>) -> Self {
-        Self {
-            on_memory,
-            memory: vec![],
-            log_file,
-        }
-    }
-
-    pub fn open_log(&mut self, path: &str) -> Result<(), std::io::Error> {
-        let log_file = Path::new(path).with_extension("log");
-
-        self.log_file = Some(
-            OpenOptions::new()
-                .append(true)
-                .create(true)
-                .open(log_file)?,
-        );
-        Ok(())
-    }
-
-    pub fn log(&mut self, order: &Order) -> Result<(), std::io::Error> {
-        if self.on_memory {
-            self.memory.push(order.clone());
-        } else {
-            if let Some(file) = &mut self.log_file {
-                let s = serde_json::to_string(order)?;
-                file.write_all(s.as_bytes())?;
-                file.write_all(b"\n")?;
-            }
+            ExecuteMode::Dry => "Dry",
         }
-        Ok(())
-    }
-
-    pub fn get(&self) -> Vec<Order> {
-        self.memory.clone()
+        .to_string()
     }
 }
 
@@ -106,13 +57,19 @@ pub struct Session {
     execute_mode: ExecuteMode,
     buy_orders: OrderList,
     sell_orders: OrderList,
-    account: AccountStatus,
+    real_account: AccountStatus,
+    psudo_account: AccountStatus,
     market: PyObject,
     current_timestamp: MicroSec,
+    current_clock_time: MicroSec,
     pub session_name: String,
     order_number: i64,
     transaction_number: i64,
 
+    position: Decimal,
+    average_price: Decimal,
+    profit: Decimal,
+
     commission_home_sum: Decimal,
     commission_foreign_sum: Decimal,
     home_sum: Decimal,
@@ -122,6 +79,10 @@ pub struct Session {
     lock_home_sum: Decimal,
     lock_foreign_sum: Decimal,
 
+    log_id: i64,
+
+    clock_interval_sec: i64,
+
     asks_edge: Decimal,
     bids_edge: Decimal,
 
@@ -129,10 +90,9 @@ pub struct Session {
 
     dummy_q: Mutex<VecDeque<Vec<Order>>>,
 
-    log: ExecuteLog,
+    log: Logger,
 }
 
-
 #[pymethods]
 impl Session {
     #[new]
@@ -168,13 +128,19 @@ impl Session {
             execute_mode: execute_mode,
             buy_orders: OrderList::new(OrderSide::Buy),
             sell_orders: OrderList::new(OrderSide::Sell),
-            account: AccountStatus::default(),
+            real_account: AccountStatus::default(),
+            psudo_account: AccountStatus::default(),
             market,
             current_timestamp: 0,
+            current_clock_time: 0,
             session_name,
             order_number: 0,
             transaction_number: 0,
 
+            position: dec![0.0],
+            average_price: dec![0.0],
+            profit: dec![0.0],
+
             commission_home_sum: dec![0.0],
             commission_foreign_sum: dec![0.0],
             home_sum: dec![0.0],
@@ -184,6 +150,10 @@ impl Session {
             lock_home_sum: dec![0.0],
             lock_foreign_sum: dec![0.0],
 
+            log_id: 0,
+
+            clock_interval_sec: 0,
+
             asks_edge: dec![0.0],
             bids_edge: dec![0.0],
 
@@ -191,7 +161,7 @@ impl Session {
 
             dummy_q: Mutex::new(VecDeque::new()),
 
-            log: ExecuteLog::new(log_memory, None),
+            log: Logger::new(log_memory),
         };
 
         session.load_order_list().unwrap();
@@ -224,10 +194,15 @@ impl Session {
     }
 
     #[getter]
-    pub fn get_current_timestamp(&self) -> MicroSec {
+    pub fn get_timestamp(&self) -> MicroSec {
         self.current_timestamp
     }
 
+    #[setter]
+    pub fn set_current_clock(&mut self, timestamp: MicroSec) {
+        self.current_clock_time = timestamp;
+    }
+
     #[getter]
     pub fn get_board(&self) -> Result<Py<PyAny>, PyErr> {
         if self.execute_mode == ExecuteMode::BackTest {
@@ -238,10 +213,9 @@ impl Session {
         Python::with_gil(|py| self.market.getattr(py, "board"))
     }
 
-    // account information
     #[getter]
-    pub fn get_account(&self) -> AccountStatus {
-        self.account.clone()
+    pub fn get_execute_mode(&self) -> String {
+        self.execute_mode.__str__()
     }
 
     // order information
@@ -313,17 +287,67 @@ impl Session {
     }
 
     #[getter]
-    pub fn get_log(&self) -> Vec<Order> {
-        self.log.get()
+    pub fn get_position(&self) -> f64 {
+        self.position.to_f64().unwrap()
+    }
+
+    #[getter]
+    pub fn get_psudo_account(&self) -> AccountStatus {
+        self.psudo_account.clone()
+    }
+
+    #[getter]
+    pub fn get_real_account(&self) -> AccountStatus {
+        self.real_account.clone()
+    }
+
+    #[getter]
+    pub fn get_account(&self) -> AccountStatus {
+        match self.execute_mode {
+            ExecuteMode::Real => self.real_account.clone(),
+            ExecuteMode::BackTest => self.psudo_account.clone(),
+            ExecuteMode::Dry => self.psudo_account.clone(),
+        }
     }
 
     #[getter]
-    pub fn get_log_df(&self) -> PyResult<PyDataFrame> {
-        let df = ordervec_to_dataframe(self.get_log());
+    pub fn get_log(&self) -> Logger {
+        self.log.clone()
+    }
+
+    pub fn log_indicator(&mut self, name: String, value: f64) {
+        let timestamp = self.calc_log_timestamp();
 
-        Ok(PyDataFrame(df))
+        let r = self
+            .log
+            .log_indicator(timestamp, &name, value, None, None, None, None);
+        if r.is_err() {
+            log::error!("log_indicator error: {:?}", r);
+        }
     }
 
+    pub fn expire_order(&mut self, ttl_sec: i64) {
+
+        for order in self.buy_orders.get_old_orders(ttl_sec) {
+            if self.cancel_order(&order.order_id).is_ok() {
+                log::debug!("expire_orders: cancel order: {:?}", order);
+            }
+            else {
+                log::warn!("expire_orders: cancel order error: {:?}", order);
+            }
+
+        }
+
+        for order in self.sell_orders.get_old_orders(ttl_sec) {
+            if self.cancel_order(&order.order_id).is_ok() {
+                log::debug!("expire_orders: cancel order: {:?}", order);
+            }
+            else {
+                log::warn!("expire_orders: cancel order error: {:?}", order);
+            }
+        }
+    }
+    
     pub fn cancel_order(&mut self, order_id: &str) -> PyResult<Py<PyAny>> {
         if self.execute_mode == ExecuteMode::BackTest || self.execute_mode == ExecuteMode::Dry {
             self.dummy_cancel_order(order_id)
@@ -349,17 +373,17 @@ impl Session {
 
     pub fn dummy_cancel_order(&mut self, order_id: &str) -> PyResult<Py<PyAny>> {
         Python::with_gil(|py| {
-            let mut order_to_cancel: Order = if let Some(order) = self.buy_orders.get_item_by_id(order_id) {
-                order
-            } else if let Some(order) = self.sell_orders.get_item_by_id(&order_id) {
-                order
-            } else {
-                log::error!("dummy_cancel_order: order not found: {}", order_id);
-                return Ok(Python::None(py));
-            };
+            let mut order_to_cancel: Order =
+                if let Some(order) = self.buy_orders.get_item_by_id(order_id) {
+                    order
+                } else if let Some(order) = self.sell_orders.get_item_by_id(&order_id) {
+                    order
+                } else {
+                    log::error!("dummy_cancel_order: order not found: {}", order_id);
+                    return Ok(Python::None(py));
+                };
 
             order_to_cancel.status = OrderStatus::Canceled;
-            self.update_balance(&order_to_cancel);
             order_to_cancel.update_time = self.current_timestamp;
 
             self.push_dummy_q(&vec![order_to_cancel.clone()]);
@@ -376,11 +400,7 @@ impl Session {
         }
     }
 
-    pub fn real_market_order(
-        &mut self,
-        side: String,
-        size: Decimal,
-    ) -> Result<Py<PyAny>, PyErr> {
+    pub fn real_market_order(&mut self, side: String, size: Decimal) -> Result<Py<PyAny>, PyErr> {
         log::debug!("market_order: side={:}, size={}", &side, size);
 
         let size_scale = self.market_config.size_scale;
@@ -397,57 +417,59 @@ impl Session {
     pub fn calc_dummy_execute_price_by_slip(&mut self, side: OrderSide) -> Decimal {
         // 板がないので、最後の約定価格＋スリッページで約定したことにする（オーダーは分割されないと想定）
         if self.execute_mode != ExecuteMode::BackTest {
-            let execute_price = if side == OrderSide::Buy {
-                self.asks_edge + self.market_config.market_order_price_slip
-            } else {
-                self.bids_edge - self.market_config.market_order_price_slip
-            };
-   
-            return execute_price;
-        }
-        else {
-            log::error!("calc_dummy_execute_price: unknown mode: {:?}", self.execute_mode);
-            return dec![0.0]; 
+            log::error!(
+                "calc_dummy_execute_price: dummy_execute_price should be used in BackTest mode, current mode= {:?}",
+                self.execute_mode
+            );
+            return dec![0.0];
         }
+
+        let execute_price = if side == OrderSide::Buy {
+            self.asks_edge + self.market_config.market_order_price_slip
+        } else {
+            self.bids_edge - self.market_config.market_order_price_slip
+        };
+
+        return execute_price;
     }
 
-    pub fn dry_market_order(
-        &mut self,
-        side: String,
-        size: Decimal,
-    ) -> Result<Py<PyAny>, PyErr> {
+    pub fn dry_market_order(&mut self, side: String, size: Decimal) -> Result<Py<PyAny>, PyErr> {
         let size_scale = self.market_config.size_scale;
         let size = size.round_dp(size_scale);
 
         let local_id = self.new_order_id(&side);
-        let order_side = OrderSide::from(&side); 
+        let order_side = OrderSide::from(&side);
 
         let transaction_id = self.dummy_transaction_id();
 
         Python::with_gil(|py| {
-            self.market
-                .call_method1(py, "dry_market_order", 
-                (self.current_timestamp, local_id.clone(), local_id.clone(),
-                    order_side, size, transaction_id))
+            self.market.call_method1(
+                py,
+                "dry_market_order",
+                (
+                    self.current_timestamp,
+                    local_id.clone(),
+                    local_id.clone(),
+                    order_side,
+                    size,
+                    transaction_id,
+                ),
+            )
         })
     }
 
-    pub fn dummy_market_order(
-        &mut self,
-        side: String,
-        size: Decimal,
-    ) -> Result<Py<PyAny>, PyErr> {
+    pub fn dummy_market_order(&mut self, side: String, size: Decimal) -> Result<Py<PyAny>, PyErr> {
         let size_scale = self.market_config.size_scale;
         let size = size.round_dp(size_scale);
 
         let local_id = self.new_order_id(&side);
-        let order_side = OrderSide::from(&side); 
+        let order_side = OrderSide::from(&side);
 
         let execute_price = self.calc_dummy_execute_price_by_slip(order_side);
 
         let mut order = Order::new(
             self.market_config.symbol(),
-            self.current_timestamp,
+            self.calc_log_timestamp(),
             local_id.clone(),
             local_id.clone(),
             order_side,
@@ -480,7 +502,7 @@ impl Session {
         price: Decimal,
         size: Decimal,
     ) -> Result<Vec<Order>, PyErr> {
-        if self.execute_mode == ExecuteMode::BackTest || self.execute_mode == ExecuteMode::Dry {        
+        if self.execute_mode == ExecuteMode::BackTest || self.execute_mode == ExecuteMode::Dry {
             return self.dummy_limit_order(side, price, size);
         } else {
             return self.real_limit_order(side, price, size);
@@ -562,7 +584,7 @@ impl Session {
         // first push order to order list
         let local_id = self.new_order_id(&side);
 
-        let order_side = OrderSide::from(&side);        
+        let order_side = OrderSide::from(&side);
 
         log::debug!(
             "dummuy_limit_order: side={:?}, size={}, price={}",
@@ -573,7 +595,7 @@ impl Session {
 
         let mut order = Order::new(
             self.market_config.symbol(),
-            self.current_timestamp,
+            self.calc_log_timestamp(),
             local_id.clone(),
             local_id.clone(),
             order_side,
@@ -590,7 +612,6 @@ impl Session {
         return Ok(vec![order]);
     }
 
-
     pub fn on_message(&mut self, message: &MarketMessage) -> Vec<Order> {
         let mut result = vec![];
 
@@ -621,6 +642,25 @@ impl Session {
         return result;
     }
 
+    pub fn update_psudo_account_by_order(&mut self, order: &Order) -> bool {
+        self.psudo_account.apply_order(order);
+
+        if order.status == OrderStatus::Filled || order.status == OrderStatus::PartiallyFilled {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    #[getter]
+    pub fn get_clock_interval_sec(&self) -> i64 {
+        self.clock_interval_sec
+    }
+
+    #[setter]
+    pub fn set_clock_interval_sec(&mut self, interval: i64) {
+        self.clock_interval_sec = interval;
+    }
 
     #[getter]
     pub fn get_dummy_q(&self) -> Vec<Vec<Order>> {
@@ -634,52 +674,82 @@ impl Session {
 
     pub fn __repr__(&self) -> String {
         let mut json = "{".to_string();
-        
-        json += "\"current_timestamp\":";
-        json += self.current_timestamp.to_string().as_str();
-        json += ", \"current_timestamp_str\": \"";
-        json += crate::common::time_string(self.current_timestamp).as_str();
-        json += "\",\n";
-
-        let last_price = self.get_last_price();
 
-        json += "\"last_price\": {";
-        json += "\"buy\":";
-        json += last_price.0.to_string().as_str();
-
-        json += ",\"sell\":";
-        json += last_price.1.to_string().as_str();
+        json += &format!("\"timestamp\":{},", self.current_timestamp);
+        json += &format!(
+            "\"timestamp_str\": {},\n",
+            time_string(self.current_timestamp)
+        );
 
-        json += "},\n";
+        // let (last_sell_price, last_buy_price) = self.get_last_price();
 
         json += "\"orders\":";
+        // order list
+        json += "{\"buy\":";
+        json += self.buy_orders.__repr__().as_str();
+        json += ", \"sell\":";
+        json += self.sell_orders.__repr__().as_str();
+        json += "}, \n";
 
-            // order list
-            json += "{\"buy\":";
-            json += self.buy_orders.__repr__().as_str();
-            json += ", \"sell\":";
-            json += self.sell_orders.__repr__().as_str();
-            json += "}, \n";
-        
+        // account information
+        json += &format!("\"account\":{}, ", self.real_account.__repr__());
 
-            // account information
-            json += "\"account\":";
-            json += self.account.__repr__().as_str();
+        json += &format!("\"psudo_account\":{},", self.psudo_account.__repr__());
 
+        json += &format!("\"psudo_position\":{}", self.position);
         json += "}";
-
         json
     }
 }
 
 impl Session {
     pub fn log(&mut self, order: &Order) -> Result<(), std::io::Error> {
-        self.log.log(order)
+        let time = self.calc_log_timestamp();
+
+        self.log.log_order(time, order)
     }
 
     pub fn open_log(&mut self, path: &str) -> Result<(), std::io::Error> {
         self.log.open_log(path)
     }
+
+    pub fn log_profit(
+        &mut self,
+        log_id: i64,
+        open_position: Decimal,
+        close_position: Decimal,
+        position: Decimal,
+        profit: Decimal,
+        fee: Decimal,
+        total_profit: Decimal,
+    ) -> Result<(), std::io::Error> {
+        let time = self.calc_log_timestamp();
+
+        self.log.log_profit(
+            time,
+            log_id,
+            open_position.to_f64().unwrap(),
+            close_position.to_f64().unwrap(),
+            position.to_f64().unwrap(),
+            profit.to_f64().unwrap(),
+            fee.to_f64().unwrap(),
+            total_profit.to_f64().unwrap(),
+        )
+    }
+
+    pub fn log_account(&mut self, account: &AccountStatus) -> Result<(), std::io::Error> {
+        let time = self.calc_log_timestamp();
+
+        self.log.log_account(time, account)
+    }
+
+    pub fn calc_log_timestamp(&self) -> MicroSec {
+        if self.current_timestamp < self.current_clock_time {
+            self.current_clock_time
+        } else {
+            self.current_timestamp
+        }
+    }
 }
 
 impl Session {
@@ -700,18 +770,23 @@ impl Session {
         }
 
         if self.execute_mode == ExecuteMode::BackTest || self.execute_mode == ExecuteMode::Dry {
-            return self.execute_dummuy_tick(tick);            
-        }
-        else {
+            return self.execute_dummuy_tick(tick);
+        } else {
             return vec![];
         }
     }
 
     pub fn on_account_update(&mut self, account: &AccountStatus) {
-        self.account = account.clone();
+        self.real_account = account.clone();
+
+        if self.log_account(account).is_err() {
+            log::error!("log_account_status error");
+        };
     }
 
     pub fn on_order_update(&mut self, order: &mut Order) {
+        self.log_id += 1;
+        order.log_id = self.log_id;
         order.update_balance(&self.market_config);
 
         if order.order_side == OrderSide::Buy {
@@ -730,20 +805,22 @@ impl Session {
             log::error!("Unknown order side: {:?}", order.order_side)
         }
 
-        self.update_balance(order);
+        if self.log(&order).is_err() {
+            log::error!("log order error{:?}", order);
+        };
 
-        let _ = self.log(&order);
+        self.update_psudo_position(order);
     }
 
     fn new_order_id(&mut self, side: &str) -> String {
         self.order_number += 1;
 
-        format!("{}-{:04}{:}", self.session_name, self.order_number, side)        
+        format!("{}-{:04}{:}", self.session_name, self.order_number, side)
     }
 
     fn load_order_list(&mut self) -> Result<(), PyErr> {
         // when dummy mode, order list is start with empty.
-        if self.execute_mode == ExecuteMode::BackTest || self.execute_mode == ExecuteMode::Dry {        
+        if self.execute_mode == ExecuteMode::BackTest || self.execute_mode == ExecuteMode::Dry {
             return Ok(());
         }
 
@@ -779,22 +856,128 @@ impl Session {
         return r;
     }
 
-    // TODO: check if this is correct
-    pub fn update_balance(&mut self, order: &Order) {
-        self.commission_foreign_sum += order.commission_foreign;
-        self.commission_home_sum += order.commission_home;
+    // ポジションが変化したときは平均購入単価と仮想Profitを計算する。
+    pub fn update_psudo_position(&mut self, order: &Order) {
+        let mut open_position = dec![0.0];
+        let mut close_position = dec![0.0];
+        let mut profit = dec![0.0];
 
-        self.home_sum += order.home_change;
-        self.free_home_sum += order.free_home_change;
-        self.lock_home_sum += order.lock_home_change;
+        if order.order_side == OrderSide::Buy {
+            if order.status == OrderStatus::Filled || order.status == OrderStatus::PartiallyFilled {
+                if dec![0.0] <= self.position {
+                    self.open_position(order.execute_price, order.execute_size);
+                    open_position = order.execute_size;
+                } else {
+                    (close_position, open_position, profit) =
+                        self.close_position(order.execute_price, order.execute_size);
+                }
+            }
+        } else if order.order_side == OrderSide::Sell {
+            if order.status == OrderStatus::Filled || order.status == OrderStatus::PartiallyFilled {
+                if dec![0.0] <= self.position {
+                    (close_position, open_position, profit) =
+                        self.close_position(order.execute_price, -order.execute_size);
+                } else {
+                    self.open_position(order.execute_price, -order.execute_size);
+                    open_position = -order.execute_size;
+                }
+            }
+        } else {
+            log::error!("Unknown order side: {:?}", order.order_side)
+        }
 
-        self.foreign_sum += order.foreign_change;
-        self.free_foreign_sum += order.free_foreign_change;
-        self.lock_foreign_sum += order.lock_foreign_change;
+        let fee = if order.is_maker {
+            order.execute_price * order.execute_size * self.market_config.maker_fee
+        } else {
+            order.execute_price * order.execute_size * self.market_config.taker_fee
+        };
+
+        let total_profit = profit - fee;
+
+        if self
+            .log_profit(
+                order.log_id,
+                open_position,
+                close_position,
+                self.position,
+                profit,
+                fee,
+                total_profit,
+            )
+            .is_err()
+        {
+            log::error!("log_profit error");
+        };
+    }
+
+    /// returns position change
+    pub fn open_position(&mut self, price: Decimal, position: Decimal) {
+        let total_cost = (self.average_price * self.position) + (price * position);
+        let total_size = self.position + position;
+
+        self.average_price = total_cost / total_size;
+        self.position += position;
     }
 
+    /// retruns position change, and profit change
+    pub fn close_position(
+        &mut self,
+        price: Decimal,
+        position: Decimal,
+    ) -> (Decimal, Decimal, Decimal) {
+        // close_position, open_position, profit_change
+        let close_position: Decimal;
+        let mut open_position: Decimal = dec![0.0];
+
+        let profit = if position.abs() <= self.position.abs() {
+            close_position = -position;
+            self.position -= close_position;
 
+            let profit = (price * close_position) - (self.average_price * close_position);
+            self.profit += profit;
 
+            profit
+        } else {
+            close_position = self.position;
+            let new_position = close_position + position;
+
+            // self.position += close_position;
+            let profit = (price * close_position) - (self.average_price * close_position);
+            self.profit += profit;
+
+            log::debug!(
+                "close_position: close_pos={} / closed_pos={}",
+                position,
+                close_position
+            );
+            log::debug!(
+                "close_position: old_pos={} / new_position={}",
+                self.position,
+                new_position
+            );
+
+            self.position = dec![0.0];
+            self.average_price = dec![0.0];
+            open_position = new_position;
+            self.open_position(price, new_position);
+
+            profit
+        };
+
+        (close_position, open_position, profit)
+    }
+
+    /*
+    pub fn change_psudo_position(&mut self, price: Decimal, position_change: Decimal, home_change: Decimal) {
+        // position and position_change have same sign, Open position
+        if dec![0.0] <= self.position * position_change {
+            self.open_position(price, position_change, home_change);
+        }
+        else {
+            self.close_position(price, position_change, home_change);
+        }
+    }
+    */
 
     fn push_dummy_q(&mut self, message: &Vec<Order>) {
         let mut q = self.dummy_q.lock().unwrap();
@@ -818,15 +1001,12 @@ impl Session {
             o.update_balance(&self.market_config);
 
             if o.status == OrderStatus::Filled || o.status == OrderStatus::PartiallyFilled {
-                if o.transaction_id == "" {
-                    o.transaction_id = self.dummy_transaction_id();
-                }
+                o.transaction_id = self.dummy_transaction_id();
             }
         }
     }
 
     fn execute_dummuy_tick(&mut self, tick: &Trade) -> Vec<Order> {
-
         // process dummy order queue
         let orders = self.pop_dummy_q();
         if let Some(mut order_vec) = orders {
@@ -854,4 +1034,163 @@ impl Session {
             return vec![];
         }
     }
+
+    pub fn set_real_account(&mut self, account: &AccountStatus) {
+        self.real_account = account.clone();
+    }
+}
+
+#[cfg(test)]
+mod session_tests {
+    use crate::{
+        common::init_debug_log,
+        exchange::binance::{BinanceConfig, BinanceMarket},
+    };
+
+    use super::*;
+
+    fn new_session() -> Session {
+        pyo3::prepare_freethreaded_python();
+
+        let config = BinanceConfig::BTCUSDT();
+        let binance = BinanceMarket::new(&config);
+
+        Python::with_gil(|py| {
+            let session = Session::new(binance.into_py(py), ExecuteMode::BackTest, None, true);
+
+            session
+        })
+    }
+
+    #[test]
+    fn test_open_plus_position() {
+        let mut session = new_session();
+        session.open_position(dec![100.0], dec![10.0]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![10.0]);
+        assert_eq!(session.profit, dec![0.0]);
+
+        session.open_position(dec![200.0], dec![10.0]);
+        assert_eq!(session.average_price, dec![150.0]);
+        assert_eq!(session.position, dec![20.0]);
+    }
+
+    #[test]
+    fn test_open_minus_position() {
+        let mut session = new_session();
+        session.open_position(dec![100.0], dec![-10.0]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![-10.0]);
+
+        session.open_position(dec![200.0], dec![-10.0]);
+        assert_eq!(session.average_price, dec![150.0]);
+        assert_eq!(session.position, dec![-20.0]);
+    }
+
+    #[test]
+    fn test_close_position_less_than_position() {
+        //init_debug_log();
+        let mut session = new_session();
+        session.open_position(dec![100.0], dec![10.0]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![10.0]);
+
+        session.close_position(dec![150.0], dec![-5.0]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![5.0]);
+
+        // 150 * 5 - 100 * 5 = 250
+        assert_eq!(session.profit, dec![250.0]);
+    }
+
+    #[test]
+    fn test_close_position_less_than_position_minus() {
+        //init_debug_log();
+        let mut session = new_session();
+        session.open_position(dec![100.0], dec![-10.0]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![-10.0]);
+
+        session.close_position(dec![150.0], dec![5.0]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![-5.0]);
+
+        // 150 * 5 - 100 * 5 = 250
+        assert_eq!(session.profit, dec![-250.0]);
+    }
+
+    #[test]
+    fn test_close_position_greater_than_position() {
+        // init_debug_log();
+        let mut session = new_session();
+        session.open_position(dec![100.0], dec![10.0]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![10.0]);
+
+        // TODO: Fic profit calculation
+        session.close_position(dec![150.0], dec![-11.0]);
+        assert_eq!(session.profit, dec![500.0]);
+        assert_eq!(session.position, dec![-1.0]);
+        assert_eq!(session.average_price, dec![150.0]);
+    }
+
+    #[test]
+    fn test_close_position_greater_than_position_minus() {
+        init_debug_log();
+        let mut session = new_session();
+        session.open_position(dec![100.0], dec![-10.0]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![-10.0]);
+
+        // TODO: Fic profit calculation
+        session.close_position(dec![150.0], dec![11.0]);
+        assert_eq!(session.profit, dec![-500.0]);
+        assert_eq!(session.position, dec![1.0]);
+        assert_eq!(session.average_price, dec![150.0]);
+    }
+
+    #[test]
+    fn test_close_position_break_outsample() {
+        //init_debug_log();
+        let mut session = new_session();
+        session.open_position(dec![100.0], dec![-0.00095]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![-0.00095]);
+
+        session.open_position(dec![100.0], dec![-0.00905]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![-0.01]);
+
+        session.close_position(dec![100.0], dec![0.00101]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![-0.00899]);
+
+        session.close_position(dec![100.0], dec![0.01899]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![0.01]);
+    }
+
+    /*
+    #[test]
+    fn test_change_position() {
+        let mut session = new_session();
+
+        // buy 10
+        session.change_psudo_position(dec![100.0], dec![10.0], dec![1000.0]);
+        assert_eq!(session.average_price, dec![100.0]);
+        assert_eq!(session.position, dec![10.0]);
+
+        // buy more 10, with 200 price
+        session.change_psudo_position(dec![200.0], dec![10.0], dec![2000.0]);
+        assert_eq!(session.average_price, dec![150.0]);
+        assert_eq!(session.position, dec![20.0]);
+        assert_eq!(session.profit, dec![0.0]);
+
+        // sell 10
+        session.change_psudo_position(dec![200.0], dec![-10.0], dec![1500.0]);
+        assert_eq!(session.average_price, dec![150.0]);
+        assert_eq!(session.position, dec![10.0]);
+        assert_eq!(session.profit, dec![500.0]);
+    }
+    */
 }


Test patch:
diff --git a/src/test/binance_test/log_load.ipynb b/src/test/binance_test/log_load.ipynb
new file mode 100644
index 00000000..8f7aa55d
--- /dev/null
+++ b/src/test/binance_test/log_load.ipynb
@@ -0,0 +1,133 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from rbot import Logger"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "log = Logger(True)\n",
+    "\n",
+    "log.restore(\"/tmp/breakout.log\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "log.profit"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "profit = log.profit"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "profit['id'] = profit['order_id'] + profit['transaction_id']"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "len(log.orders)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "len(log.position)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "orders = log.orders"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "position = log.position"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "orders.join(position, how=\"outer\", on=\"order_id\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "venv",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/src/test/binance_test/nullagent.py b/src/test/binance_test/nullagent.py
index e56c063f..a0c3d755 100644
--- a/src/test/binance_test/nullagent.py
+++ b/src/test/binance_test/nullagent.py
@@ -7,13 +7,24 @@
 from rbot import init_log
 from rbot import OrderSide
 from rbot import time_string
+from rbot import NOW, DAYS
 
 class MyAgent:
     def __init__(self):
         pass
+
+    def on_init(self, session):
+        session.clock_interval = 60
+        print("init: ", session.timestamp)
+
+        pass
     
     def on_clock(self, session, clock):
-        #print("clock: ", time_string(session.current_timestamp), time_string(clock))
+        bid_edge, ask_edge = session.last_price
+        print("clock: ", time_string(session.timestamp), time_string(clock))
+        #session.limit_order('Buy', bid_edge, 0.001)
+        session.market_order('Buy', 0.001)        
+        #session.limit_order('Sell', ask_edge, 0.001)        
         pass
 
 
@@ -23,47 +34,31 @@ def on_tick(self, session, side, price, size):
         pass
     
     def on_update(self, session, updated_order):
-        print("update: ", time_string(session.current_time), 
-              updated_order.order_side,
-              updated_order.order_price,
-              updated_order.order_size,
-              updated_order.execute_size,
-              time_string(updated_order.create_time),
-              time_string(updated_order.update_time),
-              updated_order.status,
-              )
-        print("buy orders", session.buy_orders)
-        print("sell orders", session.sell_orders)
-        print("account", session.account)
-        print("-------------------")
-    
+        print("ORDER update: ", updated_order.__str__())
+        
     def on_account_update(self, session, account):
-        print("account update: ", session.current_time, account)
+        print("ACCOUNT update: ", session.timestamp, account, session.psudo_account)
         pass
 
-
+init_log()
    
-market = BinanceMarket(BinanceConfig.BTCUSDT)
-#market.start_market_stream()
+market = BinanceMarket(BinanceConfig.TEST_BTCUSDT)
+#market.download(1, force=False, verbose=True)
 
-#init_debug_log()
-
-market.analyze_db()
+market.cancel_all_orders()
+    
+agent = MyAgent()
+runner = Runner()
 
-market.download(2, force=False, verbose=True)
+#init_debug_log()
+session = runner.back_test(market, agent, start_time=NOW()-DAYS(1), end_time=0, verbose=True,execute_time=60*3)
+#session = runner.dry_run(market, agent, verbose=True, execute_time=60)
+#session = runner.real_run(market, agent, verbose=True, execute_time=60)
 
-market.analyze_db()
-market.start_market_stream()
 
-from time import sleep
+print(session)
 
-sleep(10)
-    
-#agent = MyAgent()
-#runner = Runner()
 
-#runner.back_test(market, agent, interval_sec=60, start_time=0, end_time=0, verbose=True)
-#runner.dry_run(market, agent, interval_sec=60, verbose=True)
 
 
     
\ No newline at end of file
diff --git a/src/test/manual/logger.ipynb b/src/test/manual/logger.ipynb
new file mode 100644
index 00000000..65561380
--- /dev/null
+++ b/src/test/manual/logger.ipynb
@@ -0,0 +1,226 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from rbot import Logger\n",
+    "from rbot import Order\n",
+    "from rbot import OrderType\n",
+    "from rbot import OrderSide\n",
+    "from rbot import OrderStatus"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger = Logger(True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "order = Order(\"BTCUSDT\", 1, \"ID-1\", \"orderID2\", OrderSide.Buy, OrderType.Limit, OrderStatus.New, 1000.0, 100)\n",
+    "\n",
+    "logger.log_order(order.create_time, order)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.log_indicator(1, \"macd\", 1.0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.log_indicator(2,'macd', 2.0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.log_position(3, 1.0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.orders"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger['macdo']"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger['macd']"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.position"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.account"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.dump(\"/tmp/test\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.restore(\"/tmp/test\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.orders"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.position"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.position"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "init_debug_log()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from rbot import init_debug_log\n",
+    "init_debug_log()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.log_position(3, 1.0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger.position"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "venv",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/src/test/null_agent.ipynb b/src/test/null_agent.ipynb
index ee8be3ac..60032d4c 100644
--- a/src/test/null_agent.ipynb
+++ b/src/test/null_agent.ipynb
@@ -31,7 +31,7 @@
     "        #print(\"tick: \", session.current_timestamp, side, price, size)\n",
     "        pass\n",
     "\n",
-    "    def on_clock(self, session, current_time):\n",
+    "    def on_clock(self, session, current_time_us):\n",
     "        print(\"clock: \", current_time)\n",
     "        #session.market_order(\"Buy\", 0.001)\n",
     "        pass\n",
diff --git a/src/test/nullagent.ipynb b/src/test/nullagent.ipynb
new file mode 100644
index 00000000..c724965e
--- /dev/null
+++ b/src/test/nullagent.ipynb
@@ -0,0 +1,155 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from rbot import Runner\n",
+    "from rbot import BinanceConfig\n",
+    "from rbot import BinanceMarket\n",
+    "from rbot import init_debug_log\n",
+    "from rbot import init_log\n",
+    "from rbot import time_string\n",
+    "from rbot import NOW, DAYS\n",
+    "\n",
+    "init_log()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "class MyAgent:\n",
+    "    def __init__(self):\n",
+    "        pass\n",
+    "\n",
+    "    def on_init(self, session):\n",
+    "        session.clock_interval = 60\n",
+    "        print(\"init: \", session.timestamp)\n",
+    "\n",
+    "        pass\n",
+    "    \n",
+    "    def on_clock(self, session, clock):\n",
+    "        bid_edge, ask_edge = session.last_price\n",
+    "        print(\"clock: \", time_string(session.timestamp), time_string(clock))\n",
+    "        #session.limit_order('Buy', bid_edge, 0.001)\n",
+    "        #session.market_order('Buy', 0.001) \n",
+    "        if session.psudo_position <= 0:\n",
+    "            session.limit_order('Buy', ask_edge, 0.001)       \n",
+    "        \n",
+    "        pass\n",
+    "\n",
+    "\n",
+    "    def on_tick(self, session, side, price, size):\n",
+    "\n",
+    "        #print(\"tick: \", time_string(session.current_timestamp), side, price, size)\n",
+    "        pass\n",
+    "    \n",
+    "    def on_update(self, session, updated_order):\n",
+    "        print(\"ORDER update: \", updated_order.__str__())\n",
+    "        \n",
+    "    def on_account_update(self, session, account):\n",
+    "        print(\"ACCOUNT update: \", session.timestamp, account, session.psudo_account)\n",
+    "        pass"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "market = BinanceMarket(BinanceConfig.TEST_BTCUSDT)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "agent = MyAgent()\n",
+    "runner = Runner()\n",
+    "\n",
+    "session = runner.back_test(market, agent, start_time=NOW()-DAYS(1), end_time=0, verbose=True,execute_time=60*100)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "session"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "session.log_df"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "market.market_order('Buy', 0.001)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = session.log_df"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.sum()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "venv",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/src/test/tutorial/best_rate_bot.ipynb b/src/test/tutorial/best_rate_bot.ipynb
new file mode 100644
index 00000000..56e4af1b
--- /dev/null
+++ b/src/test/tutorial/best_rate_bot.ipynb
@@ -0,0 +1,80 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# 片道切符まんさんの「取引大会で優勝したときのロジック」を実素してみる。\n",
+    "https://note.com/_and_go/n/na62475340756\n",
+    "\n",
+    "## ロジック概要\n",
+    "\n",
+    "いくつかパターンがあるみたいですが、今回は以下のパターンを実装します。\n",
+    "\n",
+    "1. Best rate付近の大きめの板の手前に指値を出す。\n",
+    "2. Buy, Sell両方に指値を出す。\n",
+    "\n",
+    "### 考慮事項\n",
+    "\n",
+    "#### 「大きめの板」の定義はどうするか？\n",
+    "\n",
+    "1. 板を順番にみていって、最大値の板を探す（次の板が最大値の板より小さければ、最大値の板とする）\n",
+    "2. \n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from rbot import BinanceConfig, BinanceMarket"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "config = BinanceConfig.BTCUSDT\n",
+    "market = BinanceMarket(config)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "venv",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/src/test/tutorial/breakout_agent.ipynb b/src/test/tutorial/breakout_agent.ipynb
index e6113ead..a88a6c96 100644
--- a/src/test/tutorial/breakout_agent.ipynb
+++ b/src/test/tutorial/breakout_agent.ipynb
@@ -38,9 +38,10 @@
     "\n",
     "「シグナル点灯した次の足の始値」ではなく、シグナル点灯直後の最良値でエントリーすることに変更した以外はほぼ忠実に書き下しています。\n",
     "\n",
-    "1. `Agent.on_clock`を10分毎に呼び出すように設定する(`Agent.clock_interval`)\n",
+    "1. `Agent.on_clock`を10分毎に呼び出す。\n",
+    "2. \n",
     "\n",
-    "2. `Agent.on_clock`内で以下の処理を行う。\n",
+    "3. `Agent.on_clock`内で以下の処理を行う。\n",
     "\n",
     "    1. 前処理\n",
     "       \n",
@@ -56,11 +57,11 @@
     "       1. Long判定のとき\n",
     "          * すでにLongポジションがある場合は何もしない。\n",
     "          * Shortポジションがあった場合はドテン（最良値、倍サイズ）\n",
-    "          * 上記に当てはまらない場合は最良値、通常サイズでLong\n",
+    "          * 上記に当てはまらない場合は最良値、通常サイズでLong(Buy)\n",
     "       2. Short判定のとき\n",
     "           * すでにShortポジションがある場合は何もしない\n",
     "           * Shortポジションがあった場合はドテン（最良値、倍サイズ）\n",
-    "           * 上記に当てはまらない場合は最良値、通常サイズでShort"
+    "           * 上記に当てはまらない場合は最良値、通常サイズでShort(Sell)"
    ]
   },
   {
@@ -115,9 +116,6 @@
    },
    "outputs": [],
    "source": [
-    "# 必要ライブラリーのimport\n",
-    "import pandas as pd\n",
-    "\n",
     "# rbotは今回提供するbacktestライブラリ\n",
     "import rbot\n",
     "from rbot import BinanceMarket\n",
@@ -143,16 +141,12 @@
     "* 期間：ほとんどで秒(s)。ログのダウンロード`Market.download`だけ日(day)\n",
     "  * 例：ohlcvの足の幅などは秒を利用。\n",
     "\n",
-    "#### 実装すべき必須メソッド（BaseAgentクラスをOverride)\n",
-    "\n",
-    "Botは`BaseAgent`を継承し以下の３つを実装する必要があります。\n",
-    "* `__init()__()`    クラスのコンストラクタです。上位クラス`BaseAgent`の初期化`super().__init()__()`をわすれないように気をつけてください。\n",
-    "* `clock_interval()` 次の`on_clock()`が呼び出される周期を秒で返します。\n",
-    "* `on_clock(self, time_us, session)` 周期的に呼び出される関数です。`time_us`が現在時刻で、`session`クラスを使って`ohlcv`をとったり`place_order`したりします。\n",
+    "#### Botの実装すべき必須メソッド\n",
     "\n",
-    "#### オプション\n",
-    "* `on_tick(self, time_us, session, price, side, size)` 全ログイベントを受け取ります。\n",
-    "* `on_update(self, time, session, result)`オーダーが約定したり失効した場合に呼ばれます。resultの中にオーダ情報が含まれています。\n",
+    "３つのメソッドを実装するとフレームワークが適宜呼び出してくれます。\n",
+    "* `on_clock(self, session, current_time_us)` 定期的に呼び出される関数です。`time_us`は現在時刻です。`session`クラスを使ってローソク足`ohlcv`をとったりオーダーの発行ができます。\n",
+    "* `on_tick(self, session, side, price, size)` 全ログイベントを受け取ります。\n",
+    "* `on_update(self, session, updated_order)`オーダーが約定したり失効した場合に呼ばれます。'updated_order'の中に更新後のオーダ情報が含まれています。\n",
     "\n"
    ]
   },
@@ -165,19 +159,24 @@
     "class BreakOutAgent:\n",
     "    \"\"\"\n",
     "        Agentのクラス名は任意。\n",
-    "        BaseAgentを継承し、clock_interval, on_clockを実装する。\n",
+    "        on_tick, on_clock, on_updateを実装するとフレームワークから呼び出される。\n",
     "    \"\"\"\n",
     "\n",
     "    def __init__(self, param_K=1.6):\n",
-    "            \"\"\" super().__init()__ で上位クラスの初期化を必ずすること　\"\"\"\n",
-    "            super().__init__()\n",
     "            self.param_K = param_K  # パラメターKを設定する。\n",
+    "            self.offset = 10.0       # 指値のオフセット\n",
+    "\n",
+    "    def on_init(self, session):\n",
+    "        print(\"execute mode = \", session.execute_mode)\n",
+    "        session.clock_interval_sec = 60*60*2  # 2時間ごとにon_clockを呼び出すように設定する。\n",
     "\n",
     "    def on_clock(self, session, time_us):\n",
-    "        \"\"\" Botのメインロジック。on_clockで設定した秒数毎に呼ばれる \"\"\"\n",
+    "        \"\"\" Botのメインロジック。設定した秒数毎に呼ばれる \"\"\"\n",
+    "        # 古いオーダーをキャンセルする。\n",
+    "        \n",
     "        # 前処理/ 前回のon_clock中でのオーダーが処理中の場合はなにもしない（リターン）        \n",
     "        if session.buy_orders or session.sell_orders:\n",
-    "            return \n",
+    "            session.expire_order(60*60*2)  # 2時間以上経過したオーダーをキャンセルする。\n",
     "        \n",
     "        ############   メインロジック  ###################### \n",
     "        ohlcv_df = session.ohlcv(60*60*2, 6)  # 2時間足(60*60*2sec)を６本取得。 最新は６番目。ただし未確定足\n",
@@ -196,43 +195,41 @@
     "        detect_long = range_width  < diff_high\n",
     "        \n",
     "        ##########  メインロジック中に利用したindicatorのロギング（あとでグラフ化するため保存）    ##############\n",
-    "        #self.log_indicator('diff_low', time_us, diff_low)\n",
-    "        #self.log_indicator('diff_high', time_us, diff_high)\n",
-    "        #self.log_indicator('range_width', time_us, range_width)\n",
+    "        session.log_indicator('diff_low', diff_low)\n",
+    "        session.log_indicator('diff_high', diff_high)\n",
+    "        session.log_indicator('range_width', range_width)\n",
     "\n",
     "        ##########　執行戦略（順方向のポジションがあったら保留。逆方向のポジションがのこっていたらドテン）#########\n",
     "        ORDER_SIZE = 0.01     # 標準オーダーサイズ(ドテンの場合はx2)\n",
-    "        ORDER_LIFE = 60*10    # オーダーの有効期間（60x10秒=10分)\n",
-    "\n",
-    "\"\"\"        \n",
-    "        if detect_long and (not session.long_position_size): \n",
-    "            if session.short_position_size: # Shortポジションがあった場合はドテン\n",
-    "                session.place_order( #オーダーを発行する\n",
-    "                            'Buy',                      # 'Buy', 'Sell'を選択\n",
-    "                            session.best_buy_price,     # 最後にbuyがtakeされた価格。sell側にはbest_sell_priceを提供。\n",
-    "                                                        # 任意の価格が設定できるがtakeになる価格でもmakeの処理・手数料で処理している。\n",
-    "                            ORDER_SIZE * 2,             # オーダーサイズ BTCBUSDの場合BTC建で指定。ドテンなので倍サイズでオーダー\n",
-    "                            ORDER_LIFE,                 # オーダーの有効期限（秒）。この秒数をこえるとExpireする。\n",
-    "                            'doten Long'                # あとでログで識別できるように任意の文字列が設定できる。\n",
-    "                )\n",
-    "            else:\n",
-    "                session.place_order('Buy', session.best_buy_price, ORDER_SIZE, ORDER_LIFE, 'Open Long')    \n",
-    "\n",
-    "        if detect_short and (not session.short_position_size): # short判定のとき\n",
-    "            if session.long_position_size:  # Longポジションがあった場合はドテン\n",
-    "                session.place_order('Sell', session.best_sell_price, ORDER_SIZE * 2, ORDER_LIFE, 'Doten Short')\n",
-    "            else:\n",
-    "                session.place_order('Sell', session.best_sell_price, ORDER_SIZE, ORDER_LIFE, 'Open Short') \n",
-    "\"\"\"\n",
+    "\n",
+    "        session.log_indicator('position', session.position)\n",
+    "\n",
+    "        if detect_long and (session.position <= 0): \n",
+    "            if session.sell_orders:\n",
+    "                for order in session.sell_orders:\n",
+    "                    session.cancel_order(order.order_id)\n",
+    "            \n",
+    "            bit_edge, ask_edge = session.last_price\n",
+    "            session.limit_order('Buy', bit_edge - self.offset, ORDER_SIZE + (-session.position))\n",
+    "\n",
+    "        if detect_short and (0 <= session.position): # short判定のとき\n",
+    "            if session.buy_orders:\n",
+    "                for order in session.buy_orders:\n",
+    "                    session.cancel_order(order.order_id)\n",
+    "\n",
+    "            bit_edge, ask_edge = session.last_price            \n",
+    "            session.limit_order('Sell', ask_edge + self.offset, ORDER_SIZE + session.position)\n",
     "\n",
     "    # 全Tick受け取りたい時は on_tick を実装する。\n",
     "    #def on_tick(self, time, session, side, price, size):\n",
     "    #    pass\n",
     "\n",
-    "    ## 約定イベントを受け取りたい時は on_updateを実装する。\n",
-    "    #def on_update(self, time, session, result):\n",
-    "    #    print(str(result))\n",
-    "    #    pass\n"
+    "    #約定イベントを受け取りたい時は on_updateを実装する。\n",
+    "    def on_update(self, session, order_result):\n",
+    "        print(order_result)\n",
+    "\n",
+    "    def on_account_update(self, session, account):\n",
+    "        print(account)\n"
    ]
   },
   {
@@ -241,17 +238,13 @@
     "id": "tXAc9XH7d71W"
    },
    "source": [
-    "## 4. バックテスト用データのロード\n",
+    "## 4. Binanceオブジェクトの作成データのロード\n",
     "\n",
-    "現在はBianceのSpot取引のみ対応。\n",
+    "### Binanceオブジェクトを生成。\n",
     "\n",
-    "`Market.open`でバイナンス（`BN`)の通貨ペアー(`BTCBUSD`)を開く。\n",
-    "その後、`Market.download`で過去n日分のデータをダウンロードする。\n",
+    "接続先、取り扱い通過ペアーなどの情報を`BinanceConfig`に設定します。あらかじめプリセットされた値として、BTCUSDペアー本番用`BinanceConfig.BTCUSDT`, BTCUSDTペアーテストネット用`BinanceConfig.TEST_BTCUSDT`が提供されていますので必要に応じて選択してください。まずはTESTネットでテストします。\n",
     "\n",
-    "2回目以降はディフォルトではダウンロードしない。\n",
-    "再ダウンロードしたい場合は、`Market.download(5, True)`のように再ダウンロードフラグをTrueにする。\n",
-    "\n",
-    "ColabだとダウンロードかDiskに制限があるのか１日あたり１分ぐらいかかることがあります。"
+    "BinanceMarketオブジェクトをJupyterで表示するとデータベースに保存されている取引履歴の期間などが表示されます。"
    ]
   },
   {
@@ -260,31 +253,46 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "BACKTEST_PERIOD = 5             # ダウンロード＆バックテストには時間がかかるのでテスト用に少なめに設定\n",
+    "from rbot import BinanceConfig\n",
+    "from rbot import BinanceMarket\n",
+    "\n",
+    "binance = BinanceMarket(BinanceConfig.BTCUSDT)\n",
+    "binance"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### データのダウンロード\n",
+    "\n",
+    "最初にダウンロードメソッドをつかってデータをダウンロードします。\n",
+    "\n",
+    "#### ダウンロードメソッド\n",
+    "```\n",
+    "BinanceMarket.download(ndays, *, force=False, verbose=True)\n",
+    "```\n",
     "\n",
-    "binance = BinanceMarket(BinanceConfig.BTCUSDT)  # binance marketはあとで利用するので保存しておく\n",
+    "##### パラメータ\n",
+    "* `ndays` 何日前のデータからダウンロードするかを指定します。\n",
+    "* `forde` オプション：TureにするとローカルDBにデータがあっても再ダウンロードします。\n",
+    "* `verbose` オプション:ダウンロード状況を印刷します（ディフォルトは表示）\n",
     "\n",
-    "binance.download(BACKTEST_PERIOD, False)             # BACKTEST_PERIODより最新のログデータを差分ダウンロード\n",
-    "#Market.download(BACKTEST_PERIOD, True)      # 再ダウンロード (１日あたり１０秒＋アルファかかります。)\n"
+    "##### 戻り値\n",
+    "* ダウンロードされたレコード数\n"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/",
-     "height": 139
-    },
-    "id": "So_PM4hId71W",
-    "outputId": "bf043bff-c6c5-46b0-8d42-327595c3eb0d"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "# Marketオブジェクトを表示するとデータの保持期間、保存場所がわかる\n",
-    "# かなり巨大なデータベースになるので、不要になったら場所を確認して手で削除してください。\n",
-    "# 手元の計測では、70日分で15Gありました（１日あたり200Mb)\n",
-    "binance"
+    "# 過去２日分のデータをダウンロード\n",
+    "#from rbot import init_debug_log;\n",
+    "#init_debug_log()\n",
+    "\n",
+    "binance.download(10, verbose=True)"
    ]
   },
   {
@@ -293,15 +301,20 @@
    "metadata": {
     "colab": {
      "base_uri": "https://localhost:8080/",
-     "height": 35
+     "height": 139
     },
-    "id": "wRFAgPknd71X",
-    "outputId": "b10c2549-306d-4727-9e95-9dccc9ececd2"
+    "id": "So_PM4hId71W",
+    "outputId": "bf043bff-c6c5-46b0-8d42-327595c3eb0d"
    },
    "outputs": [],
    "source": [
+    "# かなり巨大なデータベースができあがります。１日分で数百MB。\n",
+    "# DBを削除する場合は以下のコマンドを実行してください\n",
+    "# なお最後に表示されたファイルを別途OSから削除してください。\n",
+    "#binance.drop_table()\n",
+    "\n",
     "# DBのある場所を表示します。かなり大きなファイルになりますので不要になった場合手動で消してください。\n",
-    "binance.file_name"
+    "#binance.file_name"
    ]
   },
   {
@@ -324,7 +337,7 @@
     "\n",
     "runner = Runner()\n",
     "\n",
-    "runner.back_test(binance, agent, 60*60*2, 0, 0, 60, True)  # 2時間足でバックテストする。\n"
+    "session = runner.back_test(binance, agent, start_time=0, end_time=0, verbose=True)  # 2時間足でバックテストする。\n"
    ]
   },
   {
@@ -373,187 +386,122 @@
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/",
-     "height": 564
-    },
-    "id": "Fv1VFjzsd71Z",
-    "outputId": "9b5d0624-9a88-4b08-ab77-1ff23347f900"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "pd.options.display.max_rows=30\n",
-    "\n",
-    "df = back_runner.result\n",
-    "df"
+    "log = session.log"
    ]
   },
   {
-   "cell_type": "markdown",
-   "metadata": {
-    "id": "-Uu92DuMd71Z"
-   },
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
-    "### いろいろ分析\n",
-    "\n",
-    "損益などの単位は、オーダー時に指定したサイズの単位の逆側になります。\n",
-    "\n",
-    "例：今回はBTCBUSDの組み合わせに対し、BTCのサイズでオーダ発行しているので、BUSDでの損益。\n"
+    "import polars as pl\n",
+    "pl.Config(fmt_str_lengths=50)\n",
+    "orders = log.orders\n",
+    "orders"
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/"
-    },
-    "id": "rDiOw-a-d71a",
-    "outputId": "582e589d-b995-4352-d473-d50da3f97d0b"
-   },
-   "outputs": [],
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
-    "# 損益結果　 \n",
-    "df['sum_profit'][-1]"
+    "### 結果分析\n"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/"
-    },
-    "id": "ydx87spFd71a",
-    "outputId": "9b6d1ecb-3bde-41f4-9e98-4a7f0886f6d3"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "# 利益のピーク\n",
-    "df[\"sum_profit\"].max()"
+    "# 損益結果\n",
+    "orders['sum_profit'][-1]"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/"
-    },
-    "id": "vdxOfSvod71a",
-    "outputId": "f1c7cdea-7e3f-4ec0-f972-29ca136992e5"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "# 1回の取引の最大利益\n",
-    "df[\"total_profit\"].max()"
+    "# 利益ピーク\n",
+    "orders['sum_profit'].max()"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/"
-    },
-    "id": "kn3ur4L8d71b",
-    "outputId": "c696355e-8795-4c54-dce9-f4aa50471859"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "# １回の取引の最大損失\n",
-    "df[\"total_profit\"].min()"
+    "# 利益最小\n",
+    "orders['sum_profit'].min()"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/"
-    },
-    "id": "k9ayHUe1d71b",
-    "outputId": "82e92fde-ecc2-494e-cad2-0070353ca2d1"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "# 手数料なしの場合の損益\n",
-    "df['profit'].sum()"
+    "# １回の取引の最大利益\n",
+    "orders['total_profit'].max()"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/"
-    },
-    "id": "EyEuPFWxd71b",
-    "outputId": "7c5beacc-d4ab-439c-d5f2-b272bedb391b"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "# 手数料合計\n",
-    "df['fee'].sum()"
+    "# １回の取引の最大損失\n",
+    "orders['total_profit'].min()"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/"
-    },
-    "id": "xwtNLnpud71b",
-    "outputId": "4e834b43-9396-4e95-c592-afbee95205c6"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "# オーダー回数・Expire数の分析\n",
-    "df.groupby([\"order_side\", \"status\"])[\"status\"].count()"
+    "orders.group_by(['order_side', 'status']).count()"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/"
-    },
-    "id": "sE5fEVXyd71c",
-    "outputId": "ea57f19e-b792-4e8a-97ef-67ffc8022e31"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "# 勝率分析\n",
-    "win_trade_df  = df[ (df[\"status\"] == \"Close\") &  (0 < df[\"total_profit\"])]\n",
-    "loss_trade_df = df[ (df[\"status\"] == \"Close\") &  (df[\"total_profit\"] < 0)]\n",
+    "# オーダーごとに集約\n",
+    "group_by_order = orders.group_by(['order_id']).agg(\n",
+    "    pl.col('symbol').first(), \n",
+    "    pl.col('order_side').first(), \n",
+    "    pl.col('status').last(), \n",
+    "    pl.col('order_price').first(), \n",
+    "    pl.col('order_size').first(), \n",
+    "    pl.col('execute_size').sum(),\n",
+    "    pl.col('update_time').last(),\n",
+    "    pl.col('total_profit').sum()\n",
+    ").sort('update_time')\n",
     "\n",
-    "win_trade = len(win_trade_df)\n",
-    "loss_trade = len(loss_trade_df)\n",
     "\n",
-    "print(\"買った数:\", win_trade)\n",
-    "print(\"負けた数:\", loss_trade)\n",
-    "print(\"勝率:\", win_trade/(loss_trade+win_trade))"
+    "group_by_order"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/"
-    },
-    "id": "SQCWC0iJd71c",
-    "outputId": "12a4a04c-8be0-4446-df13-b587b74ec28c"
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
-    "# 約定時間分析\n",
+    "lost = len(group_by_order.filter(pl.col('total_profit') < 0))\n",
+    "win = len(group_by_order.filter(pl.col('total_profit') > 0))\n",
     "\n",
-    "df['exec_time'] = df['update_time'] - df.index\n",
-    "df[(df['status'] != 'Expire')]['exec_time'].mean()"
+    "print(f'勝ち:{win} 負け:{lost} 勝率:{win/(win+lost)}')"
    ]
   },
   {
@@ -597,45 +545,7 @@
     "#binance = Market.open(\"BN\", \"BTCBUSD\")\n",
     "#Market.download(BACKTEST_PERIOD)\n",
     "\n",
-    "ohlcv = binance.ohlcv(DAYS_BEFORE(BACKTEST_PERIOD), 0, 60*10)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/",
-     "height": 455
-    },
-    "id": "ONBHzited71d",
-    "outputId": "a4f62431-e84a-40df-d1c9-a0c0660abbb5"
-   },
-   "outputs": [],
-   "source": [
-    "ohlcv"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "id": "CTUvckjYd71d"
-   },
-   "source": [
-    "#### OHLCVチャートの描画\n",
-    "\n",
-    "`Chart`クラスを利用してチャートを描く。\n",
-    "引数は、`Chart(横幅、縦幅、olhcvデータ(DataFrame型))`となっています。\n",
-    "\n",
-    "`Chart`インスタンスを作成し、`show()`メソッドを呼び出すことで描画されます。\n",
-    "\n",
-    "描画にはbokehライブラリ`https://bokeh.org`を使っています。また`Chart`クラスはPythonの`site-package`にインストールされているはずですので、それをみながらサブクラスを作ると自由に描画がカスタマイズできると思います。（インストール先の場所の例:`lib/python3.10/site-packages/rbot/chart/`)\n",
-    "\n",
-    "Colabの場合、ここで、\n",
-    "```\n",
-    "AttributeError: unexpected attribute 'overlay' to CrosshairTool, possible attributes are description, dimensions, js_event_callbacks, js_property_callbacks, line_alpha, line_color, line_width, name, subscribed_events, syncable, tags or toggleable\n",
-    "```\n",
-    "というエラーがでたらbokehのバージョンが2で一旦Pythonにロードされてしまった可能性があります。カーネルを再起動してはじめからやり直してください。"
+    "ohlcv = binance.ohlcv(0, 0, 60*60*2)"
    ]
   },
   {
@@ -651,111 +561,43 @@
    },
    "outputs": [],
    "source": [
-    "chart = Chart(900, 400, ohlcv)\n",
-    "chart.show()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "id": "PydxKAR9d71d"
-   },
-   "source": [
-    "#### OHLCVにBackTestの結果を追加する。\n",
+    "import plotly.graph_objects as go\n",
+    "from plotly.subplots import make_subplots\n",
     "\n",
-    "`Chart.draw_result`でバックテストの結果を追加表示できます。"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/",
-     "height": 867
-    },
-    "id": "3Ns8S4shd71d",
-    "outputId": "d4de4d22-03b9-4c7e-d6bb-6f863b0f6370"
-   },
-   "outputs": [],
-   "source": [
-    "chart = Chart(900, 400, ohlcv)\n",
+    "fig = make_subplots(rows=4, cols=1, shared_xaxes=True, vertical_spacing=0.1, row_heights=[0.1, 0.1, 0.1, 0.6],\n",
+    "                    subplot_titles=(\"indicator\", \"position\", \"profit\", \"candlestick\"))\n",
     "\n",
-    "chart.draw_result(back_runner.result)\n",
+    "# row 1 (indicator)\n",
+    "diff_low = log['diff_low']\n",
+    "fig.add_trace(go.Scatter(x=diff_low['timestamp'], y=diff_low['diff_low'], name=\"diff_low\"), row=1, col=1)\n",
     "\n",
-    "chart.show()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "id": "Il_LfKu_d71e"
-   },
-   "source": [
-    "### indicatorの表示\n",
+    "diff_high = log['diff_high']\n",
+    "fig.add_trace(go.Scatter(x=diff_low['timestamp'], y=diff_low['diff_low'], name=\"diff_high\"), row=1, col=1)\n",
     "\n",
-    "Agentの`on_clock`内で`Agent.log_indicator`を使って保存していたindicatorを`Agent.indicator()`を使って取り出すことができます。`DataFrame`形式になっているので表示します。"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/",
-     "height": 455
-    },
-    "id": "bylQBi7ud71e",
-    "outputId": "22152fb3-6321-4471-86d3-a00d3818fff0"
-   },
-   "outputs": [],
-   "source": [
-    "diff_low = agent.indicator('diff_low')\n",
-    "diff_high = agent.indicator('diff_high')\n",
-    "range_width = agent.indicator('range_width')\n",
-    "range_width"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "id": "xuWKP8rWd71e"
-   },
-   "source": [
-    "`Chart.new_figure(新しいパネル名, height=パネルの高さ, title=タイトル)`で新しい描画画面を作ります。\n",
+    "range_width = log['range_width']\n",
+    "fig.add_trace(go.Scatter(x=range_width['timestamp'], y=range_width['range_width'], name=\"range_width\"), row=1, col=1)\n",
     "\n",
-    "作成したパネル名を指定して、`Chart.line`でデータを表示します。\n",
+    "orders = log.orders\n",
     "\n",
-    "`Chart.line(描画パネル名, DataFrame, x_key=データフレーム中のx軸名, y_key=データフレーム中のy軸名, color=線の色, legend_label=凡例として表示する名前)`"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "colab": {
-     "base_uri": "https://localhost:8080/",
-     "height": 1000
-    },
-    "id": "bZ0Lap-td71e",
-    "outputId": "bed0f720-43ec-478d-b00a-4ffdd320b0b5"
-   },
-   "outputs": [],
-   "source": [
-    "chart = Chart(900, 400, ohlcv)\n",
+    "# row 2 (position)\n",
+    "fig.add_trace(go.Scatter(x=orders['update_time'], y=orders['position'], name=\"position\"), row=2, col=1)\n",
+    "\n",
+    "\n",
+    "# row 3 (profit)\n",
+    "profit = orders['sum_profit']\n",
+    "fig.add_trace(go.Scatter(x=orders['update_time'], y=orders['sum_profit'], name=\"profit\"), row=3, col=1)\n",
     "\n",
-    "chart.draw_result(back_runner.result)\n",
+    "# row 4 (candlestick)\n",
+    "fig.add_candlestick(x=ohlcv['timestamp'], open=ohlcv['open'], high=ohlcv['high'], low=ohlcv['low'], close=ohlcv['close'], row=4, col=1)\n",
     "\n",
-    "# indicatorの表示\n",
-    "## 新しい描画パネルを作成\n",
-    "chart.new_figure('indicator_panel', height=150, title='indicator')\n",
+    "# row 4 (order)\n",
+    "buy_orders = orders.filter((orders['order_side'] == 'Buy') & (orders['status'] == 'New'))\n",
+    "fig.add_trace(go.Scatter(x=buy_orders['update_time'], y=buy_orders['order_price'], mode='markers', marker=dict(symbol='arrow-up', color='red', size=10), name=\"buy\"), row=4, col=1)\n",
     "\n",
-    "## indicatorパネルにDataFrameを指定して折線グラフを表示\n",
-    "chart.line('indicator_panel', diff_low, color='#ff0000', legend_label='diff_low')\n",
-    "chart.line('indicator_panel', diff_high, color='#00ff00', legend_label='diff_high')\n",
-    "chart.line('indicator_panel', range_width, color='#0030ff', legend_label='range_width')\n",
+    "sell_orders = orders.filter((orders['order_side'] == 'Sell') & (orders['status'] == 'New'))\n",
+    "fig.add_trace(go.Scatter(x=sell_orders['update_time'], y=sell_orders['order_price'], mode='markers', marker=dict(symbol='arrow-down', color='blue', size=10), name=\"sell\"), row=4, col=1)\n",
     "\n",
-    "chart.show()"
+    "fig.update_layout(height=800, title_text=\"Backtest Result\")\n"
    ]
   },
   {