-
BuySellクラスを作成して__init__(), __str__()メソッドを追加する
ここではBuySellクラスを作成して__init__(), __str__()メソッドを追加します。
BuySellクラスには、BOTが自動トレードするための各種メソッドがまとめられています。
レバレッジ取引には「買い▶売り」と「空売り▶買戻し」の2種類ありますが、
BuySellクラスでは前者の「買い▶売り」の取引に関連するメソッドがまとめられています。
__init__()メソッドはBuySellクラスのインスタンスを生成したときに自動的に呼ばれます。
__str__()メソッドはprint()等でBuySellクラスを表示したときに呼ばれます。
__init__()、__str__()の詳しいことは、
「記事(Article085)」、
「記事(Article087)」で詳しく解説しています。
buy_sell.py:
# buy_sell.py
"""
BuySell class
"""
# import the libraries
from time import sleep
import datetime as dt
from datetime import timedelta
import math
import statistics
from decimal import Decimal
import numpy as np
import pandas as pd
from lib.base import Bcolors, Gvar, Coin, Gmo_dataframe
from lib.debug import Debug
from lib.api import Api
from lib.csv import Csv
from lib.trade import Trade
import warnings
warnings.simplefilter('ignore')
##############
class BuySell:
"""
__init__(), __str__(),
reset_restart_process(),
buy_sell(), buy_sell_algorithm_0(), buy_sell_algorithm_1, buy_sell_algorithm_2(), buy_sell_algorithm_3(),...
create_buy_order(), create_sell_order()
update_order(), update_closed_order(), update_partially_closed_order(), update_order_profit()
stop_loss(), stop_loss_0(), stop_loss_1(),...
cancel_pending_order()
close_pending_order()
"""
######################################################
def __init__(self, gvar: object, coin: object) ->None:
self.gvar = gvar
self.coin = coin
self.debug = Debug(gvar)
self.api = Api(gvar, coin)
self.csv = Csv(gvar, coin)
self.trade = Trade(gvar, coin)
self.folder_gmo = f'{self.gvar.domain}/{self.gvar.exchange}/' # desktop-pc/bot/
self.folder_trading_type = f'{self.gvar.domain}/{self.gvar.exchange}/{self.coin.trade_type}/' # desktop-pc/bot/buy_sell/
self.debug.print_log(f"{Bcolors.OKGREEN}BuySell({self.gvar.althorithm_id},{self.gvar.buysell_condition}){Bcolors.ENDC} class initiated: {self.coin.symbol}, qty=[{self.coin.qty:,.{self.coin.qty_decimal}f}], rate=[{self.coin.profit_rate:.4f}, {self.coin.stop_loss_rate:.4f}], debug1={self.coin.debug_mode_debug1}, debug2={self.coin.debug_mode_debug2} ")
return
#########################
def __str__(self) -> str:
return f"BuySell({self.folder_gmo=}, {self.folder_trading_type=}, {self.gvar.real_mode=}, {self.gvar.reset_mode=}, {self.gvar.althorithm_id=}, {self.coin.symbol=})"
図1-1はBOTとBuySellクラスの各種メソッドの関連図です。
BOTのmain()から関数「auto_trade()」を呼びます。
auto_trade()では、GMOコインから取引履歴をロードします。
そしてBuySellクラスのbuy_sell()メソッドを呼び出して取引データを渡します。
buy_sell()メソッドではアルゴリズムを使用して取引のタイミングを決めてcreate_buy_order(), create_sell_order()メソッドを呼びます。
取引の処理が完了するとauto_trade()に戻ります。
auto_trade()は、次にBuySellクラスのupdate_order()メソッドを呼びます。
update_order()では、update_closed_order()メソッドを呼んで取引が約定(執行)された注文を更新します。
さらにコール元の要求があれば、
stop_loss(), cancel_pending_order(), close_pending_order()等の処理を行います。
最後にupdate_order_profit()メソッドを呼び出して取引の損益を計算してauto_trade()に戻ります。
図1-2はデモプログラムの実行結果です。
ここではBuySellクラスの各種プロパティを表示しています。
print()でBuySellのオブジェクトを指定するとBuySellクラスの__str__()メソッドが呼ばれます。
__str__()はBuySell, Gvar, Coinクラスから各種プロパティを取得して返します。
-
BuySellクラスにreset_restart_process()メソッドを追加する
ここではBuySellクラスにreset_restart_process()メソッドを追加します。
このメソッドはBOTによる自動トレードで必要となる各種CSVファイルを作成して初期化します。
行47-63はBOTが初期モード(gvar.reset_mode=True)のときに実行されます。
ここでは買い注文と売り注文のCSVファイルを作成して初期化します。
CSVファイルは仮想通貨ごとに作成されます。
行69-85はBOTに新規に取引する仮想通貨を追加したときに実行されます。
ここでも買い注文と売り注文のCSVファイルを作成して初期化します。
buy_sell.py:
########################################
def reset_restart_process(self) -> None:
self.gvar.callers_method_name = 'reset_restart_process():'
symbol = self.coin.symbol
# get master df from the coin class
master_df = self.coin.master_df
if master_df.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - master_df is empty {master_df.shape[0]=} return ")
self.debug.sound_alert(3)
return
order_qty = self.coin.qty
mas_df = master_df
# ---------------------------------------------------------
# # Column Dtype
# ---------------------------------------------------------
# 0 price float64
# 1 side object
# 2 size float64
# 3 timestamp datetime64[ns, UTC]
# --------------------------------------------------------
### get latest buy order info from the GMO dataframe (timestamp is sorted by ascending order)
buy_time = mas_df.iloc[0]['timestamp'] # get oldest time
buy_price = mas_df.iloc[0]['price'] # get oldest price
buy_order_id = 'FakeB:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f') # FakeB:MMDD-HHMMSS-999999(milliseconds)
buy_position_id = 'PosiB:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f') # PosiB:MMDD-HHMMSS-999999(milliseconds)
# round time
freq = '1T'
buy_time = buy_time.round(freq=freq) # round x minutes (where x is 1,4,10,15,30)
buy_time = buy_time.round(freq='S') # round seconds
buy_time = buy_time - timedelta(hours=1) # substract 1 hour
### get sell order info from the GMO dataframe
sell_time = buy_time # + timedelta(seconds=5)
sell_price = self.trade.get_sell_price(buy_price)
sell_order_id = 'FakeS:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f') # FakeS:MMDD-HHMMSS-999999(milliseconds)
sell_position_id = 'PosiS:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f') # PosiS:MMDD-HHMMSS-999999(milliseconds)
# reset mode ?
if self.gvar.reset_mode:
self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} reset): delete_allfiles() ▶ add a fake data into the buy/sell order csv files...")
### 1) add a fake data into the buy order, buy order sub files
# write_buy_order(buy_time: utcnow, sell_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_buy_order(buy_time, sell_time, order_qty, buy_price, order_qty, buy_price, 0.0)
# write_buy_order_sub(buy_time: utcnow, sell_time: utcnow, buy_order_id: str, sell_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_buy_order_sub(buy_time, sell_time, buy_order_id, sell_order_id, buy_position_id, order_qty, buy_price, order_qty, buy_price, 0.0)
### 2) add a fake data into the sell order, sell sub_order log file
# write_sell_order(sell_time: utcnow, buy_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order(sell_time, buy_time, order_qty, sell_price, order_qty, sell_price, 0.0)
# write_sell_order_sub(sell_time: utcnow, buy_time: utcnow, sell_order_id: str, buy_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order_sub(sell_time, buy_time, sell_order_id, buy_order_id, sell_position_id, order_qty, sell_price, order_qty, sell_price, 0.0)
else: # restart mode
# self.debug.print_log(f".... processing({symbol} restart): update_order() ▶ add a fake data into the sell/buy order csv files...")
# master csv file not found ?
if self.coin.master_csv_not_found:
self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} restart): master_csv_not_found(True) ▶ add a fake data into the buy/sell order csv files...")
### 1) add a fake data into the buy order, buy order sub csv files
# write_buy_order(buy_time: utcnow, sell_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_buy_order(buy_time, sell_time, order_qty, buy_price, order_qty, buy_price, 0.0)
# write_buy_order_sub(buy_time: utcnow, sell_time: utcnow, buy_order_id: str, sell_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_buy_order_sub(buy_time, sell_time, buy_order_id, sell_order_id, buy_position_id, order_qty, buy_price, order_qty, buy_price, 0.0)
### 2) add a fake data into the sell order, sell order sub csv files
# write_sell_order(sell_time: utcnow, buy_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order(sell_time, buy_time, order_qty, sell_price, order_qty, sell_price, 0.0)
# write_sell_order_sub(sell_time: utcnow, buy_time: utcnow, sell_order_id: str, buy_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order_sub(sell_time, buy_time, sell_order_id, buy_order_id, sell_position_id, order_qty, sell_price, order_qty, sell_price, 0.0)
else: # skip close pending orders
self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} restart): skip close pending orders for restart...")
# end of if coin.master_csv_not_found:
# end of if self.gvar.reset_mode:
return
図2はデモプログラムの実行結果です。
ここではGvarクラスのreset_modeプロパティに「True」を設定して初期化モードで実行しています。
この場合、BOTのサブフォルダ「buy_sell」に買いの注文ファイル「buy_order({symbol}).csv, buy_order_sub({symbol}).csv」
と売りの注文ファイル「sell_order({symbol}).csv, sell_order_sub({symbol}).csv」が作成されて初期化されます。
CSVファイルは仮想通貨ごとに作成されます。
たとえば、「buy_order(BTC_JPY).csv, buy_order(ETH_JPY).csv, buy_order(BCH_JPY).csv,...」のようなCSVファイルが作成されます。
-
BuySellクラスにbuy_sell(), buy_sell_algorithm_0()メソッドを追加する
ここではBuySellクラスにbuy_sell()メソッドを追加します。
このメソッドはBOTの肝になる部分です。
buy_sell()メソッドの引数には「algorithm_id」を指定します。
「algorithm_id=0」はデフォルトで用意されています。
BOTのテストを行うときは「algorithm_id=0」を指定して行います。
BOTのテストが完了したら独自のアルゴリズムを作成して
新規メソッドとして「buy_sell_algorithm_1()」、「buy_sell_algorithm_2()」のように追加します。
仮想通貨を売買(トレード)するアルゴリズムは、
「記事(Article108)」で紹介しているさまざまなテクニカルインジケーターを組み合わせて作ります。
具体的な新規メソッドの作り方については後述します。
デフォルトで用意されている「buy_sell_algorithm_0()」メソッドは単純なアルゴリズムを組み込んでいます。
まずは、前回取引(トレード)した注文が約定しているか調べます。
まだ、約定していないときは何もしないで戻ります。
前回の取引が既に約定しているときは、成行きで買いの注文を入れます。
通常、成行きの注文はすぐに約定するので注文が約定するまで待ちます。
買いの注文が約定したら、今度は指値で売りの注文を入れてポジションを決済します。
指値の売りの注文を入れるときの金額は、
単純に買いの金額(レート)に利益率を掛けて計算します。
利益率はCoinクラスの「coin.profit_rate」プロパティに設定されています。
具体的には、
買いの注文を入れるときは「create_buy_order()」メソッド、
売りの注文を入れるときは「create_sell_order()」メソッドを呼びます。
buy_sell.py:
##############################################
def buy_sell(self, algorithm_id: int) -> None:
if algorithm_id <= 3:
eval(f'self.buy_sell_algorithm_{str(algorithm_id)}()') # call 'self.buy_sell_algorithm_?() method
else:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid Algorithm_id {algorithm_id=} {Bcolors.ENDC} - skip buy_sell ")
self.debug.sound_alert(3)
return
#######################################
def buy_sell_algorithm_0(self) -> None:
"""
"""
self.gvar.callers_method_name = 'buy_sell(0):'
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast dataframe()
### filter buy master
filter_mask = mas_df['side'] == 'BUY'
mas_buy_df = mas_df[filter_mask]
# mas_buy_df is less than trend_search_count ? => SYTEM ERROR
if mas_buy_df.shape[0] < self.coin.trend_search_count: # < 3
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_buy_df({mas_buy_df.shape[0]}) is less than {self.coin.trend_search_count}. skip buy_sell() ")
self.debug.sound_alert(3)
return
### filter sell master
filter_mask = mas_df['side'] == 'SELL'
mas_sell_df = mas_df[filter_mask]
# mas_sell_df is less than trend_search_count ? => SYTEM ERROR
if mas_sell_df.shape[0] < self.coin.trend_search_count: # < 3
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_sell_df({mas_sell_df.shape[0]}) is less than {self.coin.trend_search_count}. skip buy_sell() ")
self.debug.sound_alert(3)
return
self.debug.trace_write(f".... {self.gvar.callers_method_name} buy_sell_condition({self.gvar.buysell_condition}), mas_buy_df.shape({mas_buy_df.shape[0]}), mas_sell_df.shape({mas_sell_df.shape[0]}) ")
# mas_df:
# -------------------------------------------------------------
# # Column Dtype
# -------------------------------------------------------------
# 0 time datetime64[ns, UTC]
# 1 open float64
# 2 close float64
# 3 low float64
# 4 high float64
# 5 price float64
# 6 buy_price float64
# 7 sell_price float64
# 8 side object
# 9 size float64
# 10 symbol object
# ---------------------------------------------------------------
position = 'buy or sell'
### load buy order / buy order sub files
buy_df = self.csv.get_buy_order()
buy_sub_df = self.csv.get_buy_order_sub()
### load sell order / sell order sub files
sell_df = self.csv.get_sell_order()
sell_sub_df = self.csv.get_sell_order_sub()
### update the position depending on buy/sell sub order log files
### get the last buy sub order info
last_buy_time = buy_sub_df.iloc[-1]['buy_time']
last_sell_time = buy_sub_df.iloc[-1]['sell_time']
last_buy_real_qty = buy_sub_df.iloc[-1]['real_qty']
last_buy_real_price = buy_sub_df.iloc[-1]['real_price']
### get the last sell order (pair of the buy order)
# find the pair of the sell order
find_mask = sell_df['sell_time'] == last_sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
# self.debug.print_log(f".... {bcolors.WARNING}{self.gvar.callers_method_name}{bcolors.ENDC} ▶ sell order not found: {last_sell_time=:%Y-%m-%d %H:%M:%S} ")
last_sell_real_qty = 0
else: ### found pair of the sell order
# get sell order real_qty
ix = dfx.index[0]
last_sell_real_qty = dfx.loc[ix, 'real_qty']
# closed order ?
if round(last_buy_real_qty, _q_) == round(last_sell_real_qty, _q_):
position = 'buy' # sell order not required => position(BUY)
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}NEW BUY ORDER REQUEST{Bcolors.ENDC} ")
else: # not found sell order or incomplete sell order
return # skip return
# buy position ?
if position.startswith('buy'):
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}BUY{Bcolors.ENDC} position (long position) ")
### BUY position only
### check buy condition and create a buy order
# get the latest time & buy_price from GMO master dataframe
mas_time = mas_buy_df.iloc[-1]['time']
mas_price = mas_buy_df.iloc[-1]['price']
while True:
###########################################################
### 1: check duplicate buy order
###########################################################
# mas_time(gmo) is less than or equal to last_buy_time ? => SKIP
if mas_time <= last_buy_time:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(1){Bcolors.ENDC} mas_time({mas_time:%Y-%m-%d %H:%M:%S}) is less than or equal to last_buy_time({last_buy_time:%Y-%m-%d %H:%M:%S}) ")
break # exit while true loop
# duplicate buy order ? => SKIP
# find_order(df: pd.DataFrame, col: str, val=any) -> bool:
if self.trade.find_order(buy_df, col='buy_time', val=mas_time):
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(2){Bcolors.ENDC} duplicate buy order: buy_time={mas_time:%Y-%m-%d %H:%M:%S}) ")
break # exit while true loop
### no duplicate buy order
###########################################################
### 2: execute buy order (MARKET) & sell order (LIMIT)
###########################################################
qty = self.coin.qty
### execute buy order with qty (MARKET)
# create_buy_order(buy_time: utcnow, qty: float, price: float) -> int: # return status
status = self.create_buy_order(mas_time, qty, mas_price) # ★ price is used for fake mode: execute buy order with market price
if status == 0:
# reload buy order sub file
buy_sub_df = self.csv.get_buy_order_sub()
# get the last buy order sub info
last_buy_time = buy_sub_df.iloc[-1]['buy_time']
# get the latest sell price from gmo master
ms_price = mas_sell_df.iloc[-1]['price']
### execute sell order (LIMIT)
# create_sell_order(sell_time: utcnow, ms_price: float, buy_time: utcnow) -> int:
status = self.create_sell_order(mas_time, mas_price, last_buy_time)
break # exit while true loop
# end of while True:
return
####################################### buy_sell : algorithm 1 not implemented
def buy_sell_algorithm_1(self) -> None:
pass
####################################### buy_sell : algorithm 2 not implemented
def buy_sell_algorithm_2(self) -> None:
pass
####################################### buy_sell : algorithm 3 not implemented
def buy_sell_algorithm_3(self) -> None:
pass
図3はデモプログラムの実行結果です。
画面にはログ情報が表示されています。
BOTからBuySellクラスのbuy_sell(0)メソッドが呼ばれています。
さらにbuy_sell(0)からcreate_buy_order(), create_sell_order()メソッドが呼ばれています。
buy_sell(0)では一定のアルゴリズムに従って買いと売りの注文を出します。
このbuy_sell()がBOTの肝になる部分です。
buy_sell()には、「記事(Article108)」
で解説しているさまざまなテクニカルインジケーターを組み合わせて新規のメソッドを
「buy_sell_algorithm_1(), buy_sell_algorithm_2(),...」のようなメソッド名で追加します。
なお、デフォルトとして「buy_sell_algorithm_0()」メソッドが用意されています。
このメソッドはBOTをテストするときに使用します。
デモプログラムでは、デフォルトのアルゴリズムを使用しています。
-
BuySellクラスにcreate_buy_order()メソッドを追加する
ここではBuySellクラスにcreate_buy_order()メソッドを追加します。
このメソッドでは、新規の買い注文を成行きの指定で入れます。
成行き注文の場合、FAK(Fill and Kill」が適用されて、
全ての数量が執行されないで終了することがあります。
この場合、create_buy_order()では残数の数量に対して再度成行きの注文を入れます。
すべての数量が執行されたらコール元に戻ります。
buy_sell.py:
##########################################################################################
def create_buy_order(self, buy_time: dt.datetime.utcnow, qty: float, price: float) -> int: # return status : price (latest buy market price)
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'create_buy_order():'
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}) ")
status = 0
# reset buy/sell position count
self.coin.position_count_0_10 = 0
self.coin.position_count_11_30 = 0
self.coin.position_count_31_50 = 0
self.coin.position_count_51_70 = 0
self.coin.position_count_71_90 = 0
self.coin.position_count_91_999 = 0
################################################
### 【1】CHECK DUPLICATE BUY ORDER
################################################
### check duplicate buy order
# OPEN (load the buy order file) : buy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order()
# find a buy order
find_mask = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# duplicate buy order ?
if dfx.shape[0] > 0:
status = 9 # set internal status code
self.debug.trace_write(f".... {self.gvar.callers_method_name} duplicate buy order ({buy_time=:%Y-%m-%d %H:%M:%S}) ▶ {status=} error return... ")
return status # error return
# reset sell_position_list
self.coin.sell_position_list = []
#############################################################
### 【2】EXECUTE BUY ORDER MARKET (FAK: Fill And Kill)
#############################################################
### execute buy order with market price : FAK (Fill And Kill)
buy_qty = qty # save original buy qty
# define buy order header list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
status = 0
loop_count = 0
while True:
loop_count += 1
# exe_buy_order_market_wait(qty: float, price: float, latest_close_price=0.0) -> tuple:
status, res_df, msg_code, msg_str = self.api.exe_buy_order_market_wait(buy_qty, price) # ★ FAK (Fill And Kill) : price is used for fake mode
res_df = pd.DataFrame(res_df) # cast
self.debug.print_log(f".... {self.gvar.callers_method_name} while({loop_count}): exe_buy_order_market_wait() ▶ {status=}, {res_df.shape=} ")
####################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_buy_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug1:
# ★ create two buy order subs for each buy order header ★
# update_get_price_response_for_debug1_1(res_df: pd.DataFrame, loop_count: int) -> pd.DataFrame:
res_df = self.trade.update_get_price_response_for_debug1_1(res_df, loop_count)
# loop_count == 1: return 2 rows => add 2 buy order subs
# loop_count == 2: return 1 row => add 1 buy order sub
# end of if not self.coin.real_mode:
###################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_buy_order()
# exe_buy_order_market_wait() error ?
if status != 0:
if status == 1 and msg_code == 'ERR-201': # Trading margin is insufficient ?
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {Bcolors.FAIL}the trading margin is insufficient error: ERR-201{Bcolors.ENDC} ")
self.debug.sound_alert(2)
break # exit while true loop => error return
elif status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}, {price=:,.{_p_}f})): ▶ {Bcolors.FAIL}get_price() time out error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop => error return
else: # misc error
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}, {price=:,.{_p_}f})): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop => error return
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single row or multiple rows)
################################################################################################################
### 【3】ADD A BUY ORDER CHILD
################################################################################################################
# res_df = get_price() response for exe_buy_order_market_wait()
# ---------------------------------------------------------
# # Column Dtype
# ---------------------------------------------------------
# 0 executionId object
# 1 fee float64
# 2 lossGain float64
# 3 orderId object
# 4 positionId object ★
# 5 price float64
# 6 settleType object (OPEN or CLOSE)
# 7 side object (BUY or SELL)
# 8 size float64
# 9 symbol object (BTC_JPY)
# 10 timestamp datetime64[ns, UTC]
# ---------------------------------------------------------
### write a buy order child
# define buy order child list
buy_order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response df for exe_buy_order_market_wait(FAK)
for k in range(len(res_df)):
buy_order_id = res_df.loc[k, 'orderId'] # buy order id (same order id)
position_id = res_df.loc[k, 'positionId'] # position id (unique id)
real_qty = res_df.loc[k, 'size'] # real qty ★
real_price = res_df.loc[k, 'price'] # real price ★
real_fee = res_df.loc[k, 'fee'] # real fee
buy_order_id_list.append(buy_order_id) # B9999-999999-999999
position_id_list.append(position_id) # P9999-999999-999999
real_qty_list.append(real_qty) # 10, 10, 20 = 40
real_price_list.append(real_price) # XRP_JPY(64.470)
real_fee_list.append(real_fee) # zero(0)
# write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_sell_time_ = buy_time; _sell_order_id_ = 'Dummy'; _qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(buy_time, buy_order_id, _sell_time_, _sell_order_id_, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
first_buy_order_id = buy_order_id_list[0] # same order ids
position_ids_by_semicolon = '' # 'positionId1;positionId2;positionId3'
for k, position_id_x in enumerate(position_id_list):
if k == 0:
position_ids_by_semicolon += position_id_x
else:
position_ids_by_semicolon += ';' + position_id_x
sub_sum_real_qty = round(sum(real_qty_list), _q_) # XRP_JP(9999)
sub_sum_real_fee = round(sum(real_fee_list), 0) # zero(0)
if sum(real_price_list) > 0:
sub_mean_real_price = round(statistics.mean(real_price_list), _p_) # XRP_JPY(99.123)
else:
sub_mean_real_price = 0
header_real_qty_list.append(sub_sum_real_qty)
header_real_price_list.append(sub_mean_real_price)
header_real_fee_list.append(sub_sum_real_fee)
###################################################################################################################
### 【4】ADD A BUY ORDER SUB (buy_time, sell_time,...) ★ sell_time <= buy_time
###################################################################################################################
### write a buy order sub
# write_buy_order_sub(buy_time: utcnow, sell_time: utcnow, buy_order_id: str, sell_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_sell_time_ = buy_time; _sell_order_id_ = 'Dummy'; _qty_ = sub_sum_real_qty # positionId = 'positionId1;positionId2;positionId3'
self.csv.write_buy_order_sub(buy_time, _sell_time_, first_buy_order_id, _sell_order_id_, position_ids_by_semicolon, _qty_, price, sub_sum_real_qty, sub_mean_real_price, sub_sum_real_fee) # ★
### check exit condition
header_sum_real_qty = round(sum(header_real_qty_list), _q_) # XRP_JPY(9999)
# self.debug.trace_write(f".... {self.gvar.callers_method_name} while({loop_count}): buy_qty={buy_qty}, header_sum_real_qty={header_sum_real_qty} ")
# no outstanding buy_qty ?
if round(qty, _q_) == round(header_sum_real_qty, _q_):
# increment buy count
self.gvar.buy_count += 1
self.coin.buy_count += 1
break # exit while true loop
else: # outstanding buy_qty exists
pass # skip (do nothing)
# end of if round(buy_qty, _q_) == round(header_sum_real_qty, _q_):
### partial exe_buy_order_market_wait() was executed => continue to buy remaining qty
remaining_qty = buy_qty - header_sum_real_qty # calculate new buy qty
buy_qty = remaining_qty # update new buy qty
# continue exe_buy_order_market_wait() ★
# end of while True:
#################################################################################
### 【5】ADD A BUY ORDER HEADER (buy_time, sell_time) ★ sell_time <= buy_time
#################################################################################
### update buy order header(real_qty, real_price, real_fee)
header_sum_real_qty = round(sum(header_real_qty_list), _q_) # XRP_JPY(9999)
header_sum_real_fee = round(sum(header_real_fee_list), 0) # zero(0)
if sum(header_real_price_list) > 0:
header_mean_real_price = round(statistics.mean(header_real_price_list), _p_) # XRP_JPY(99.123)
else:
header_mean_real_price = 0
### write a buy order
# write_buy_order(buy_time: utcnow, sell_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_sell_time_ = buy_time
self.csv.write_buy_order(buy_time, _sell_time_, qty, price, header_sum_real_qty, header_mean_real_price, header_sum_real_fee)
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_order() ▶ {status=} normal return... ")
return status
図4-1はデモプログラムの実行結果です。
ここでは関数「auto_trade()」からBuySellクラスのbuy_sell(althorithm_id=0)メソッドを呼び出しています。
buy_sell()メソッドの引数に「althorithm_id=0」を指定しているので「アルゴリズム0」が適用されて買い・売りの取引を行います。
買いのタイミングになるとBuySellクラスのcreate_buy_order()メソッドを呼び出して成行きの買い注文を入れます。
図4-2はcreate_buy_order()で作成したCSVファイルをExcelで開いた画像です。
「buy_order(XRP_JPY).csv」ファイルには「buy_time, qty, price, real_qty, real_price,...」などが格納されています。
「real_qty, real_price」には約定したときの数量と価格(レート)が格納されます。
ここでは数量(10XRP_JPY)を注文しています。
「buy_order_sub(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, qty, price, real_qty, real_price,...」などが格納されています。
「position_id」のポジションIDは売り注文(買いポジションの決済)を入れるときに使います。買い注文は成行きなので通常すぐに約定します。
「but_order_child(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, qty, price, reqal_qty, real_price,...」などが格納されています。
買い注文のCSVファイルがなぜ3段階に階層化されているかと言えば、注文が約定されるとき数量が分割されるからです。
たとえば、数量「100XRP_JPY」を成行きで注文すると「20, 30, 50」のように分割されて約定されることがあります。
さらに、成行き注文のときは(FAK: Fill and Kill)が適用されて全数約定しないことがあります。
このような場合、create_buy_order()は残数の成行き注文を再度入れて全数約定するようにしています。
たとえば、数量「100」を成行きで注文して「70」だけ約定したときは、再度「30」で成行き注文を入れます。
この場合、1回の買い注文で2個の注文IDが生成されることになります。
こういった理由で、買い注文のCSVファイルは3階層になっています。
ちなみに、売り注文のCSVファイルも同様に3階層になっています。
-
BuySellクラスにcreate_sell_order()メソッドを追加する
ここではBuySellクラスにcreate_sell_order()メソッドを追加します。
このメソッドは前出のcreate_buy_order()で買った仮想通貨を決済するために、
指値で売り注文を入れます。
指値の場合、成行きの注文と異なり「Fill and Store」が適用されます。
したがって、成行きのように途中で注文が終了することはありません。
buy_sell.py:
#################################################################################################################
def create_sell_order(self, sell_time: dt.datetime.utcnow, ms_price: float, buy_time: dt.datetime.utcnow) -> int: # return status : ms_price (latest market sell price)
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'create_sell_order():'
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, {ms_price=:,.{_p_}f}, {buy_time=:%Y-%m-%d %H:%M:%S}) ")
status = 0
################################################
### 【1】CHECK DUPLICATE SELL ORDER
################################################
### check duplicate sell order
# get the sell order : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order()
# find a sell order
find_mask = sell_df['sell_time'] == sell_time # PK(sell_time)
dfx = sell_df[find_mask]
# duplicate sell order ?
if dfx.shape[0] > 0:
status = 9 # set internal status code
self.debug.trace_write(f".... {self.gvar.callers_method_name} duplicate sell order ({sell_time=:%Y-%m-%d %H:%M:%S}) ▶ {status=} error return... ")
return status # error return
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub()
### OPEN (load the buy order child csv file ) : buy_order_child(BTC_JPY).csv
buy_ch_df = self.csv.get_buy_order_child()
######################################################################
### 【2】READ ALL BUY ORDER SUBs (filter by buy_time)
######################################################################
# define sell order sub list
sub_qty_list = []
sub_price_list = []
# filter the buy order sub by buy_time
filter_mask = buy_sub_df['buy_time'] == buy_time
buy_subx_df = buy_sub_df[filter_mask]
if buy_subx_df.shape[0] > 0:
buy_subx_df.reset_index(inplace=True)
# get all buy order sub filtered by buy_time
for i in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[i, 'buy_time']
sub_buy_order_id = buy_subx_df.loc[i, 'buy_order_id']
sub_qty = buy_subx_df.loc[i, 'qty']
sub_price = buy_subx_df.loc[i, 'price']
sub_real_qty = buy_subx_df.loc[i, 'real_qty']
sub_real_price = buy_subx_df.loc[i, 'real_price']
sub_real_fee = buy_subx_df.loc[i, 'real_fee']
#################################################################################
### 【3】READ ALL BUY ORDER CHILD (filter by buy_time + buy_order_id)
#################################################################################
# define sell order child list
ch_buy_order_id_list = []
ch_sell_order_id_list = []
ch_position_id_list = []
ch_qty_list = []
ch_price_list = []
### filter the buy order child by buy_time + buy_order_id
# buy order child is empty ?
if buy_ch_df.shape[0] == 0:
buy_chx_df = pd.DataFrame()
else: # filter buy order child
filter_mask = (buy_ch_df['buy_time'] == buy_time) & (buy_ch_df['buy_order_id'] == sub_buy_order_id)
buy_chx_df = buy_ch_df[filter_mask]
if buy_chx_df.shape[0] > 0:
buy_chx_df.reset_index(inplace=True)
# get all buy order child filtered by buy_time + buy_order_id
for j in range(len(buy_chx_df)): # ★ buy_chx_df ★
### get buy order child info
ch_buy_time = buy_chx_df.loc[j, 'buy_time']
ch_buy_order_id = buy_chx_df.loc[j, 'buy_order_id']
ch_position_id = buy_chx_df.loc[j, 'position_id']
ch_qty = buy_chx_df.loc[j, 'qty']
ch_price = buy_chx_df.loc[j, 'price']
ch_real_qty = buy_chx_df.loc[j, 'real_qty']
ch_real_price = buy_chx_df.loc[j, 'real_price']
ch_real_fee = buy_chx_df.loc[j, 'real_fee']
#############################################################################################
### 【4】EXECUTE SELL CLOSE ORDER LIMIT (FAS:Fill And Store) FOR EACH BUY ORDER CHILD
#############################################################################################
sell_qty = ch_real_qty
sell_price = self.trade.get_sell_price(ch_real_price, trace=True)
# market price is greater than sell price
if ms_price > sell_price:
sell_price = ms_price
### execute a sell close order with limit(FAS)
# exe_sell_close_order_limit(position_id: str, qty: float, price: float) -> float:
status, res_sell_order_id, msg_code, msg_str = self.api.exe_sell_close_order_limit(ch_position_id, sell_qty, sell_price)
# res_sell_order_id => 'S9999-999999-999999'
# exe_sell_close_order_limit() error ?
if status != 0:
if status == 1 and msg_code == 'ERR-208': # Exceeds the available balance
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}the available balance exceeded ERR-208: continue...{Bcolors.ENDC} ")
self.debug.sound_alert(2)
continue # continue to next buy order child
else: # misc error
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
self.debug.sound_alert(2)
continue # continue to next buy order child
# end of if status != 0:
### no error for exe_sell_close_order_limit()
################################################################################################################
### 【5】ADD A SELL ORDER SUB
################################################################################################################
### write a sell order sub
# write_sell_order_sub(sell_time: utcnow, buy_time: utcnow, sell_order_id: str, buy_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order_sub(sell_time, buy_time, res_sell_order_id, ch_buy_order_id, ch_position_id, sell_qty, sell_price)
ch_buy_order_id_list.append(ch_buy_order_id)
ch_sell_order_id_list.append(res_sell_order_id)
ch_position_id_list.append(ch_position_id)
ch_qty_list.append(sell_qty)
ch_price_list.append(sell_price)
# continue to next buy order child
# end of for j in range(len(buy_chx_df)):
ch_sum_qty = round(sum(ch_qty_list), _q_)
if sum(ch_price_list) > 0:
ch_mean_price = round(statistics.mean(ch_price_list), _p_)
else:
ch_mean_price = 0
sub_qty_list.append(ch_sum_qty)
sub_price_list.append(ch_mean_price)
#####################################################################################
### 【6】UPDATE BUY ORDER SUB (sell_order_id, sell_time)
#####################################################################################
sell_order_ids_by_semicolon = '' # 'SellOrderID1;SellOrderID2;SellOrderID3'
for k, sell_order_id_x in enumerate(ch_sell_order_id_list):
if k == 0:
sell_order_ids_by_semicolon += sell_order_id_x
else:
sell_order_ids_by_semicolon += ';' + sell_order_id_x
### update buy order sub (sell_order_id, sell_time)
find_mask = (buy_sub_df['buy_time'] == buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}(): {Bcolors.FAIL} buy order sub not found ▶ {sub_buy_order_id=} {Bcolors.ENDC} ")
else: # found buy order sub => update sell_order_id, sell_time
buy_sub_df.loc[find_mask, 'sell_order_id'] = sell_order_ids_by_semicolon # 'SellOrderID1;SellOrderID2;SellOrderID3'
buy_sub_df.loc[find_mask, 'sell_time'] = sell_time
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({buy_time=:%Y-%m-%d %H:%M:%S}, {sub_buy_order_id=}, {sell_order_ids_by_semicolon=}, {sell_time=:%Y-%m-%d %H:%M:%S}) ")
# continue to next buy order sub
# end of for i in range(len(buy_subx_df)):
#################################################################################
### 【7】SAVE BUY ORDER SUB TO CSV FILE
#################################################################################
### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
################################################################################
### 【8】ADD A SELL ORDER
################################################################################
### update sell order (real_qty, real_price, real_fee)
sub_sum_qty = round(sum(sub_qty_list), _q_)
if sum(sub_price_list) > 0:
sub_mean_price = round(statistics.mean(sub_price_list), _p_)
else:
sub_mean_price = 0
# write_sell_order(sell_time: utcnow, buy_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order(sell_time, buy_time, sub_sum_qty, sub_mean_price)
################################################################################
### 【9】UPDATE A BUY ORDER (sell_time)
################################################################################
# update_buy_order(buy_time: utcnow, col_name_list: list, col_value_list: list) -> None: # PK(buy_time)
self.csv.update_buy_order(buy_time, col_name_list=['sell_time'], col_value_list=[sell_time])
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_sell_order() ▶ {status=} normal return... ")
return status
図5-1はデモプログラムの実行結果です。
ここでは関数「auto_trade()」からBuySellクラスのbuy_sell(althorithm_id=0)メソッドを呼び出しています。
buy_sell()メソッドの引数に「althorithm_id=0」を指定しているの「アルゴリズム0」が適用されて買い・売りの取引を行います。
売りのタイミングになるとBuySellクラスのcreate_sell_order()メソッドを呼び出して指値の売り注文(買いポジションの決済)を入れます。
つまり、前出のcreate_buy_order()メソッドで注文した買いポジションを決済しています。
画面に表示されているログから分かるように内部的には、
Apiクラスのexe_sell_close_order_limit()メソッドを呼び出して指値の売り注文を入れています。
図5-2はcreate_sell_order()メソッドで作成した売り注文のCSVファイルをExcelで開いた画像です。
「sell_order(XRP_JPY).csv」ファイルには「sell_time, qty, price, real_qty, real_price,...」などが格納されています。
「qty, price」は指値で売り注文を入れたときの数量と価格です。
「real_qty, real_price」は注文が約定したときの数量、価格(レート)です。
この状態ではまだ約定していないので双方とも「0」になっています。
「sell_order_sub(XRP_JPY).csv」ファイルには「sell_time, sell_order_id, position_id, qty, price, real_qty, real_price, buy_order_id,..」
などが格納されています。
「real_qty, real_price」には約定したときの数量と価格(レート)が格納されます。
このファイルには対応する買い注文の注文ID(buy_order_id)も格納されます。
実際に約定したときは、注文数が分割されることがあるので「sell_order_child(XRP_JPY).csv」ファイルを作成します。
-
BuySellクラスにupdate_order(), update_closed_order(), update_partially_closed_order()メソッドを追加する
ここではBuySellクラスにupdate_order()、update_partially_closed_order()メソッドを追加します。
update_order()は親メソッドで、update_partially_closed_order()は子メソッドです。
update_order()からは、
update_closed_order(), update_partially_closed_order(), update_order_profit(),
stop_loss(), cancel_pending_order(), close_pending_order()などのメソッドも呼びます。
update_partially_closed_order()では、
買いと売りの取引が完了した注文のCSVファイルを更新して保存します。
具体的には約定したときの数量(執行数量)、金額(レート)等を更新します。
buy_sell.py:
###############################
def update_order(self) -> None:
"""
"""
########################################################################
### 【1】UPDATE CLOSED SELL ORDER
########################################################################
### update closed sell order
self.update_closed_order()
########################################################################
### 【2】UPDATE ORDER PROFIT (PART) : first_pass(True)
########################################################################
### update order profit
self.update_order_profit(first_pass=True)
########################################################################
### 【3】CHECK STOP LOSS : gvar.stoploss_method_id : 0, 1,...
########################################################################
### check latest price(GMO) and stop loss price
if self.gvar.stop_loss:
self.stop_loss(self.gvar.stoploss_method_id) # 0, 1,...
########################################################################
### 【4】CANCEL PENDING ORDERS OR CLOSE PENDING ORDERS
########################################################################
### cancel pending orders
# close pending orders request & cancel pending order ? => call cancel_pending_orders() method
if self.gvar.cancel_pending_orders and self.gvar.close_pending_orders_request:
self.cancel_pending_order()
elif self.coin.cancel_pending_orders: # cancel pending order by coin (one time request)
self.cancel_pending_order()
elif self.gvar.close_pending_orders: # close pending orders (current time is between 05:00AM - 06:00AM)
self.close_pending_order()
########################################################################
### 【5】 UPDATE ORDER PROFIT (PART2) : first_pass(False)
########################################################################
### update the order file ( order(BTC).csv )
if self.gvar.stop_loss or self.gvar.cancel_pending_orders:
self.update_order_profit(first_pass=False)
elif self.coin.cancel_pending_orders: # cancel pending order by coin (one time request)
self.update_order_profit(first_pass=False)
elif self.gvar.close_pending_orders: # close pending orders (current time is between 05:00AM - 06:00AM)
self.update_order_profit(first_pass=False)
return
######################################
def update_closed_order(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'update_closed_order():'
order_completed = self.trade.get_order_status(exclude=False) # include stop_loss, cancelled, closed order info
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast df (master dataframe)
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}last order completed({order_completed}){Bcolors.ENDC}, mas_df.shape({mas_df.shape[0]}), close({self.gvar.close_pending_orders_request}), cancel({self.gvar.cancel_pending_orders}) ")
# mas_df is empty ? => SYTEM ERROR
if mas_df.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_df({mas_df.shape[0]}) is empty. skip update_order() ")
self.debug.sound_alert(3)
return
### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order() # PK(buy_time)
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
# get_orderbooks_try() -> tuple: # return (sell_df, buy_df)
sell_bk, buy_bk = self.api.get_orderbooks_try()
# sleep(0.5)
sell_bk_df = pd.DataFrame(sell_bk) # ascending order of price [-1, price] : low to high price 100, 110, 120, 130, 140, 150
buy_bk_df = pd.DataFrame(buy_bk) # descending order of price iloc[0, price] : high to low price 150, 140, 130, 120, 110, 100
# not empty order books ?
if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:
# get latest sell/buy price from the order books
bk_sell_price = sell_bk_df.iloc[0]['price'] # get lowest sell price : 100
bk_buy_price = buy_bk_df.iloc[0]['price'] # get highest buy price : 150
else:
bk_sell_price = 0.0
bk_buy_price = 0.0
# incomplete sell order (pending sell order) ?
if not order_completed:
### print GMO orderbooks info (sell price, buy price)
# # get_orderbooks_try() -> tuple: # return (sell_df, buy_df)
# sell_bk, buy_bk = self.api.get_orderbooks_try()
# sleep(0.5)
# sell_bk_df = pd.DataFrame(sell_bk) # ascending order of price [-1, price] : low to high price 100, 110, 120, 130, 140, 150
# buy_bk_df = pd.DataFrame(buy_bk) # descending order of price iloc[0, price] : high to low price 150, 140, 130, 120, 110, 100
# not empty order books ?
if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:
# get latest sell/buy price from the order books
bk_sell_price = sell_bk_df.iloc[0]['price'] # get lowest sell price : 100
bk_buy_price = buy_bk_df.iloc[0]['price'] # get highest buy price : 150
# get the last sell price from the sell order sub
last_sell_price = sell_sub_df.iloc[-1]['price'] # 120
# get sell position number (buy => sell)
filter_mask = sell_bk_df['price'] < last_sell_price # < 120
dfx = sell_bk_df[filter_mask] # 100, 110
sell_position = dfx.shape[0] # 2
# update sell position count
if sell_position >= 91:
self.coin.position_count_91_999 += 1
elif sell_position >= 71:
self.coin.position_count_71_90 += 1
elif sell_position >= 51:
self.coin.position_count_51_70 += 1
elif sell_position >= 31:
self.coin.position_count_31_50 += 1
elif sell_position >= 11:
self.coin.position_count_11_30 += 1
else:
self.coin.position_count_0_10 += 1
self.coin.sell_position_list.append(sell_position) # append sell_position : coin.sell_position=[10, 8, 5, 3, 1, 0]
self.debug.trace_write(f".... {self.gvar.callers_method_name}{Bcolors.WARNING} {sell_position=}{Bcolors.ENDC} => close condition [{last_sell_price=:,.{_p_}f} <= {bk_buy_price=:,.{_p_}f}] ") # 120 <= 150
reverse_sell_position_list = self.coin.sell_position_list[::-1] # [0, 1, 3, 5, 8, 10]
if len(reverse_sell_position_list) < 21:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_sell_position_list=} ")
else:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_sell_position_list[0:21]=} ")
else: # empty order books
self.coin.sell_position_list = [] # reset sell position list
# end of if not order_completed:
#################################################
### 【1】READ ALL SELL ORDER
#################################################
### get all sell order
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # unique time
buy_time = sell_df.loc[i, 'buy_time'] # unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell order sub
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell order sub ?
if dfx.shape[0] > 0:
dfx.reset_index(inplace=True)
sell_order_id = str(dfx.loc[0, 'sell_order_id'])
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_order_id}) has been closed: sell_order_closed={sell_order_closed} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_time:%Y-%m-%d %H:%M:%S}) has been closed: sell_order_closed={sell_order_closed} ")
# end of if dfx.shape[0] > 0:
continue # continue to next sell order
# end of if sell_order_closed:
### open or incomplete sell order
##########################################################################################
### 【2】READ ALL SELL ORDER SUB & EXECUTE GET_PRICE() FOR EACH SELL ORDER SUB
##########################################################################################
### for sell order sub : use pandas dataframe groupby()
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order : 2 rows
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
sell_order_sub_seq = j + 1 # 1, 2, 3,....
### get sell order sub info
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★ (unique id) used for get_price() api
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★ (non unique id)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # (unique id)
sub_sell_qty = sell_subx_df.loc[j, 'qty'] # 10
sub_sell_price = sell_subx_df.loc[j, 'price']
sub_sell_real_qty = sell_subx_df.loc[j, 'real_qty'] # 10
sub_sell_real_price = sell_subx_df.loc[j, 'real_price']
sub_sell_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_sell_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_sell_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_sell_order_open = False
sub_sell_order_closed = False
sub_sell_order_incomplete = False
if sub_sell_real_qty == 0:
sub_sell_order_open = True
elif sub_sell_qty == sub_sell_real_qty:
sub_sell_order_closed = True
else:
sub_sell_order_incomplete = True
# fake sell sub order ? => SKIP
if sub_sell_order_id.startswith('Fake'):
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id})... ")
continue
# cancelled sell sub order ? => SKIP
if sub_sell_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled ▶ sub_sell_cancelled={sub_sell_cancelled} ")
continue
# stop loss sell sub order ? => SKIP
if sub_sell_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss ▶ sub_sell_stop_loss={sub_sell_stop_loss} ")
continue
# closed sell sub order ? => SKIP
if sub_sell_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed ▶ sub_sell_order_closed={sub_sell_order_closed} ")
continue
### open or incomplete sell order sub
############################################################
### 【3】UPDATE SELL ORDER SUB (get_price_count+1)
############################################################
### update sell order sub (get_price_count += 1) ★
# DO NOT USE: self.csv.update_sell_order_sub_count(sell_time, sell_order_id)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub_count({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub
sell_sub_df.loc[find_mask, 'get_price_count'] += 1 # increment get_price_count by 1
ix = dfx.index[0]
sub_sell_get_price_count = sell_sub_df.loc[ix, 'get_price_count']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub_count({sub_sell_order_id=}, {sub_sell_get_price_count=}) ")
# end of if dfx.shape[0] == 0:
### get order_status
qty = sub_sell_qty
price = sub_sell_price
# get_order_status_try(order_id: str, qty: float, price: float) ->tuple: # return (status, order_status)
status, order_status = self.api.get_order_status_try(sub_sell_order_id, qty, price) # ★ qty, price are used for fake mode only
# status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)
### status = 0; order_status = 'EXPIRED' # DEBUG DEBUG DEBUG
# get_order_status() error ?
if status != 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")
continue # continue to next sell order sub
### get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
# pending exe_sell_close_order_limit() request ? : ORDERED(指値注文有効中), EXECUTE(全量約定)
if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status_try({sub_sell_order_id}, {qty:,.{_q_}f}, {price:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_sell_close_order_limit() request ")
continue # continue to next sell order sub
### executed exe_sell_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'
if order_status in 'EXPIRED,CANCELED':
### update sell order, sell order sub and order csv file
if order_status == 'CANCELED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}): {Bcolors.FAIL}the order has been cancelled by GMO. please confirm the reason{Bcolors.ENDC} ")
self.debug.sound_alert(3)
continue # continue to next sell order sub
if order_status == 'EXPIRED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")
self.debug.sound_alert(3)
### close sell order sub
# update a sell order sub (update real_qty, real_price, stop_loss)
# DO NOT USE: self.csv.update_sell_order_sub(sell_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update sell order sub (real_qty, real_price, real_fee, stop_loss)
sell_sub_df.loc[find_mask, 'real_qty'] = sub_sell_qty
sell_sub_df.loc[find_mask, 'real_price'] = 9_999_999.0 # gmo loss cut rate
sell_sub_df.loc[find_mask, 'real_fee'] = 0.0
sell_sub_df.loc[find_mask, 'stop_loss'] = True
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, real_qty={sub_sell_qty:,.{_q_}f}, real_price={9_999_999.0:,.{_p_}f}, real_fee={0.0:,.0f}, stop_loss=True) ")
# end of if dfx.shape[0] == 0:
### close sell order
# update sell order (real_qty, real_price, real_fee)
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL} ({sell_time=:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order
sell_df.loc[find_mask, 'real_qty'] = sell_qty
sell_df.loc[find_mask, 'real_price'] = 9_999_999.0 # gmo loss cut rate
sell_df.loc[find_mask, 'real_fee'] = 0.0
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={sell_qty:,.{_q_}f}, real_price={9_999_999.0:,.{_p_}f}, real_fee={0.0:,.0f}) ")
# end of if dfx.shape[0] == 0:
### update order (update stop_loss)
# update_order_csv_buy(buy_time: utcnow, buy_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(buy_time + buy_order_id)
self.csv.update_order_csv_buy(sub_buy_time, sub_buy_order_id, col_name_list=['stop_loss'], col_value_list=[True])
continue # continue to next sell order sub
# end of if order_status == 'EXPIRED':
# end of if order_status in 'EXPIRED,CANCELED':
### get highest buy price from buy orderbooks
latest_book_price = bk_buy_price # bk_buy_price = buy_bk_df.iloc[0]['price']
# ### get real qty, sell price, fee from the GMO
# ### get the latest close price from the GMO master
# ### mas_latest_close_price = mas_df.iloc[-1]['close'] # get latest close price (GMO)
_debug1_ = self.coin.debug_mode_debug1; _debug2_ = self.coin.debug_mode_debug2
######################################################################################### DEBUG(1), DEBUG(2)
# _debug1_ = True; _debug2_ = True # ★ TEMP TEMP TEMP ★
# self.debug.trace_warn(f".... DEBUG _debug1_ = True; _debug2_ = True ")
######################################################################################### DEBUG(1), DEBUG(2)
### get sell real qty, price & fee from the GMO : get_price() for exe_sell_close_order_limit() ★
sleep(1) # sleep 1 sec
# get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:
status, res_df, msg_code, msg_str = self.api.get_price_try(sub_sell_order_id, sub_sell_qty, sub_sell_price, latest_book_price, _debug1_, _debug2_) # ★ qty, price, close_price are used for fake mode
# status: 0(ok), 8(retry error), 9(empty dataframe or internal error), ?(misc error)
sleep(1) # sleep 1 sec
res_df = pd.DataFrame(res_df) # cast dataframe
# not real mode and debug (stop loss or cancel pending orders) ?
######################################################################################### DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug1:
if res_df.shape[0] > 0:
# ★ create two sell order subs for each sell order header ★
# update_get_price_response_for_debug1_2(res_df: pd.DataFrame, sell_order_sub_seq: int) -> pd.DataFrame
res_df = self.trade.update_get_price_response_for_debug1_2(res_df, sell_order_sub_seq) # 1, 2, 3, ...
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({sell_order_sub_seq=}) ▶ {res_df.shape=} ")
######################################################################################## DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
####################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug2:
# ★ create outstanding sell order sub depending on get_price_count ★
# get sell 'get_price_count'
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# found sell order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
get_price_count = sell_sub_df.loc[ix, 'get_price_count'] # sell_sub_df.loc[find_mask, 'get_price_count'].values[0] # 0,1,2,3,4,5,...
# update_get_price_response_for_debug2(res_df: pd.DataFrame, get_price_count: int) -> pd.DataFrame:
res_df = self.trade.update_get_price_response_for_debug2(res_df, get_price_count)
if res_df.shape[0] == 0:
status = 9 # set internal status code
msg_code = 'ERR-999' # null => ERR-999
msg_str = 'DataFrame is empty' # null => DataFrame is empty
else:
status = 0
msg_code = 'OK'
msg_str = 'Normal Return'
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug2({get_price_count=}) ▶ {status=}, {res_df.shape=} ")
###################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
# get_price() error ?
if status != 0:
if status == 9: # pending exe_sell_close_order_limit() request ?
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_sell_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_sell_close_order_limit() request: continue to next sell order sub... ")
status = 0 # reset status code
continue # continue to next sell order sub
else: # status == 1 'ERR-5008' Timestamp for this request is too late
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_price({sub_sell_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
continue # continue to next sell order sub
# end of if status == 9:
else: # normal return => executed exe_sell_close_order_limit() request
pass # self.debug.sound_alert(1)
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
####################################################################
### 【4】UPDATE A SELL ORDER SUB (real_qty, real_price, real_fee)
####################################################################
# res_df = get_price() response for exe_sell_close_order_limit()
# ----------------------------------------------------------------
# # Column Dtype
# ----------------------------------------------------------------
# 0 executionId object
# 1 fee float64
# 2 lossGain float64
# 3 orderId object (non unique id => same id)
# 4 positionId object (unique id)
# 5 price float64
# 6 settleType object (OPEN or CLOSE)
# 7 side object (BUY or SELL)
# 8 size float64
# 9 symbol object (BTC_JPY)
# 10 timestamp datetime64[ns, UTC]
# ----------------------------------------------------------------
### update a sell order sub ★
sub_sum_real_qty = round(res_df.groupby('orderId')['size'].sum().values[0], _q_) # round(999.12, _q_)
sub_sum_real_fee = round(res_df.groupby('orderId')['fee'].sum().values[0], 0) # zero(0)
sub_mean_real_price = round(res_df.groupby('orderId')['price'].mean().values[0], _p_) # round(999999.123, _p_)
# DO NOT USED: self.csv.update_sell_order_sub(sell_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update sell order sub (real_qty, real_price, real_fee)
sell_sub_df.loc[find_mask, 'real_qty'] = sub_sum_real_qty
sell_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price
sell_sub_df.loc[find_mask, 'real_fee'] = sub_sum_real_fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, real_qty={sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={sub_sum_real_fee:,.0f}) ")
# end of if dfx.shape[0] == 0:
##############################################################################################################################
### 【5】ADD A SELL ORDER CHILD (sell_time, sell_order_id, real_qty, real_price, real_fee) + (buy_time, buy_order_id)
##############################################################################################################################
### check exit condition : sub_sell_qty=sub_sell_qty, sub_sum_real_qty=sum(res_df['size'])
# ######################################### TEMP TEMP TEMP
# temp_sell_qty = round(sub_sell_qty, _q_)
# temp_real_qty = round(sub_sum_real_qty, _q_)
# self.debug.trace_warn(f".... DEBUG: {temp_sell_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
# ######################################## TEMP TEMP TEMP
# completed exe_sell_close_order_limit() request ?
if round(sub_sell_qty, _q_) == round(sub_sum_real_qty, _q_):
# changed open => closed
if self.coin.real_mode:
self.debug.sound_alert(1)
# increment sell count
self.gvar.sell_count += 1
self.coin.sell_count += 1
# response df for get_price(): exe_sell_close_order_limit()
for k in range(len(res_df)):
sell_order_id = res_df.loc[k, 'orderId'] # sell order id (unique id)
position_id = res_df.loc[k, 'positionId'] # position id (non unique id => same id)
real_qty = res_df.loc[k, 'size'] # real qty
real_price = res_df.loc[k, 'price'] # real price
real_fee = res_df.loc[k, 'fee'] # real fee
if not self.coin.real_mode:
position_id = sub_position_id
### write a sell order child ★
# write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sub_sell_time, sell_order_id, sub_buy_time, sub_buy_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
# ### update sell order sub (bought=True) ★ DO NOT USE update_sell_order_sub() : SUSPEND SUSPEND SUSPEND
else: # outstanding sell_qty exists => incomplete status (sell order / sell order sub will be updated by update_order()
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} incomplete sell order sub({sub_sell_order_id}) ▶ sell_qty={sub_sell_qty:,.{_q_}f}, sell_real_qty={sub_sum_real_qty:,.{_q_}f} ")
# pass # skip (do nothing)
# end of if sub_sell_qty == total_real_qty:
# continue to next sell order sub
# end of for j in range(len(sell_subx_df)):
#################################################################################
### 【6】SAVE BUY/SELL ORDER SUB TO CSV FILES
#################################################################################
### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【7】UPDATE SELL ORDER (real_qty, real_price, real_fee)
#################################################################################
### calculate total sell qty / fee and mean price ★ sell_time ★
filter_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[filter_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL} sell order sub({sell_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub
header_sum_real_qty = dfx.groupby('sell_time')['real_qty'].sum().values[0]
header_sum_real_fee = dfx.groupby('sell_time')['real_fee'].sum().values[0]
header_mean_real_price = dfx.groupby('sell_time')['real_price'].mean().values[0]
header_sum_real_qty = round(header_sum_real_qty, _q_) # round(990.12, _q_)
header_sum_real_fee = round(header_sum_real_fee, 0) # zero(0)
header_mean_real_price = round(header_mean_real_price, _p_) # round(999999.123, _p_)
### update sell order (real_qty, real_price, real_fee) ★
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL} sell order({sell_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order => update
sell_df.loc[find_mask, 'real_qty'] = header_sum_real_qty
sell_df.loc[find_mask, 'real_price'] = header_mean_real_price
sell_df.loc[find_mask, 'real_fee'] = header_sum_real_fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={header_sum_real_fee:,.0f}) ")
# end of if dfx.shape[0] == 0:
# end of if dfx.shape[0] == 0:
# continue to next sell order... ★
# end of for i in range(len(sell_df)):
#################################################################################
### 【8】SAVE BUY/SELL ORDER TO CSV FILES
#################################################################################
### CLOSE (save the buy order csv file) : buy_order(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order(BTC_JPY).csv
buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order csv file) : sell_order(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order({symbol}).csv'
# desktop-pc/bot//buy_sell/sell_order(BTC_JPY).csv
sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
return
################################################
def update_partially_closed_order(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = ':: update_partially_closed_order():'
order_completed = self.trade.get_order_status(exclude=False) # include stop_loss, cancelled, closed order info
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast df (master dataframe)
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}last order completed({order_completed}){Bcolors.ENDC}, mas_df.shape({mas_df.shape[0]}), close({self.gvar.close_pending_orders_request}), cancel({self.gvar.cancel_pending_orders}) ")
# mas_df is empty ? => SYTEM ERROR
if mas_df.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_df({mas_df.shape[0]}) is empty. skip update_order() ")
self.debug.sound_alert(3)
return
# ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
# buy_df = self.csv.get_buy_order() # PK(buy_time)
# ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
# buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
# get_orderbooks_try() -> tuple: # return (sell_df, buy_df)
sell_bk, buy_bk = self.api.get_orderbooks_try()
# sleep(0.5)
sell_bk_df = pd.DataFrame(sell_bk) # ascending order of price [-1, price] : low to high price 100, 110, 120, 130, 140, 150
buy_bk_df = pd.DataFrame(buy_bk) # descending order of price iloc[0, price] : high to low price 150, 140, 130, 120, 110, 100
# not empty order books ?
if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:
# get latest sell/buy price from the order books
bk_sell_price = sell_bk_df.iloc[0]['price'] # get lowest sell price : 100
bk_buy_price = buy_bk_df.iloc[0]['price'] # get highest buy price : 150
else:
bk_sell_price = 0.0
bk_buy_price = 0.0
#################################################
### 【1】READ ALL SELL ORDER
#################################################
### get all sell order
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # unique time
buy_time = sell_df.loc[i, 'buy_time'] # unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell order sub
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell order sub ?
if dfx.shape[0] > 0:
dfx.reset_index(inplace=True)
sell_order_id = str(dfx.loc[0, 'sell_order_id'])
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_order_id}) has been closed: sell_order_closed={sell_order_closed} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_time:%Y-%m-%d %H:%M:%S}) has been closed: sell_order_closed={sell_order_closed} ")
# end of if dfx.shape[0] > 0:
continue # continue to next sell order
# end of if sell_order_closed:
### open or incomplete sell order
##########################################################################################
### 【2】READ ALL SELL ORDER SUB & EXECUTE GET_PRICE() FOR EACH SELL ORDER SUB
##########################################################################################
### for sell order sub : use pandas dataframe groupby()
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order : 2 rows
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
sell_order_sub_seq = j + 1 # 1, 2, 3,....
### get sell order sub info
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★ (unique id) used for get_price() api
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★ (non unique id)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # (unique id)
sub_sell_qty = sell_subx_df.loc[j, 'qty'] # 10
sub_sell_price = sell_subx_df.loc[j, 'price']
sub_sell_real_qty = sell_subx_df.loc[j, 'real_qty'] # 10
sub_sell_real_price = sell_subx_df.loc[j, 'real_price']
sub_sell_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_sell_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_sell_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_sell_order_open = False
sub_sell_order_closed = False
sub_sell_order_incomplete = False
if sub_sell_real_qty == 0:
sub_sell_order_open = True
elif sub_sell_qty == sub_sell_real_qty:
sub_sell_order_closed = True
else:
sub_sell_order_incomplete = True
# fake sell sub order ? => SKIP
if sub_sell_order_id.startswith('Fake'):
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id})... ")
continue
# cancelled sell sub order ? => SKIP
if sub_sell_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled ▶ sub_sell_cancelled={sub_sell_cancelled} ")
continue
# stop loss sell sub order ? => SKIP
if sub_sell_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss ▶ sub_sell_stop_loss={sub_sell_stop_loss} ")
continue
# closed sell sub order ? => SKIP
if sub_sell_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed ▶ sub_sell_order_closed={sub_sell_order_closed} ")
continue
### open or incomplete sell order sub
##########################################################################################
### 【3】GET ORDER STATUS
##########################################################################################
### get order_status
qty = sub_sell_qty
price = sub_sell_price
# get_order_status_try(order_id: str, qty: float, price: float) ->tuple: # return (status, order_status)
status, order_status = self.api.get_order_status_try(sub_sell_order_id, qty, price) # ★ qty, price are used for fake mode only
# status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)
### status = 0; order_status = 'EXPIRED' # DEBUG DEBUG DEBUG
# get_order_status() error ?
if status != 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")
continue # continue to next sell order sub
### get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
# pending exe_sell_close_order_limit() request ? : ORDERED(指値注文有効中), EXECUTE(全量約定)
if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status_try({sub_sell_order_id}, {qty:,.{_q_}f}, {price:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_sell_close_order_limit() request ")
continue # continue to next sell order sub
### executed exe_sell_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'
if order_status in 'EXPIRED,CANCELED':
### update sell order, sell order sub and order csv file
if order_status == 'CANCELED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}): {Bcolors.FAIL}the order has been cancelled by GMO. please confirm the reason{Bcolors.ENDC} ")
self.debug.sound_alert(3)
continue # continue to next sell order sub
if order_status == 'EXPIRED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")
self.debug.sound_alert(3)
### close sell order sub
# update a sell order sub (update real_qty, real_price, stop_loss)
# DO NOT USE: self.csv.update_sell_order_sub(sell_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update sell order sub (real_qty, real_price, real_fee, stop_loss)
sell_sub_df.loc[find_mask, 'real_qty'] = sub_sell_qty
sell_sub_df.loc[find_mask, 'real_price'] = 9_999_999.0 # gmo loss cut rate
sell_sub_df.loc[find_mask, 'real_fee'] = 0.0
sell_sub_df.loc[find_mask, 'stop_loss'] = True
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, real_qty={sub_sell_qty:,.{_q_}f}, real_price={9_999_999.0:,.{_p_}f}, real_fee={0.0:,.0f}, stop_loss=True) ")
# end of if dfx.shape[0] == 0:
### close sell order
# update sell order (real_qty, real_price, real_fee)
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL} ({sell_time=:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order
sell_df.loc[find_mask, 'real_qty'] = sell_qty
sell_df.loc[find_mask, 'real_price'] = 9_999_999.0 # gmo loss cut rate
sell_df.loc[find_mask, 'real_fee'] = 0.0
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={sell_qty:,.{_q_}f}, real_price={9_999_999.0:,.{_p_}f}, real_fee={0.0:,.0f}) ")
# end of if dfx.shape[0] == 0:
### update order (update stop_loss)
# update_order_csv_buy(buy_time: utcnow, buy_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(buy_time + buy_order_id)
self.csv.update_order_csv_buy(sub_buy_time, sub_buy_order_id, col_name_list=['stop_loss'], col_value_list=[True])
continue # continue to next sell order sub
# end of if order_status == 'EXPIRED':
# end of if order_status in 'EXPIRED,CANCELED':
### get highest buy price from buy orderbooks
latest_book_price = bk_buy_price # bk_buy_price = buy_bk_df.iloc[0]['price']
##########################################################################################
### 【4】GET ORDER PRICE
##########################################################################################
# ### get real qty, sell price, fee from the GMO
# ### get the latest close price from the GMO master
# ### mas_latest_close_price = mas_df.iloc[-1]['close'] # get latest close price (GMO)
_debug1_ = self.coin.debug_mode_debug1; _debug2_ = self.coin.debug_mode_debug2
######################################################################################### DEBUG(1), DEBUG(2)
# _debug1_ = True; _debug2_ = True # ★ TEMP TEMP TEMP ★
# self.debug.trace_warn(f".... DEBUG _debug1_ = True; _debug2_ = True ")
######################################################################################### DEBUG(1), DEBUG(2)
### get sell real qty, price & fee from the GMO : get_price() for exe_sell_close_order_limit() ★
sleep(1) # sleep 1 sec
# get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:
status, res_df, msg_code, msg_str = self.api.get_price_try(sub_sell_order_id, sub_sell_qty, sub_sell_price, latest_book_price, _debug1_, _debug2_) # ★ qty, price, close_price are used for fake mode
# status: 0(ok), 8(retry error), 9(empty dataframe or internal error), ?(misc error)
sleep(1) # sleep 1 sec
res_df = pd.DataFrame(res_df) # cast dataframe
# not real mode and debug (stop loss or cancel pending orders) ?
######################################################################################### DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug1:
if res_df.shape[0] > 0:
# ★ create two sell order subs for each sell order header ★
# update_get_price_response_for_debug1_2(res_df: pd.DataFrame, sell_order_sub_seq: int) -> pd.DataFrame
res_df = self.trade.update_get_price_response_for_debug1_2(res_df, sell_order_sub_seq) # 1, 2, 3, ...
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({sell_order_sub_seq=}) ▶ {res_df.shape=} ")
######################################################################################## DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
####################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug2:
# ★ create outstanding sell order sub depending on get_price_count ★
# get sell 'get_price_count'
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# found sell order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
get_price_count = sell_sub_df.loc[ix, 'get_price_count'] # sell_sub_df.loc[find_mask, 'get_price_count'].values[0] # 0,1,2,3,4,5,...
# update_get_price_response_for_debug2(res_df: pd.DataFrame, get_price_count: int) -> pd.DataFrame:
res_df = self.trade.update_get_price_response_for_debug2(res_df, get_price_count)
if res_df.shape[0] == 0:
status = 9 # set internal status code
msg_code = 'ERR-999' # null => ERR-999
msg_str = 'DataFrame is empty' # null => DataFrame is empty
else:
status = 0
msg_code = 'OK'
msg_str = 'Normal Return'
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug2({get_price_count=}) ▶ {status=}, {res_df.shape=} ")
###################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
# get_price() error ?
if status != 0:
if status == 9: # pending exe_sell_close_order_limit() request ?
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_sell_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_sell_close_order_limit() request: continue to next sell order sub... ")
status = 0 # reset status code
continue # continue to next sell order sub
else: # status == 1 'ERR-5008' Timestamp for this request is too late
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_price({sub_sell_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
continue # continue to next sell order sub
# end of if status == 9:
else: # normal return => executed exe_sell_close_order_limit() request
pass # self.debug.sound_alert(1)
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
##############################################################################################################################
### 【5】ADD A SELL ORDER CHILD (sell_time, sell_order_id, real_qty, real_price, real_fee) + (buy_time, buy_order_id)
##############################################################################################################################
# response df for get_price(): exe_sell_close_order_limit()
for k in range(len(res_df)):
sell_order_id = res_df.loc[k, 'orderId'] # sell order id (unique id)
position_id = res_df.loc[k, 'positionId'] # position id (non unique id => same id)
real_qty = res_df.loc[k, 'size'] # real qty
real_price = res_df.loc[k, 'price'] # real price
real_fee = res_df.loc[k, 'fee'] # real fee
if not self.coin.real_mode:
position_id = sub_position_id
### write a sell order child ★
# write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sub_sell_time, sell_order_id, sub_buy_time, sub_buy_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
# continue to next sell order sub
# end of for j in range(len(sell_subx_df)):
# continue to next sell order... ★
# end of for i in range(len(sell_df)):
return
図6-1はデモプログラムの実行結果です。
この画面は指値の売り注文が約定したときの画面です。
Apiクラスのget_order_status()の戻り値が「EXECUTED」になっているので約定しています。
Apiクラスのget_price()では約定した売り注文の数量、金額(レート)等を取得しています。
図6-2は買い・売り注文のときに作成したCSVファイルをExcelで開いた画面です。
「buy_order_(XRP_JPY).csv」ファイルには「buy_time, qty, price, real_qty, real_price,..」などが格納されています。
「real_qty, real_price」は約定した数量と金額(レート)です。
「buy_order_sub(XRP_JPY).csv」ファイルには「buy_order_id, position_id, real_qty, real_price,...」などが格納されています。
「position_id」は売り注文を出すときに使います。
「buy_order_child(XRP_JPY).csv」ファイルには「buy_order_id, position_id, real_qty, real_price,...」などが格納されています。
「sell_order_child(XRP_JPY).csv」ファイルには「sell_order_id, position_id, qty, price, real_qty, real_price,...」などが格納されています。
ここでは数量「40」で売り注文を出したのに数量が「20, 10, 10」に分割されて約定しています。
図6-3は売り注文で作成されたCSVファイルをExcelで開いた画像です。
「sell_order(XRP_JPY).csv」ファイルには「sell_time, qty, price, real_qty, real_price, buy_time,...」などが格納されています。
「real_qty, real_price」は約定した数量と金額(レート)です。
「sell_order_sub(XRP_JPY).csv」ファイルには「sell_time, sell_order_id, position_id, qty, price, real_qty, real_price, buy_order_id,...」などが格納されています。
「sell_order_child(XRP_JPY).csv」ファイルには「sell_time, sell_order_id, position_id, real_qty, real_price, buy_order_id,..」などが格納されています。
ここでは数量「40」で売り注文を出したのに数量が「20, 10, 10」に分割されて約定しています。
図6-4は仮想通貨(BTC_JPY)のレバレッジ取引で作成されたCSVファイルです。
このデモプログラムでは買い注文を数量「0.04BTC_JPY)で出しています。
そして売りでは数量「0.04BTC_JPY」で注文していますが数量が「0.02, 0.01, 0.01」に分割されて執行されています。
「order(BTC_JPY).xlsx」ファイルにはこの取引の損益が作成されています。
「63」円の損失になっていますが、デモプログラムは強制的に約定させているので金額は無視してください。
BOTでは損益を仮想通貨ごとに集計してリアルタイムで画面に表示します。
図6-5は仮想通貨「ETH_JPY」の実行結果です。
買いでは数量「0.4ETH_JPY」で注文しています。
売りでも同じ数量「0.4ETH_JPY」で注文していますが、数量が「0.2, 0.1, 0.1」に分割されて約定されています。
損益ファイルでは「54」円の損失になっていますが、デモプログラムは強制的に約定させているので金額は無視してください。
図6-6は仮想通貨「LTC_JPY」の実行結果です。
デモプログラムは成行きの買いを数量「4LTC_JPY」で出していますが、
FAK(Fill and Kill)が適用されて「2LTC_JPY」だけ執行されています。
執行された「2LTC_JPY」は、さらに「1LTC_JPY」と「1LTC_JPY」に分割されています。
デモプログラムは残数「2LTC_JPY」に対して再度成行きの買い注文を出して約定しています。
結果的に2個の買い注文IDが生成されたことになります。
売りの指値の注文ではポジションID指定で3回の売り注文(決済)を出しています。
売り注文の数量は「1, 1, 2」になります。
ところがこの売り注文は分割されて結果的に執行された数量は「1, 1, 1, 1」になっています。
売りの注文IDは3個生成されています。
損益ファイルには2個の注文に分割されて作成されています。
2個の注文とも「20」円の損失になっていますが、デモプログラムは強制的に約定させているので金額は無視してください。
-
BuySellクラスにupdate_order_profit()メソッドを追加する
ここではBuySellクラスにupdate_order_profit()メソッドを追加します。
このメソッドは取引の損益を計算してCSVファイルとExcelファイルに保存します。
buy_sell.py:
#######################################################
def update_order_profit(self, first_pass=True) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'update_order_profit():'
order_completed = self.trade.get_order_status(exclude=False) # include stop_loss, cancelled, closed order info
self.debug.trace_write(f".... {self.gvar.callers_method_name} first_pass({first_pass}), last_order_completed({order_completed}) ")
######################################################################################################################################################################
### 【1】IMPORT BUY ORDER SUB & APPEND/UPDATE ORDER CSV FILE (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee) & (sell_time, sell_order_id)
######################################################################################################################################################################
### OPEN (load the order csv file) : order(BTC_JPY).csv
ord_df = self.csv.get_order_csv()
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub()
### import buy order sub file
# get all buy order sub
for i in range(len(buy_sub_df)):
### get buy order sub info
sub_buy_time = buy_sub_df.loc[i, 'buy_time'] # ★ (non unique)
sub_sell_time = buy_sub_df.loc[i, 'sell_time'] # ★ (non unique)
sub_buy_order_id = buy_sub_df.loc[i, 'buy_order_id'] # ★ 'B9999-999999-999999' (unique)
sub_sell_order_id = buy_sub_df.loc[i, 'sell_order_id'] # ★ 'S9999-999999-999999;S9999-999999-999999;S9999-999999-999999' (list of sell_order_ids) : buy sub(one) <= sell sub(many)
sub_buy_qty = buy_sub_df.loc[i, 'qty']
sub_buy_price = buy_sub_df.loc[i, 'price']
sub_buy_real_qty = buy_sub_df.loc[i, 'real_qty']
sub_buy_real_price = buy_sub_df.loc[i, 'real_price']
sub_buy_real_fee = buy_sub_df.loc[i, 'real_fee']
sub_buy_get_price_count = buy_sub_df.loc[i, 'get_price_count']
sub_buy_stop_loss = buy_sub_df.loc[i, 'stop_loss']
sub_buy_order_cancelled = buy_sub_df.loc[i, 'cancelled']
sub_buy_order_closed = True if sub_buy_qty == sub_buy_real_qty else False
# fake sub order ? => SKIP
if sub_buy_order_id.startswith('Fake'):
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id})... ")
continue
# empty order csv file ?
if ord_df.shape[0] == 0:
dfx = pd.DataFrame()
else: # not empty dataframe => find the order csv file by buy_order_id
find_mask = ord_df['buy_order_id'] == sub_buy_order_id # B9999-999999-999999
dfx = ord_df[find_mask]
# not found order csv file ?
if dfx.shape[0] == 0:
### append order csv file ★IMPORTANT★ DO NOT APEEND 'Null' Value => sell_order_id = 'S9999-999999-999999'
columns = Gmo_dataframe.ORDER_CSV
_sell_real_qty_=0.0; _sell_real_price_=0.0; _sell_real_fee_=0.0; _real_profit_=0.0
_accumulated_leverage_fee_=0.0; _close_count_=0; _close_update_date_=dt.datetime.now()
_order_closed_ = False; _stop_loss_=sub_buy_stop_loss; _cancelled_=sub_buy_order_cancelled
_order_open_date_ = dt.datetime.now(); _order_close_date_ = dt.datetime.now(); _log_time_=dt.datetime.now()
data = [[sub_buy_time, sub_sell_time, sub_buy_order_id, sub_sell_order_id, sub_buy_real_qty, _sell_real_qty_, sub_buy_real_price, _sell_real_price_, sub_buy_real_fee, _sell_real_fee_, _real_profit_, _accumulated_leverage_fee_, _close_count_, _close_update_date_, _order_closed_, _stop_loss_, _cancelled_, _order_open_date_, _order_close_date_, _log_time_]]
new_df = pd.DataFrame(data, columns=columns)
# convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
new_df['buy_time'] = pd.to_datetime(new_df['buy_time'], utc=True) # object => utc time (aware)
new_df['sell_time'] = pd.to_datetime(new_df['sell_time'], utc=True) # object => utc time (aware)
new_df['close_update_date'] = pd.to_datetime(new_df['close_update_date']) # object => ns time (jp time)
new_df['order_open_date'] = pd.to_datetime(new_df['order_open_date']) # object => ns time (jp time)
new_df['order_close_date'] = pd.to_datetime(new_df['order_close_date']) # object => ns time (jp time)
new_df['log_time'] = pd.to_datetime(new_df['log_time']) # object => ns time (jp time)
new_df['buy_order_id'] = new_df['buy_order_id'].astype(str) # int64 => string
new_df['sell_order_id'] = new_df['sell_order_id'].astype(str) # int64 => string
# append a new row (order row)
ord_df = ord_df.append(new_df, ignore_index=True)
self.debug.trace_write(f".... {self.gvar.callers_method_name} write_order_csv_buy(buy_order_id={sub_buy_order_id}, buy_real_qty={sub_buy_real_qty:,.{_q_}f}, buy_real_price={sub_buy_real_price:,.{_p_}f}, buy_real_fee={sub_buy_real_fee:,.0f}) ")
else: # found order csv file => update order csv file
# get buy_real_qty and sell_real_qty
ix = dfx.index[0] # get index of the first row
# ord_buy_order_id = ord_df.loc[ix, 'buy_order_id']
ord_buy_real_qty = ord_df.loc[ix, 'buy_real_qty']
ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']
order_closed = ord_df.loc[ix, 'order_closed']
# completed order csv file ?
if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:
if not order_closed:
# update order csv file
ord_df.loc[find_mask, 'order_closed'] = True
ord_df.loc[find_mask, 'order_close_date'] = dt.datetime.now()
ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
ord_df['order_close_date'] = pd.to_datetime(ord_df['order_close_date']) # object => ns time (jp time)
ord_df['log_time'] = pd.to_datetime(ord_df['log_time']) # object => ns time (jp time)
# self.debug.trace_write(f".... update_order2(): pass update order info (buy): buy_order_id={sub_buy_order_id}, buy_real_qty={sub_buy_real_qty:,.{_q_}f}, buy_real_price={sub_buy_real_price:,.{_x_}f}, buy_real_fee={sub_buy_real_fee:,.0f} ")
else: # incomplete order => update order csv file
# update order info
ord_df.loc[find_mask, 'buy_time'] = sub_buy_time
ord_df.loc[find_mask, 'buy_order_id'] = sub_buy_order_id
ord_df.loc[find_mask, 'buy_real_qty'] = sub_buy_real_qty
ord_df.loc[find_mask, 'buy_real_price'] = sub_buy_real_price
ord_df.loc[find_mask, 'buy_real_fee'] = sub_buy_real_fee
ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
### update order_closed flag
# get buy_real_qty and sell_real_qty
ix = dfx.index[0] # get index of the first row
# ord_buy_order_id = ord_df.loc[ix, 'buy_order_id']
ord_buy_real_qty = ord_df.loc[ix, 'buy_real_qty']
ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']
order_closed = ord_df.loc[ix, 'order_closed']
if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:
ord_df.loc[find_mask, 'order_closed'] = True
# convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
ord_df['buy_time'] = pd.to_datetime(ord_df['buy_time'], utc=True) # object => utc time (aware)
ord_df['sell_time'] = pd.to_datetime(ord_df['sell_time'], utc=True) # object => utc time (aware)
ord_df['close_update_date'] = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)
ord_df['order_open_date'] = pd.to_datetime(ord_df['order_open_date']) # object => ns time (jp time)
ord_df['order_close_date'] = pd.to_datetime(ord_df['order_close_date']) # object => ns time (jp time)
ord_df['log_time'] = pd.to_datetime(ord_df['log_time']) # object => ns time (jp time)
ord_df['buy_order_id'] = ord_df['buy_order_id'].astype(str) # int64 => string
ord_df['sell_order_id'] = ord_df['sell_order_id'].astype(str) # int64 => string
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_buy(buy_order_id={sub_buy_order_id}, buy_real_qty={sub_buy_real_qty:,.{_q_}f}, buy_real_price={sub_buy_real_price:,.{_p_}f}, buy_real_fee={sub_buy_real_fee:,.0f}) ")
# end of if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:
# end of if dfx.shape[0] == 0:
# continue to next buy sub orders
# end of for i in range(len(buy_sub_df)):
#######################################################################################################################################
### 【2】IMPORT SELL ORDER SUB & UPDATE ORDER CSV FILE (sell_time, sell_order_id, sell_real_qty, sell_real_price, sell_real_fee)
#######################################################################################################################################
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub()
### import sell order sub file
for i in range(len(sell_sub_df)):
### get sell order sub info
sub_sell_time = sell_sub_df.loc[i, 'sell_time'] # ★ (non unique)
sub_buy_time = sell_sub_df.loc[i, 'buy_time'] # ★ (non unique)
sub_sell_order_id = sell_sub_df.loc[i, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_buy_order_id = sell_sub_df.loc[i, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : sell sub(many) => buy_sub(one)
sub_sell_qty = sell_sub_df.loc[i, 'qty']
sub_sell_price = sell_sub_df.loc[i, 'price']
sub_sell_real_qty = sell_sub_df.loc[i, 'real_qty']
sub_sell_real_price = sell_sub_df.loc[i, 'real_price']
sub_sell_real_fee = sell_sub_df.loc[i, 'real_fee']
sub_sell_get_price_count = sell_sub_df.loc[i, 'get_price_count']
sub_sell_stop_loss = sell_sub_df.loc[i, 'stop_loss']
sub_sell_order_cancelled = sell_sub_df.loc[i, 'cancelled']
sub_sell_order_open = False
sub_sell_order_closed = False
sub_sell_order_incomplete = False
if sub_sell_real_qty == 0:
sub_sell_order_open = True
elif sub_sell_qty == sub_sell_real_qty:
sub_sell_order_closed = True
else:
sub_sell_order_incomplete = True
# fake sub order ? => SKIP
if sub_sell_order_id.startswith('Fake'):
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id})... ")
continue
# empty order csv file ?
if ord_df.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - order csv file is empty: continue... ")
self.debug.sound_alert(3)
continue
# find the order csv file by buy_order_id
find_mask = ord_df['buy_order_id'] == sub_buy_order_id
dfx = ord_df[find_mask]
# not found order csv file ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}ERROR{Bcolors.ENDC} order csv file not found: buy_order_id={sub_buy_order_id} ")
self.debug.sound_alert(3)
continue
### found order csv file
# get buy_real_qty and sell_real_qty
ix = dfx.index[0] # get index of the first row
ord_sell_order_id = ord_df.loc[ix, 'sell_order_id']
ord_buy_real_qty = ord_df.loc[ix, 'buy_real_qty']
ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']
ord_order_closed = ord_df.loc[ix, 'order_closed']
# completed order csv file ?
if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:
if not ord_order_closed:
# update order csv file
ord_df.loc[find_mask, 'order_closed'] = True
ord_df.loc[find_mask, 'order_close_date'] = dt.datetime.now()
ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
ord_df['order_close_date'] = pd.to_datetime(ord_df['order_close_date']) # object => ns time (jp time)
ord_df['log_time'] = pd.to_datetime(ord_df['log_time']) # object => ns time (jp time)
# self.debug.trace_write(f".... update_order2(): pass update order csv file (sell): sell_order_id={sub_sell_order_id}, sell_real_qty={sub_sell_real_qty:,.{_q_}f}, sell_real_price={sub_sell_real_price:,.{_x_}f}, sell_real_fee={sub_sell_real_fee:,.0f} ")
else: # incomplete order => update order csv file
### get a list of sell_order_id from the sell order sub ★ buy_order_id ★
filter_mask = sell_sub_df['buy_order_id'] == sub_buy_order_id
dfx = sell_sub_df[filter_mask]
sell_order_id_list = dfx.groupby('buy_order_id')['sell_order_id'].apply(list)[0]
sell_order_ids_by_semicolon = ''
for k, order_id in enumerate(sell_order_id_list):
if k == 0:
sell_order_ids_by_semicolon += order_id
else:
sell_order_ids_by_semicolon += ';' + order_id
### calculate total sell qty / fee and mean price ★ buy_order_id ★
sell_sum_real_qty = dfx.groupby('buy_order_id')['real_qty'].sum().values[0]
sell_sum_real_fee = dfx.groupby('buy_order_id')['real_fee'].sum().values[0]
sell_mean_real_price = dfx.groupby('buy_order_id')['real_price'].mean().values[0]
### update order csv file sell info (sell_time, sell_order_id, sell_real_qty, sell_real_price, sell_real_fee) OK
ord_df.loc[find_mask, 'sell_time'] = sub_sell_time
ord_df.loc[find_mask, 'sell_order_id'] = sell_order_ids_by_semicolon # 'OrderId1;OrderId2;OrderId3'
ord_df.loc[find_mask, 'sell_real_qty'] = round(sell_sum_real_qty, 2) # 999.12
ord_df.loc[find_mask, 'sell_real_price'] = round(sell_mean_real_price, 3) # 999999.123
ord_df.loc[find_mask, 'sell_real_fee'] = round(sell_sum_real_fee, 3) # 999.123
# convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
ord_df['buy_time'] = pd.to_datetime(ord_df['buy_time'], utc=True) # object => utc time (aware)
ord_df['sell_time'] = pd.to_datetime(ord_df['sell_time'], utc=True) # object => utc time (aware)
ord_df['close_update_date'] = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)
ord_df['order_open_date'] = pd.to_datetime(ord_df['order_open_date']) # object => ns time (jp time)
ord_df['order_close_date'] = pd.to_datetime(ord_df['order_close_date']) # object => ns time (jp time)
ord_df['log_time'] = pd.to_datetime(ord_df['log_time']) # object => ns time (jp time)
ord_df['buy_order_id'] = ord_df['buy_order_id'].astype(str) # int64 => string
ord_df['sell_order_id'] = ord_df['sell_order_id'].astype(str) # int64 => string
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_sell(sell_order_id={sub_sell_order_id}, sell_real_qty={sell_sum_real_qty:,.{_q_}f}, sell_real_price={sell_mean_real_price:,.{_p_}f}, sell_real_fee={sell_sum_real_fee:,.0f}) ")
# end of if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:
# continue to next sell order sub
# end of for i in range(len(sell_sub_df)):
#######################################################################################################################################
### 【3】SAVE ORDER TO CSV/EXCEL FILES
#######################################################################################################################################
### CLOSE (save to the order csv file) : order(BTC_JPY).csv
if ord_df.shape[0] > 0:
### CLOSE (order csv file)
csv_file = self.folder_trading_type + f'order({symbol}).csv'
# desktop-pc/bot//buy_sell/order(BTC_JPY).csv
ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file
### SAVE (order excel file)
# save to the order excel file
excel_file = self.folder_trading_type + f'order({symbol}).xlsx'
# desktop-pc/bot/buy_sell/order(BTC_JPY).xlsx
excel_df = ord_df.copy()
# (1) convert time to string
excel_df['buy_time'] = excel_df['buy_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))
excel_df['sell_time'] = excel_df['sell_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))
excel_df['close_update_date'] = excel_df['close_update_date'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))
excel_df['log_time'] = excel_df['log_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))
# (2) drop real_profit column
excel_df = excel_df.drop(['real_profit'], axis=1)
# (3) add new columns
excel_df['buy(SUM)'] = '=ROUND(E2*G2,3)' # add a new column
excel_df['sell(SUM)'] = '=ROUND(F2*H2,3)' # add a new column
excel_df['profit(AMT)'] = '=IF((H2-G2)>0,ROUNDDOWN((H2 - G2) * F2, 0),ROUNDUP((H2 - G2) * F2, 0))' # add a new column : rounddown(profit), roundup(loss)
excel_df['profit(%)'] = '=M2/K2' # add a new column
# (4) update excel formula
# get all excel order info
row = 2
for i in range(len(excel_df)):
order_closed = excel_df.loc[i, 'order_closed'] # True or False
stop_loss = excel_df.loc[i, 'stop_loss'] # True or False
cancelled = excel_df.loc[i, 'cancelled'] # True or False
if order_closed or stop_loss or cancelled:
cell = str(row)
excel_df.loc[i, 'buy(SUM)'] = f'=ROUND(E{cell}*G{cell},3)'
excel_df.loc[i, 'sell(SUM)'] = f'=ROUND(F{cell}*H{cell},3)'
excel_df.loc[i, 'profit(AMT)'] = f'=IF((H{cell}-G{cell})>0,ROUNDDOWN((H{cell} - G{cell}) * F{cell}, 0),ROUNDUP((H{cell} - G{cell}) * F{cell}, 0))'
excel_df.loc[i, 'profit(%)'] = f'=M{cell}/K{cell}'
else:
excel_df.loc[i, 'buy(SUM)'] = ''
excel_df.loc[i, 'sell(SUM)'] = ''
excel_df.loc[i, 'profit(AMT)'] = ''
excel_df.loc[i, 'profit(%)'] = ''
row += 1
# end of for j in range(len(excel_df)):
# (5) re-order columns
columns = [
'buy_time','sell_time','buy_order_id','sell_order_id','buy_real_qty','sell_real_qty','buy_real_price','sell_real_price','buy_real_fee','sell_real_fee',
'buy(SUM)','sell(SUM)','profit(AMT)','profit(%)',
'accumulated_leverage_fee','close_count','close_update_date',
'order_closed','stop_loss','cancelled','order_open_date','order_close_date','log_time'
]
excel_df = excel_df[columns]
# print('excel_df.info()\n', excel_df.info())
# ------------------------------------------------------------------------------------------
# # Column Dtype
# ------------------------------------------------------------------------------------------
# 1 time(B) datetime64[utc] => Convert to string
# 2 time(S) datetime64[utc] => Convert to string
# 3 order(B) object
# 4 order(S) object
# 5 qty(B) float64
# 6 qty(S) float64
# 7 price(B) float64
# 8 price(S) float64
# 9 fee(B) float64
# 10 fee(S) float64
# real_profit float64 => REMOVE
# 11 buy(SUM) float64 => ADD =ROUNDUP(E2*G2,3)+I2
# 12 sell(SUM) float64 => ADD =ROUNDDOWN(F2*H2,3)+J2
# 13 profit(AMT) float64 => ADD =L2-K2
# 14 profit(%) float64 => ADD =M2/K2
# 15 fee(L) float64
# 16 count(L) int
# 17 close(L) datetime64[ns]
# 18 order_closed bool
# 19 stop bool
# 20 cancel bool
# 21 open_date datetime64[ns]
# 22 close_date datetime64[ns]
# 23 log_time datetime64[ns] => Convert to string
# ----------------------------------------------------------------------------------------
# (5) rename column names & export to excel file order(BTC_JPY).xlsx
excel_df.columns = Gmo_dataframe.ORDER_XLSX
excel_df.to_excel(excel_file, sheet_name='GMO Orders', index=False)
# end of if ord_df.shape[0] > 0:
return
図7-1はデモプログラムの実行結果です。
このデモプログラムでは仮想通貨「XRP_JPY, LTC_JPY, BCH_JPY」に対して10回自動トレードを繰り返しています。
注文数は「10XRP_JPY, 1LTC_JPY, 0.1BCH_JPY」で利益率は「0.0035, 0.0015, 0.0015」を設定しています。
注文数は最小値で利益率も小さいので金額は無視して約定の回数に注目してください。
画像にはBuySellクラスのupdate_order_profit()メソッドが実行されたことがログに表示されています。
このメソッドでは仮想通貨ごとの損益を計算してExcelのファイルに保存します。
図7-2はupdate_order_profit()メソッドが作成した損益ファイルをExcelで開いています。
このファイルには「time(B/S), order(B/S), qty(B/S), price(B/S), profit(AMT), profit(%), close, stop, cancel,..」などが格納されています。
「()」のBはBuy, SはSellを意味します。
「profit(AMT)」には損益額、「profit(%)」には損益をパーセント(%)で表示します。
「closed」が「TRUE」のときは取引が約定していることを意味します。
「stop, cancel」が「TRUE」のときは取引がストップ・ロス、キャンセルされたことを意味します。
「BCH_JPY」が3回、「LTC_JPY」が1回約定しています。
BOTのログには仮想通貨ごとの約定回数と損益が集計されてリアルタイムで表示されます。
-
BuySellクラスにstop_loss(), stop_loss_0()メソッドを追加する
ここではBuySellクラスにstop_loss()メソッドを追加します。
このメソッドは価格が下落したときに強制的にポジションを決済して損失が大きくなるのを回避します。
stop_loss()もBOTの肝となる部分です。
buy_sell()とstop_loss()の作り方によって利益がでるかどうかが決まります。
stop_loss()の引数には「method_id」を指定します。
デフォルトで「method_id=0」が用意されています。
stop_loss_0()メソッドはBOTをテストするときに使用します。
BOTのテストが完了したら本番用のストップ・ロスのメソッドを作って
「stop_loss_1(), stop_loss_2(),...」のようなメソッド名を追加します。
buy_sell.py:
############################################
def stop_loss(self, method_id: int) -> None:
"""
"""
if method_id <= 1:
eval(f'self.stop_loss_{str(method_id)}()') # call 'self.stop_loss_?() method
else:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid stoploss_method_id {method_id=} {Bcolors.ENDC} - skip stop_loss() ")
self.debug.sound_alert(3)
# if method_id == 0:
# self.stop_loss_0() # stop loss method 0
# elif method_id == 1:
# self.stop_loss_1() # stop loss method 1
# else: #
# self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid stoploss_method_id {method_id=} {Bcolors.ENDC} - skip stop_loss() ")
# self.debug.sound_alert(3)
return
##############################
def stop_loss_0(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'stop_loss(0):'
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast df (master dataframe)
self.debug.trace_write(f".... {self.gvar.callers_method_name} mas_df.shape({mas_df.shape[0]}), loss cut: 18%({self.coin.buy_sell_action14}), 15%({self.coin.buy_sell_action13}), 10%({self.coin.buy_sell_action12}) ")
# ### OPEN (load the order csv file) : order(BTC_JPY).csv
# ord_df = self.csv.get_order_csv()
### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order() # PK(buy_time)
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
#################################################
### 【1】READ ALL SELL ORDERs
#################################################
# define sell order list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
### get all sell orders
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # ★ unique time
buy_time = sell_df.loc[i, 'buy_time'] # ★ unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell order sub
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
sell_order_id = str(dfx.loc[ix, 'sell_order_id'])
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sell_order_id}) has been closed: {sell_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{BBcolors.ENDC} sell order({sell_time:%Y-%m-%d %H:%M:%S}) has been closed: ={sell_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue # skip closed order
# end of if sell_order_closed:
#################################################################
### 【2】READ SELL ORDER SUB (open or incomplete status)
#################################################################
### for sell order sub (open or incomplete status)
# define sell order sub list
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
### get sell order sub info
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★ (unique id)
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★ (non unique id) : sell sub(many) => buy_sub(one)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = sell_subx_df.loc[j, 'qty'] # 40
sub_price = sell_subx_df.loc[j, 'price']
sub_real_qty = sell_subx_df.loc[j, 'real_qty'] # 20
sub_real_price = sell_subx_df.loc[j, 'real_price']
sub_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_get_price_count = sell_subx_df.loc[j, 'get_price_count']
sub_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_order_open = False
sub_order_closed = False
sub_order_incomplete = False
if sub_real_qty == 0:
sub_order_open = True
elif sub_qty == sub_real_qty:
sub_order_closed = True
else:
sub_order_incomplete = True
# fake sell sub order ? => SKIP
if sub_sell_order_id.startswith('Fake') :
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id}) ")
continue
# sell sub order has been stop loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss: {sub_stop_loss=} ")
continue
# sell sub order has been cancelled ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled: {sub_cancelled=} ")
continue
# closed sell sub order ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed: {sub_order_closed=} ")
continue
#######################################################################################################
### 【3】CHECK STOP LOSS CONDITIONS FOR EACH SELL ORDER SUB (open or incomplte sell order sub)
#######################################################################################################
### open or incomplete sell order sub
stop_loss_detected = False
while True:
#####################################################
### Check stop loss debug mode
#####################################################
if self.coin.debug_mode_stop_loss:
stop_loss_detected = True
break # exit while true loop
#####################################################
### Unconditional stop loss
#####################################################
### check loss cut price : buy => sell
### calculate stop loss amount
stop_loss_amt = 0
latest_sell_price = 0
filter_mask = mas_df['side'] == 'SELL'
dfx = mas_df[filter_mask]
if dfx.shape[0] > 0:
# get latest sell price from GMO master dataframe
sell_price = dfx.iloc[-1]['price']
latest_sell_price = sell_price
# get last buy qty & price from the buy order header
qty = buy_df.iloc[-1]['qty']
buy_price = buy_df.iloc[-1]['real_price'] # get a real buy price
# calculate a stop loss amount : stop_loss_amt = math.floor((sell_price - buy_price) * qty)
stop_loss_amt = math.floor((sell_price - buy_price) * qty) # positive => profit, negative => loss
stop_loss_amt = math.floor(stop_loss_amt) # positive => profit, negative => loss
### calculate loss cut price from the buy sub order : loss_cut_price = last_buy_real_price - (last_buy_real_price * 0.20)
last_buy_real_price = buy_sub_df.iloc[-1]['real_price']
### buy_sell : sell position => calculate down limit for 5%, 10%, 15%, 18%, 20%, 23%, 25%(GMO loss cut rate)
# calculate loss cut price from the sell order sub : loss_cut_price = last_buy_real_price - (last_buy_real_price * 0.20)
loss_cut_price_05 = last_buy_real_price - (last_buy_real_price * 0.05) # 0.25(GMO loss cut rate) => 0.05
loss_cut_price_10 = last_buy_real_price - (last_buy_real_price * 0.10) # 0.25(GMO loss cut rate) => 0.10
loss_cut_price_15 = last_buy_real_price - (last_buy_real_price * 0.15) # 0.25(GMO loss cut rate) => 0.15
loss_cut_price_18 = last_buy_real_price - (last_buy_real_price * 0.18) # 0.25(GMO loss cut rate) => 0.18
loss_cut_price_20 = last_buy_real_price - (last_buy_real_price * 0.20) # 0.25(GMO loss cut rate) => 0.20 BOT LOSS CUT
loss_cut_price_23 = last_buy_real_price - (last_buy_real_price * 0.23) # 0.25(GMO loss cut rate) => 0.23 DO NOT USE THIS
loss_cut_price_25 = last_buy_real_price - (last_buy_real_price * 0.25) # 0.25(GMO loss cut rate) => 0.25 GMO LOSS CUT
# gmo master latest sell price is greater than zero ?
if latest_sell_price > 0:
if latest_sell_price <= loss_cut_price_25: # <= 75
self.coin.buy_sell_lc25 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 25%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_25%({loss_cut_price_25:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_23: # <= 79
self.coin.buy_sell_lc23 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 23%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_23%({loss_cut_price_23:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_20: # <= 80
self.coin.buy_sell_lc20 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 20%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_20%({loss_cut_price_20:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_18: # <= 85
self.coin.buy_sell_lc18 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 18%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_18%({loss_cut_price_18:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_15: # <= 90
self.coin.buy_sell_lc15 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 15%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_15%({loss_cut_price_15:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_10: # <= 95
self.coin.buy_sell_lc10 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 10%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_10%({loss_cut_price_10:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_05: # <= 99
self.coin.buy_sell_lc5 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 5%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_5%({loss_cut_price_05:,.{_x_}f}) ")
### check down limit based on buy price
# GMO latest sell price is less than or equal to loss cut price (20%) ? => EXECUTE STOP LOSS
if latest_sell_price <= loss_cut_price_20:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}STOP LOSS DETECTED REAL(1-5) EXECUTE{Bcolors.ENDC} loss cut 20%: latest_sell_price({latest_sell_price:,.{_p_}f}) <= loss_cut_20({loss_cut_price_20:,.{_p_}f}) ")
stop_loss_detected = True
break # exit while true loop
# end of if latest_sell_price > 0:
break # exit while true loop
# end of while True:
if not stop_loss_detected: continue
#######################################################
# ★ STOP LOSS DETECTED ★ #
#######################################################
# increment stop loss count
self.gvar.stop_loss_count += 1
self.coin.stop_loss_count += 1
######################################################
### 【4】UPDATE PARTIALLY CLOSED ORDER
######################################################
if sub_order_incomplete:
### add partially closed sell order child
self.update_partially_closed_order()
self.gvar.callers_method_name = 'stop_loss(0):'
######################################################
### 【5】CANCEL CLOSED BUY ORDER SUB
######################################################
### cancel closed buy order sub
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed buy order sub({sub_buy_order_id}) ")
# increment buy cancel count
self.gvar.buy_cancel_count += 1 # cancel closed buy order (with sell order)
self.coin.buy_cancel_count += 1 # cancel closed buy order (with sell order)
### set a cancelled flag for buy order sub: PK(buy_time + buy_order_id) ★
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
buy_sub_df.loc[find_mask, 'cancelled'] = True
######################################################
### 【6】CANCEL OPEN/INCOMPLETE SELL ORDER SUB
######################################################
### for open or incomplete sell sub order
# cancel open/incomplete sell order sub
status, msg_code, msg_str = self.api.exe_cancel_order(sub_sell_order_id) # ★
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} open/incomplete sell order sub({sub_sell_order_id}): exe_cancel_order() ▶ {status=} ")
sleep(0.5) # sleep 0.5 sec
###########################################################################################
### 【7】RESELL OUTSTANDING SELL ORDER SUB QTY : EXECUTE SELL CLOSE ORDER MARKET (FAK)
###########################################################################################
### resell open/incomplete sell order sub with market price (stop loss price)
outstanding_qty = sub_qty - sub_real_qty
new_sell_qty = outstanding_qty
qty = new_sell_qty
if sub_real_price > 0:
new_sell_price = sub_real_price + sub_real_fee
else:
new_sell_price = sub_price
### get the latest close price from the GMO master
# get a column position of the GMO master
mas_latest_close_price = mas_df.iloc[-1]['close'] # get latest close price (GMO)
status = 0
loop_count = 0
while True:
loop_count += 1
### resell sell order sub outstanding qty
status, res_df, msg_code, msg_str = self.api.exe_sell_close_order_market_wait(sub_position_id, qty, new_sell_price, mas_latest_close_price) # ★ FAK(Fill and Kill): new_sell_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}RESELL({loop_count}){Bcolors.ENDC} open/incomplete sell order sub({sub_sell_order_id}): exe_sell_close_order_market_wait() ▶ {status=} ")
# error ?
if status != 0:
if status == 1 and msg_code == 'ERR-208': # Exceeds the available balance
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC}{Bcolors.FAIL} the available balance exceeded error... ERR-208{Bcolors.ENDC} ")
self.debug.sound_alert(1)
break # exit while true loop and continue to read next sell order sub
elif status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_sell_close_order_market_wait() time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next sell order sub
else:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_sell_close_order_market_wait() ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while tue loop and continue to read next sell order sub
# end of if status == 1 and msg_code == 'ERR-201':
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
######################################################################################################
### 【8】ADD SELL ORDER CHILD ★ DO NOT CREATE RESELL VERSION OF NEW SELL ORDER SUB ★
######################################################################################################
# res_df = get_price() response for exe_sell_order_market_wait()
# ----------------------------------------------------------------
# # Column Dtype
# ----------------------------------------------------------------
# 0 executionId int64
# 1 fee float64
# 2 lossGain float64
# 3 orderId object(string)
# 4 posiitonId object(string)
# 5 price float64
# 6 settleType object('OPEN' or 'CLOSE')
# 7 side object('BUY' or 'SELL')
# 8 size float64
# 9 symbol object('BTC')
# 10 timestamp datetime64[ns]
# ----------------------------------------------------------------
### IMPORTANT ★ DO NOT CREATE RESELL VERSION OF NEW SELL ORDER SUB ★
### append a new record into the sell order child (★ this is the resell version of buy order ★)
# define sell order child list
order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response df for get_price for exe_sell_close_order_market()
for k in range(len(res_df)):
order_id = res_df.loc[k, 'orderId'] # orderId (same orderId): resell version of orderId
position_id = res_df.loc[k, 'positionId'] # positionId (same positionId)
real_qty = res_df.loc[k, 'size'] # real qty :
real_price = res_df.loc[k, 'price'] # real price
real_fee = res_df.loc[k, 'fee'] # real fee
order_id_list.append(order_id) # S9999-999999-999999
position_id_list.append(position_id) # P9999-999999-999999
real_qty_list.append(real_qty)
real_price_list.append(real_price)
real_fee_list.append(real_fee)
### write a sell order child (resell version) ★
# write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sell_time, order_id, buy_time, sub_buy_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
sum_real_qty = round(sum(real_qty_list), _q_) # round(999.12, _q_)
sum_real_fee = round(sum(real_fee_list), 0) # zero(0)
if sum(real_price_list) > 0:
mean_real_price = round(statistics.mean(real_price_list), _p_) # round(999999.123, _p_)
else:
mean_real_price = 0
sub_real_qty_list.append(sum_real_qty)
sub_real_price_list.append(mean_real_price)
sub_real_fee_list.append(sum_real_fee)
### check exit condition:
sub_sum_real_qty = round(sum(sub_real_qty_list), _q_) # round(9999.12, _q_)
new_sub_sum_real_qty = sub_real_qty + sub_sum_real_qty
# ####################################################### TEMP TEMP TEMP
# temp_qty = round(sub_qty, _q_)
# temp_real_qty = round(new_sub_sum_real_qty, _q_)
# self.debug.trace_warn(f".... DEBUG: {temp_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
# ####################################################### TEMP TEMP TEMP
if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
# increment sell cancel count
self.gvar.sell_cancel_count += 1 # cancel open/incomplete sell order
self.coin.sell_cancel_count += 1 # cancel open/incomplete sell order
break # exit while true loop
else: # outstanding buy_qty exists
pass # skip (do nothing)
# end of round(sub_qty, digits) == round(new_sub_sum_real_qty, digits):
### partial exe_sell_order_market_wait() = continue to sell remaining qty
remaining_qty = new_sell_qty - new_sub_sum_real_qty
qty = remaining_qty
# continue exe_sell_close_order_market_wait() ★
# end of while True:
#################################################################################
### 【9】UPDATE SELL ORDER SUB (real_qty, real_price, real_fee, stop_loss)
#################################################################################
sub_sum_real_qty = round(sum(sub_real_qty_list), _q_) # round(999.12,_q_)
sub_sum_real_fee = round(sum(sub_real_fee_list), 0) # zero(0)
if sum(sub_real_price_list) > 0:
sub_mean_real_price = round(statistics.mean(sub_real_price_list), _p_) # round(999999.123, _p_)
else:
sub_mean_real_price = 0
header_real_qty_list.append(sub_sum_real_qty)
header_real_price_list.append(sub_mean_real_price)
header_real_fee_list.append(sub_sum_real_fee)
### update the sell order sub (real_qty, real_price, real_fee, stop_loss) ★
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update
sell_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty # add real_qty (resell version)
sell_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price # update real_price (resell version)
sell_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee # add real_fee (resell version)
sell_sub_df.loc[find_mask, 'stop_loss'] = True
ix = dfx.index[0]
new_sub_sum_real_qty = sell_sub_df.loc[ix, 'real_qty']
new_sub_sum_real_fee = sell_sub_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub(sell_order_id={sub_sell_order_id}, real_qty={new_sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={new_sub_sum_real_fee:,.0f}, stop_loss=True) ")
# end of if dfx.shape[0] == 0:
#################################################################################
### 【10】UPDATE ORDER CSV FILE (stop_loss, order_closed)
#################################################################################
### update order info (stop_loss=True, order_closed) ★
# update_order_csv_buy(buy_time: utcnow, buy_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(buy_time + buy_order_id)
self.csv.update_order_csv_buy(sub_buy_time, sub_buy_order_id, col_name_list=['stop_loss'], col_value_list=[True])
# reset sell order sub list values
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# continue to next sell order sub... ★
# end of for j in range(len(sell_sub_df)):
#################################################################################
### 【11】SAVE BUY/SELL ORDER SUB TO CSV FILES
#################################################################################
### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【12】UPDATE SELL ORDER (real_qty, real_price, real_fee)
#################################################################################
if len(header_real_qty_list) > 0:
### update sell order (real_qty, real_price, real_fee) ★
header_sum_real_qty = round(sum(header_real_qty_list), _q_) # round(999.12, _q_
header_sum_real_fee = round(sum(header_real_fee_list), 0) # zero(0)
if sum(header_real_price_list) > 0:
header_mean_real_price = round(statistics.mean(header_real_price_list), _p_) # round(999999.123, _p_)
else:
header_mean_real_price = 0
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order({sell_time:%Y-%m-%d %H:%M:%S}) ▶ {Bcolors.FAIL} order({sell_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order header => update
sell_df.loc[find_mask, 'real_qty'] += header_sum_real_qty # add real_qty (resell version)
sell_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (resell version)
sell_df.loc[find_mask, 'real_fee'] += header_sum_real_fee # add real_fee (resell version)
ix = dfx.index[0]
new_header_sum_real_qty = sell_df.loc[ix, 'real_qty']
new_header_sum_real_fee = sell_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={new_header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={new_header_sum_real_fee:,.0f}) ")
# end of if dfx.shape[0] == 0:
# end of if len(header_real_qty_list) > 0:
# reset list values
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
# continue to next sell order
# end of for i in range(len(sell_df)):
#################################################################################
### 【13】SAVE BUY/SELL ORDER TO CSV FILES
#################################################################################
### CLOSE (save the buy order csv file) : buy_order(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order(BTC_JPY).csv
buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order csv file) : sell_order(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order(BTC_JPY).csv
sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
return
図8-1はデモプログラムの実行結果です。
画面にはBuySellクラスのstop_loss(0)メソッドのログが表示されています。
stop_loss(0)では未執行分の売り注文のキャンセルが実行されています。
さらに未執行の売りボジションに対して成行きの売り注文を出して強制的に決済しています。
詳細は図8-2で説明します。
図8-2は売り注文で作成されたCSVファイルをExcelで開いた画像です。
売り注文のCSVファイルも買い注文と同様3階層になっています。
ここでは3種類のCSVファイルを開いています。
「sell_order(XRP_JPY).csv」ファイルには「sell_time, qty, price, real_qry, real_price,...」などが格納されています。
「real_qty, real_price」には約定した数量と価格(レート)が格納されます。
「sell_order_sub(XRP_JPY).csv」ファイルには「sell_time, sell_order_id, position_id, real_qty, real_price, stop_loss,...」などが格納されています。
「real_qty」には約定した数量(40)が格納されています。
「stop_loss」には「TRUE」が格納されているのでストップ・ロスが適用されています。
「sell_order_child(XRP_JPY).csv」ファイルには「sell_time, sell_order_id, position_id, real_qty, real_price,...」などが格納されています。
実はこの売り注文は数量「40」で出されていますが、数量が「20」だけ執行された状態でストップ・ロスが適用されています。
この場合、未執行の数量「20」に対してキャンセル処理を行います。
さらに、未執行の売りポジションに対して成行きの売り注文を出して残数の「20」を強制的に決済します。
つまり、ストップ・ロスを適用します。
「sell_order_child(XRP_JPY).csv」のExcelシートの行2は取引所で部分的に執行された売り注文のデータで、
行3はストップ・ロスで強制的に決済したときの売り注文のデータです。
したがってシートの列(B)の「sell_order_id」には異なる売りの注文IDが格納されています。
余談ですが、リアルで取引している状態でこのような条件を検証するのは無理です。
ところが、BOTのシミュレーション機能を使えば簡単に今回のような処理の検証ができます。
図8-3は損益ファイルをExcelで開いた画像です。
「order(XRP_JPY).xlsx」ファイルには「time(B/S), order(B/S), qty(B/S), price(B/S), profit(AMT), profit(%), closed, stop, cancel,..」などの情報が格納されています。
「()」内のBはBuy, SはSellの意味です。
「stop」に「TRUE」が格納されているので、この取引にはストップ・ロスが適用されたことが分かります。
「qty」に執行された数量「40」が格納されていますが、これだと全数ストップ・ロスが適用されたのか、
部分的にストップ・ロスが適用されたのか分かりません。
このような場合は、図8-2の「sell_order_sub(XRP_JPY).csv」と「sell_order_child(XRP_JPY).csv」ファイルを参照します。
-
BuySellクラスにcancel_pending_order()メソッドを追加する
ここではBuySellクラスにcancel_pending_order()メソッドを追加します。
このメソッドは、保留状態になっているポジションを強制的に決済してレバレッジ手数料が加算されるのを回避するときに使用します。
GMOコインの場合、レバレッジ手数料は午前6時を経過したときに加算されます。
したがって、レバレッジ手数料を回避するには午前6時前にポジションを決済させる必要があります。
buy_sell.py:
########################################
def cancel_pending_order(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'cancel_pending_order():'
self.debug.trace_write(f".... {self.gvar.callers_method_name} ")
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast df (master dataframe)
### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order() # PK(buy_time)
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
#################################################
### 【1】READ ALL SELL ORDERs
#################################################
# define sell order list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
### get all sell order
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # ★ unique time
buy_time = sell_df.loc[i, 'buy_time'] # ★ unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell order sub
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
sell_order_id = dfx.loc[ix, 'sell_order_id']
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_order_id}) has been closed: {sell_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_time:%Y-%m-%d %H:%M:%S}) has been closed: {sell_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue # skip closed order
# end of if sell_order_closed:
#################################################################
### 【2】READ ALL SELL ORDER SUB (OPEN OR INCOMPLETE STATUS)
#################################################################
### for sell order sub (open or incomplete status)
# define sell order sub list
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
### get sell order sub info
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : sell sub(many) => buy_sub(one)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = sell_subx_df.loc[j, 'qty']
sub_price = sell_subx_df.loc[j, 'price']
sub_real_qty = sell_subx_df.loc[j, 'real_qty']
sub_real_price = sell_subx_df.loc[j, 'real_price']
sub_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_order_open = False
sub_order_closed = False
sub_order_incomplete = False
if sub_real_qty == 0:
sub_order_open = True
elif sub_qty == sub_real_qty:
sub_order_closed = True
else:
sub_order_incomplete = True
# fake sell order sub ? => SKIP
if sub_sell_order_id.startswith('Fake') :
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id}) ")
continue
# sell order sub has been stopped loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss ▶ {sub_stop_loss=} ")
continue
# cancelled sell order sub ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled: {sub_cancelled=} ")
continue
# closed sell order sub ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed: {sub_order_closed=} ")
continue
### open or incomplete sell order sub
######################################################
### 【3】UPDATE PARTIALLY CLOSED ORDER
######################################################
if sub_order_incomplete:
### add partially closed sell order child
self.update_partially_closed_order()
self.gvar.callers_method_name = 'cancel_pending_order():'
##########################################################################################
### 【4】CANCEL CLOSED BUY ORDER SUB : ★ Sell Order Sub(many) => Buy Order Sub(one) ★
##########################################################################################
### for closed buy order sub
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed buy order sub({sub_buy_order_id}) ")
# increment buy cancel count
self.gvar.buy_cancel_count += 1 # cancel closed buy order
self.coin.buy_cancel_count += 1 # cancel closed buy order
### set a cancelled flag for buy order sub : ★ PK(buy_time + buy_order_id) ★
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
buy_sub_df.loc[find_mask, 'cancelled'] = True
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub(buy_order_id={sub_buy_order_id}, cancelled=True) ")
######################################################
### 【5】CANCEL OPEN/INCOMPLETE SELL ORDER SUB
######################################################
### cancel open or incomplete sell order sub
### cancel sell order sub
status, msg_code, msg_str = self.api.exe_cancel_order(sub_sell_order_id) # ★
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CANCEL{Bcolors.ENDC} open/incomplete sell order sub({sub_sell_order_id}): exe_cancel_order() ▶ {status=} ")
sleep(0.5) # sleep 0.5 sec
##################################################################################################
### 【6】RESELL OUTSTANDING SELL ORDER SUB QTY : EXECUTE SELL CLOSE ORDER MARKET WAIT (FAK)
##################################################################################################
### resell open or incomplete sell order sub with market price
outstanding_qty = sub_qty - sub_real_qty # calculate outstanding qty
new_sell_qty = outstanding_qty
qty = new_sell_qty
if sub_real_price > 0:
new_sell_price = sub_real_price + sub_real_fee
else:
new_sell_price = sub_price
### get the latest close price from the GMO master
# get a column position of the GMO master
mas_latest_close_price = mas_df.iloc[-1]['close'] # get latest close price (GMO)
status = 0
loop_count = 0
while True:
loop_count += 1
# exe_sell_close_order_market_wait(position_id: str, qty: float, price: float, latest_close_price=0.0) -> tuple: # return (status, df, msg_code, msg_str)
status, res_df, msg_code, msg_str = self.api.exe_sell_close_order_market_wait(sub_position_id, qty, new_sell_price, mas_latest_close_price) # ★ FAK(Fill and Kill): new_sell_price_str, mas_latest_close_price_str ares used for fake mode ; 4BTC; 1BTC
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}RESELL({loop_count}){Bcolors.ENDC} open/incomplete sell order sub({sub_sell_order_id}): exe_sell_close_order_market_wait() ▶ {status=} ")
# error ?
if status != 0:
if status == 1 and msg_code == 'ERR-208': # Exceeds the available balance
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC}{Bcolors.FAIL} the available balance exceeded error... ERR-208{Bcolors.ENDC} ")
self.debug.sound_alert(1)
break # exit while true loop and continue to read next sell sub orders
elif status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_sell_close_order_market_wait() time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next sell sub orders
else: # misc error
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_sell_close_order_market_wait() misc error ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next sell order sub
# end of if status == 1 and msg_code == 'ERR-201':
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
##################################################################################################
### 【7】ADD SELL ORDER CHILD ★ DO NOT CREATE RESELL VERSION OF NEW SELL ORDER SUB ★
##################################################################################################
# res_df = response for exe_sell_close_order_market_wait()
# -----------------------------------------------------------
# # Column Dtype
# -----------------------------------------------------------
# 0 executionId int64
# 1 fee float64
# 2 lossGain float64
# 3 orderId object(string) (non unique)
# 4 positionId object(string) (non unique)
# 5 price float64
# 6 settleType object('OPEN' or 'CLOSE')
# 7 side object('BUY' or 'SELL')
# 8 size float64
# 9 symbol object('BTC_JPY')
# 10 timestamp datetime64[ns]
# ----------------------------------------------------------
### IMPORTANT ★ DO NOT CREATE RESELL VERSION OF NEW SELL ORDER SUB ★
### append a new record into the sell order child (★ this is the resell version of sell order child ★)
# define sell order child list
order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response for exe_sell_close_order_market_wait()
for k in range(len(res_df)):
order_id = res_df.loc[k, 'orderId'] # orderId (same orderId) : resell version of orderId
position_id = res_df.loc[k, 'positionId'] # positionId (same positionId)
real_qty = res_df.loc[k, 'size'] # real qty
real_price = res_df.loc[k, 'price'] # real price
real_fee = res_df.loc[k, 'fee'] # real fee
order_id_list.append(order_id) # S9999-999999-999999
position_id_list.append(position_id) # P9999-999999-999999
real_qty_list.append(real_qty) #
real_price_list.append(real_price)
real_fee_list.append(real_fee)
### write a sell order child (resell version) ★
# write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sell_time, order_id, buy_time, sub_buy_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
sum_real_qty = round(sum(real_qty_list), _q_) # round(999.12, _q_)
sum_real_fee = round(sum(real_fee_list), 0) # zero(0)
if sum(real_price_list) > 0:
mean_real_price = round(statistics.mean(real_price_list), _p_) # round(999999.123, _p_)
else:
mean_real_price = 0
sub_real_qty_list.append(sum_real_qty)
sub_real_price_list.append(mean_real_price)
sub_real_fee_list.append(sum_real_fee)
### check exit condition:
sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)
new_sub_sum_real_qty = sub_real_qty + sub_sum_real_qty
# no outstanding sell_qty ?
if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
# increment sell cancel count
self.gvar.sell_cancel_count += 1 # cancel open/incomplete sell order
self.coin.sell_cancel_count += 1 # cancel open/incomplete sell order
break # exit while true loop
else: # outstanding sell_qty exists
pass # skip (do nothing)
# end of if round(sub_qty, digits) == round(new_sub_sum_real_qty, digits):
### partial exe_sell_close_order_market_wait() = continue to sell remaining qty
remaining_qty = new_sell_qty - new_sub_sum_real_qty
qty = remaining_qty
# continue exe_sell_close_order_market_wait() ★
# end of while True:
#################################################################################
### 【8】UPDATE SELL ORDER SUB (real_qty, real_price, real_fee, cancelled)
#################################################################################
sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)
sub_sum_real_fee = round(sum(sub_real_fee_list), 0)
if sum(sub_real_price_list) > 0:
sub_mean_real_price = round(statistics.mean(sub_real_price_list), _p_)
else:
sub_mean_real_price = 0
header_real_qty_list.append(sub_sum_real_qty)
header_real_price_list.append(sub_mean_real_price)
header_real_fee_list.append(sub_sum_real_fee)
### update the sell order sub (real_qty, real_price, real_fee, cancelled)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL}sell order sub not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update
sell_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty # add real_qty (resell version)
sell_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price # update real_price (resell version)
sell_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee # add real_fee (resell version)
sell_sub_df.loc[find_mask, 'cancelled'] = True
ix = dfx.index[0]
new_sub_sum_real_qty = sell_sub_df.loc[ix, 'real_qty']
new_sub_sum_real_fee = sell_sub_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub(sell_order_id={sub_sell_order_id}, real_qty={new_sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={new_sub_sum_real_fee:,.0f}, cancelled=True) ")
# end of if dfx.shape[0] == 0:
#################################################################################
### 【9】UPDATE ORDER CSV FILE (cancelled, order_closed)
#################################################################################
### update order csv file (cancelled=True, order_closed=True) ★
# update_order_info_buy(buy_time, buy_order_id, symbol='BTC_JPY', col_name=any, col_value=any):
# update_order_csv_buy(buy_time: utcnow, buy_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(buy_time + buy_order_id)
self.csv.update_order_csv_buy(sub_buy_time, sub_buy_order_id, col_name_list=['cancelled'], col_value_list=[True]) # PK(buy_time + buy_order_id)
# reset sell order sub list values
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# continue to next sell order sub
# end of for j in range(len(sell_subx_df)):
#################################################################################
### 【10】SAVE BUY/SELL ORDER SUB TO CSV FILES
#################################################################################
### CLOSE (save the buy order sub csv file)
csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order sub csv file)
csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【11】UPDATE SELL ORDER (real_qty, real_price, real_fee)
#################################################################################
if len(header_real_qty_list) > 0:
### update sell order (real_qty, real_price, real_fee) ★
header_sum_real_qty = round(sum(header_real_qty_list), _q_)
header_sum_real_fee = round(sum(header_real_fee_list), 0)
if sum(header_real_price_list) > 0:
header_mean_real_price = round(statistics.mean(header_real_price_list), _p_)
else:
header_mean_real_price = 0
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL}sell order({sell_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order => update
sell_df.loc[find_mask, 'real_qty'] += header_sum_real_qty # add real_qty (resell version)
sell_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (resell version)
sell_df.loc[find_mask, 'real_fee'] += header_sum_real_fee # add real_fee (resell version)
ix = dfx.index[0]
new_header_sum_real_qty = sell_df.loc[ix, 'real_qty']
new_header_sum_real_fee = sell_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={new_header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={new_header_sum_real_fee:,.0f}) ")
# end of if dfx.shape[0] == 0:
# end of if len(header_real_qty_list) > 0:
# reset sell header list values
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
# continue to next sell order... ★
# end of for i in range(len(sell_df)):
#################################################################################
### 【12】SAVE BUY/SELL ORDER TO CSV FILES
#################################################################################
### CLOSE (save the buy order csv file)
csv_file = self.folder_trading_type + f'buy_order({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order(BTC_JPY).csv
buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order csv file)
csv_file = self.folder_trading_type + f'sell_order({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order(BTC_JPY).csv
sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
return
図9-1はデモプログラムの実行結果です。
画面にはBuySellクラスのcancel_pending_order()メソッドが実行されたログが表示されています。
このメソッドは後述するレバレッジ手数料を発生させたくない場合などに使用します。
GMOコインはポジションを保持した状態で午前6時を経過するとレバレッジ手数料を加算します。
BOTはこのレバレッジ手数料を回避するために強制的にポジションを決済する機能をサポートしています。
ポジションを強制的に決済させるには、
Gvarクラスのcancel_pending_ordersプロパティに「True」を設定して自動トレードします。
図9-2は売りのポジションを強制的に決済したときに作成されたCSVファイルと損益ファイルをExcelで表示した画面です。
「sell_order_sub(XRP_JPY).csv」ファイルの「real_qty」には売りの数量「40XRP_JPY」が格納されています。
「sell_order_child(XRP_JPY).csv」ファイルの「real_qty」には執行された数量が「20XRP_JPY」に分割されて格納されています。
Excelシートの行2(上段)はGMOコインが部分的に執行した数量で、
行3(下段)はBOTが強制的に残数「20XRP_JPY」を決済したときの数量です。
ポジションが強制的に決済されたかどうか調べるには、
まず「sell_order_sub(XRP_JPY).csv」の「cancelled」の値をチェックします。
「TRUE」が格納されていれば強制的に決済されています。
この場合、「sell_order_child(XRP_JPY).csv」の「sell_order_id」に異なる売り注文IDが格納されます。
-
BuySellクラスにclose_pending_order()メソッドを追加する
ここではBuySellクラスにclose_pending_order()メソッドを追加します。
このメソッドはレバレッジ手数料を計算させたいときに使用します。
BOTを使用するときは、午前5時~午前6時の時間帯に自動的にこのメソッドが呼ばれます。
close_pending_order()はポジションを保留している注文のレバレッジ手数料を計算して損益ファイルを更新します。
buy_sell.py:
#######################################
def close_pending_order(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'close_pending_order():'
self.debug.trace_write(f".... {self.gvar.callers_method_name} ")
# df = self.coin.master2_df
# mas_df = pd.DataFrame(df) # cast df (master dataframe)
### OPEN (load the order csv file) : order(BTC_JPY).csv
ord_df = self.csv.get_order_csv()
# ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
# buy_df = self.csv.get_buy_order() # PK(buy_time)
# ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
# buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the buy order child log csv file) : buy_order_child(BTC_JPY).csv
buy_ch_df = self.csv.get_buy_order_child() # PK(buy_time + buy_order_id + position_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
# ### OPEN (load the sell order child csv file) : sell_order_child(BTC_JPY).csv
# sell_ch_df = self.csv.get_sell_order_child() # PK(sell_time + sell_order_id + position_id)
#################################################
### 【1】READ ALL SELL ORDER
#################################################
### get all sell orders
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # ★ unique time
buy_time = sell_df.loc[i, 'buy_time'] # ★ unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell sub order log
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell sub order ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
sell_order_id = dfx.loc[ix, 'sell_order_id']
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_order_id}) has been closed: {sell_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_time=:%Y-%m-%d %H:%M:%S}) has been closed: {sell_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue # skip closed sell order
# end of if sell_order_closed:
#################################################################
### 【2】READ ALL SELL ORDER SUB (OPEN OR INCOMPLETE STATUS)
#################################################################
### for sell order sub (open or incomplete status)
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
### get sell order sub info
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : sell sub(many) => buy_sub(one)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = sell_subx_df.loc[j, 'qty']
sub_price = sell_subx_df.loc[j, 'price']
sub_real_qty = sell_subx_df.loc[j, 'real_qty']
sub_real_price = sell_subx_df.loc[j, 'real_price']
sub_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_get_price_count = sell_subx_df.loc[j, 'get_price_count']
sub_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_order_open = False
sub_order_closed = False
sub_order_incomplete = False
if sub_real_qty == 0:
sub_order_open = True
elif sub_qty == sub_real_qty:
sub_order_closed = True
else:
sub_order_incomplete = True
# fake sell order sub ? => SKIP
if sub_sell_order_id.startswith('Fake') :
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id}) ")
continue
# sell order sub has been stopped loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss ▶ {sub_stop_loss=} ")
continue
# cancelled sell order sub ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled: {sub_cancelled=} ")
continue
# closed sell order sub ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed: {sub_order_closed=} ")
continue
### open or incomplete sell order sub
#################################################################
### 【3】CALCULATE LEVERAGE FEE AND UPDATE ORDER CSV FILE
#################################################################
### calculate leverage fee
# get buy order child
find_mask = (buy_ch_df['buy_time'] == sub_buy_time) & (buy_ch_df['buy_order_id'] == sub_buy_order_id) & (buy_ch_df['position_id'] == sub_position_id)
dfx = buy_ch_df[find_mask]
# not found buy order child ?
if dfx.shape[0] == 0:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found buy order child {Bcolors.ENDC} : {sub_buy_time=:%Y-%m-%d %H:%M:%S}, {sub_buy_order_id=}, {sub_position_id=} ")
else: # found buy order child
ix = dfx.index[0]
ch_buy_real_qty = dfx.loc[ix, 'real_qty']
ch_buy_real_price = dfx.loc[ix, 'real_price']
ch_leverage_fee = math.ceil((ch_buy_real_price * ch_buy_real_qty) * 0.0004) # leverage fee (0.04%)
### update order csv file (accumulated_leverage_fee, close_count, close_update_date)
# not empty order csv file ?
if ord_df.shape[0] > 0:
find_mask = ord_df['buy_order_id'] == sub_buy_order_id # B9999-999999-999999
dfx = ord_df[find_mask]
# not found order csv file ?
if dfx.shape[0] == 0:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found order csv file{Bcolors.ENDC} : {sub_buy_order_id=} ")
else: # found order csv file
update_order_csv = False
ix = dfx.index[0]
close_count = ord_df.loc[ix, 'close_count']
close_update_date = ord_df.loc[ix, 'close_update_date']
accumulated_leverage_fee = ord_df.loc[ix, 'accumulated_leverage_fee']
if close_count == 0:
update_order_csv = True
# self.debug.trace_warn(f"DEBUG: update_order_csv={update_order_csv}, close_count={close_count} ")
else: # close_count > 0
now_date_str = f"{dt.datetime.now():%Y%m%d}" # YYYYMMDD
close_update_date_str = f"{close_update_date:%Y%m%d}" # YYYYMMDD
if close_update_date_str != now_date_str:
update_order_csv = True
# self.debug.trace_warn(f"DEBUG: update_order_csv={update_order_csv}, close_count={close_count}, now_date_str={now_date_str}, close_update_date_str={close_update_date_str}, leverage_fee={accumulated_leverage_fee:,.0f} ")
# update_order_csv = True # DEBUG DEBUG DEBUG
# end of if close_count == 0:
if update_order_csv:
# update order csv file
ord_df.loc[find_mask, 'accumulated_leverage_fee'] += ch_leverage_fee
ord_df.loc[find_mask, 'close_count'] += 1
ord_df.loc[find_mask, 'close_update_date'] = dt.datetime.now()
ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
ord_df['close_update_date'] = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)
new_accumulated_leverage_fee = ord_df.loc[ix, 'accumulated_leverage_fee']
new_close_count = ord_df.loc[ix, 'close_count']
new_close_update_date = ord_df.loc[ix, 'close_update_date']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_buy(): {sub_buy_order_id=}, {new_accumulated_leverage_fee=:,.0f}, {new_close_count=}, {new_close_update_date=:%Y-%m-%d %H:%M:%S} ")
# end of if ord_df.shape[0] > 0:
# end of if dfx.shape[0] == 0:
# continue to next sell order sub...
# end of for j in range(len(sell_subx_df)):
# continue to next sell order... ★
# end of for i in range(len(sell_df)):
#################################################################################
### 【4】SAVE ORDER CSV TO CSV FILE
#################################################################################
### CLOSE (save to the order csv file) : order(BTC_JPY).csv
if ord_df.shape[0] > 0:
### CLOSE (order csv file)
csv_file = self.folder_trading_type + f'order({symbol}).csv'
# desktop-pc/bot/buy_sell/order(BTC_JPY).csv
ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file
return
図10-1はデモプログラムの実行結果です。
画面にはclose_pending_order()メソッドが実行されたことがログに表示されています。
図10-2はCSVファイルと損益ファイルをExcelで開いたときの画面です。
「buy_order_child(XRP_JPY).csv」ファイルの「real_qty」には買い注文で執行された数量「40XRP_JPY」が格納されています。
「sell_order_sub(XRP_JPY).csv」ファイルの「real_qty」には売り注文で執行された数量「20XRP_JPY」が格納されています。
売り注文の残数「20XRP_JPY」があるので、GMOコインは午前6時を経過するとレバレッジ手数料を加算します。
「order(XRP_JPY).xlsx」ファイルにはこのレバレッジ手数料が自動的に計算されて格納されます。
Excelのシート「fee(L)」にはレバレッジ手数料の累計が格納されます。
「count(L)」にはレバレッジ手数料が加算された回数が格納されます。
「close(L)」にはレバレッジ手数料が加算された最終日時が格納されます。
ちなみに(L)は「Leverage」の略です。
「profit(AMT)」から「fee(L)」を減算すると純利益が計算できます。
-
BuySellクラスの全てのソースコードを掲載
ここではBuySellクラスの全てのソースコードを掲載しています。
buy_sell.py:
# buy_sell.py
"""
BuySell class
"""
# import the libraries
from time import sleep
import datetime as dt
from datetime import timedelta
import math
import statistics
from decimal import Decimal
import numpy as np
import pandas as pd
from lib.base import Bcolors, Gvar, Coin, Gmo_dataframe
from lib.debug import Debug
from lib.api import Api
from lib.csv import Csv
from lib.trade import Trade
import warnings
warnings.simplefilter('ignore')
##############
class BuySell:
"""
__init__(), __str__(),
reset_restart_process(),
buy_sell(), buy_sell_algorithm_0(), buy_sell_algorithm_1, buy_sell_algorithm_2(), buy_sell_algorithm_3(),...
create_buy_order(), create_sell_order()
update_order(), update_closed_order(), update_partially_closed_order(), update_order_profit()
stop_loss(), stop_loss_0(), stop_loss_1(),...
cancel_pending_order()
close_pending_order()
"""
######################################################
def __init__(self, gvar: object, coin: object) ->None:
self.gvar = gvar # Gvar
self.coin = coin # Coin
self.debug = Debug(gvar) # dependent class
self.api = Api(gvar, coin) # dependent class
self.csv = Csv(gvar, coin) # dependent class
self.trade = Trade(gvar, coin) # dependent class
self.folder_gmo = f'{self.gvar.domain}/{self.gvar.exchange}/' # desktop-pc/bot/
self.folder_trading_type = f'{self.gvar.domain}/{self.gvar.exchange}/{self.coin.trade_type}/' # desktop-pc/bot/buy_sell/
self.debug.print_log(f"{Bcolors.OKGREEN}{self.gvar.exchange.upper()} BuySell({self.gvar.althorithm_id},{self.gvar.buysell_condition}){Bcolors.ENDC} class initiated: {self.coin.symbol}, qty=[{self.coin.qty:,.{self.coin.qty_decimal}f}], rate=[{self.coin.profit_rate:.4f}, {self.coin.stop_loss_rate:.4f}], debug1={self.coin.debug_mode_debug1}, debug2={self.coin.debug_mode_debug2} ")
return
#########################
def __str__(self) -> str:
return f"BuySell({self.folder_gmo=}, {self.folder_trading_type=}, {self.gvar.real_mode=}, {self.gvar.reset_mode=}, {self.gvar.althorithm_id=}, {self.coin.symbol=})"
########################################
def reset_restart_process(self) -> None:
"""
"""
self.gvar.callers_method_name = 'reset_restart_process():'
symbol = self.coin.symbol
# get master df from the coin class
master_df = self.coin.master_df
if master_df.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - master_df is empty {master_df.shape[0]=} return ")
self.debug.sound_alert(3)
return
order_qty = self.coin.qty
mas_df = master_df
# ---------------------------------------------------------
# # Column Dtype
# ---------------------------------------------------------
# 0 price float64
# 1 side object
# 2 size float64
# 3 timestamp datetime64[ns, UTC]
# --------------------------------------------------------
### get latest buy order info from the GMO dataframe (timestamp is sorted by ascending order)
buy_time = mas_df.iloc[0]['timestamp'] # get oldest time
buy_price = mas_df.iloc[0]['price'] # get oldest price
buy_order_id = 'FakeB:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f') # FakeB:MMDD-HHMMSS-999999(milliseconds)
buy_position_id = 'PosiB:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f') # PosiB:MMDD-HHMMSS-999999(milliseconds)
# round time
freq = '1T'
buy_time = buy_time.round(freq=freq) # round x minutes (where x is 1,4,10,15,30)
buy_time = buy_time.round(freq='S') # round seconds
buy_time = buy_time - timedelta(hours=1) # substract 1 hour
### get sell order info from the GMO dataframe
sell_time = buy_time # + timedelta(seconds=5)
sell_price = self.trade.get_sell_price(buy_price)
sell_order_id = 'FakeS:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f') # FakeS:MMDD-HHMMSS-999999(milliseconds)
sell_position_id = 'PosiS:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f') # PosiS:MMDD-HHMMSS-999999(milliseconds)
# reset mode ?
if self.gvar.reset_mode or self.coin.master_csv_not_found:
if self.gvar.reset_mode:
self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} reset): delete_allfiles() ▶ add a fake data into the buy/sell order csv files...")
else:
self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} restart): master_csv_not_found(True) ▶ add a fake data into the buy/sell order csv files...")
### 1) add a fake data into the buy order, buy order sub files
# write_buy_order(buy_time: utcnow, sell_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_buy_order(buy_time, sell_time, order_qty, buy_price, order_qty, buy_price, 0.0)
# write_buy_order_sub(buy_time: utcnow, sell_time: utcnow, buy_order_id: str, sell_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_buy_order_sub(buy_time, sell_time, buy_order_id, sell_order_id, buy_position_id, order_qty, buy_price, order_qty, buy_price, 0.0)
### 2) add a fake data into the sell order, sell sub_order log file
# write_sell_order(sell_time: utcnow, buy_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order(sell_time, buy_time, order_qty, sell_price, order_qty, sell_price, 0.0)
# write_sell_order_sub(sell_time: utcnow, buy_time: utcnow, sell_order_id: str, buy_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order_sub(sell_time, buy_time, sell_order_id, buy_order_id, sell_position_id, order_qty, sell_price, order_qty, sell_price, 0.0)
else: # restart mode
self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} restart): skip close pending orders for restart...")
# end of if self.gvar.reset_mode:
return
##############################################
def buy_sell(self, algorithm_id: int) -> None:
"""
"""
if algorithm_id <= 3:
eval(f'self.buy_sell_algorithm_{str(algorithm_id)}()') # call 'self.buy_sell_algorithm_?() method
else:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid Algorithm_id {algorithm_id=} {Bcolors.ENDC} - skip buy_sell ")
self.debug.sound_alert(3)
# if algorithm_id == 0:
# self.buy_sell_algorithm_0()
# elif algorithm_id == 1:
# self.buy_sell_algorithm_1()
# elif algorithm_id == 2:
# self.buy_sell_algorithm_2()
# elif algorithm_id == 3:
# self.buy_sell_algorithm_3()
# else:
# self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid Algorithm_id {algorithm_id=} {Bcolors.ENDC} - skip buy_sell ")
# self.debug.sound_alert(3)
return
#######################################
def buy_sell_algorithm_0(self) -> None:
"""
"""
self.gvar.callers_method_name = 'buy_sell(0):'
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast dataframe()
### filter buy master
filter_mask = mas_df['side'] == 'BUY'
mas_buy_df = mas_df[filter_mask]
# mas_buy_df is less than trend_search_count ? => SYTEM ERROR
if mas_buy_df.shape[0] < self.coin.trend_search_count: # < 3
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_buy_df({mas_buy_df.shape[0]}) is less than {self.coin.trend_search_count}. skip buy_sell() ")
self.debug.sound_alert(3)
return
### filter sell master
filter_mask = mas_df['side'] == 'SELL'
mas_sell_df = mas_df[filter_mask]
# mas_sell_df is less than trend_search_count ? => SYTEM ERROR
if mas_sell_df.shape[0] < self.coin.trend_search_count: # < 3
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_sell_df({mas_sell_df.shape[0]}) is less than {self.coin.trend_search_count}. skip buy_sell() ")
self.debug.sound_alert(3)
return
self.debug.trace_write(f".... {self.gvar.callers_method_name} buy_sell_condition({self.gvar.buysell_condition}), mas_buy_df.shape({mas_buy_df.shape[0]}), mas_sell_df.shape({mas_sell_df.shape[0]}) ")
# mas_df:
# -------------------------------------------------------------
# # Column Dtype
# -------------------------------------------------------------
# 0 time datetime64[ns, UTC]
# 1 open float64
# 2 close float64
# 3 low float64
# 4 high float64
# 5 price float64
# 6 buy_price float64
# 7 sell_price float64
# 8 side object
# 9 size float64
# 10 symbol object
# ---------------------------------------------------------------
position = 'buy or sell'
### load buy order / buy order sub files
buy_df = self.csv.get_buy_order() # ★
buy_sub_df = self.csv.get_buy_order_sub() # ★
### load sell order / sell order sub files
sell_df = self.csv.get_sell_order() # ★
sell_sub_df = self.csv.get_sell_order_sub() # ★
### update the position depending on buy/sell sub order log files
### get the last buy sub order info
last_buy_time = buy_sub_df.iloc[-1]['buy_time']
last_sell_time = buy_sub_df.iloc[-1]['sell_time']
last_buy_real_qty = buy_sub_df.iloc[-1]['real_qty']
last_buy_real_price = buy_sub_df.iloc[-1]['real_price']
### get the last sell order (pair of the buy order)
# find the pair of the sell order
find_mask = sell_df['sell_time'] == last_sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
# self.debug.print_log(f".... {bcolors.WARNING}{self.gvar.callers_method_name}{bcolors.ENDC} ▶ sell order not found: {last_sell_time=:%Y-%m-%d %H:%M:%S} ")
last_sell_real_qty = 0
else: ### found pair of the sell order
# get sell order real_qty
ix = dfx.index[0]
last_sell_real_qty = dfx.loc[ix, 'real_qty']
# closed order ?
if round(last_buy_real_qty, _q_) == round(last_sell_real_qty, _q_):
position = 'buy' # sell order not required => position(BUY)
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}NEW BUY ORDER REQUEST{Bcolors.ENDC} ")
else: # not found sell order or incomplete sell order
return # skip return
# end of if round(last_buy_real_qty, digits) == round(last_sell_real_qty, digits):
# buy position ?
if position.startswith('buy'):
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}BUY{Bcolors.ENDC} position (long position) ")
### BUY position only
### check buy condition and create a buy order
# get the latest time & buy_price from GMO master dataframe
mas_time = mas_buy_df.iloc[-1]['time'] # gmo time ★
mas_price = mas_buy_df.iloc[-1]['price'] # gmo price ★
while True:
###########################################################
### 1: check duplicate buy order
###########################################################
# mas_time(gmo) is less than or equal to last_buy_time ? => SKIP
if mas_time <= last_buy_time:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(1){Bcolors.ENDC} mas_time({mas_time:%Y-%m-%d %H:%M:%S}) is less than or equal to last_buy_time({last_buy_time:%Y-%m-%d %H:%M:%S}) ")
break # exit while true loop
# duplicate buy order ? => SKIP
# find_order(df: pd.DataFrame, col: str, val=any) -> bool:
if self.trade.find_order(buy_df, col='buy_time', val=mas_time):
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(2){Bcolors.ENDC} duplicate buy order: buy_time={mas_time:%Y-%m-%d %H:%M:%S}) ")
break # exit while true loop
### no duplicate buy order
###########################################################
### 2: execute buy order (MARKET) & sell order (LIMIT)
###########################################################
qty = self.coin.qty
### execute buy order with qty (MARKET)
# create_buy_order(buy_time: utcnow, qty: float, price: float) -> int: # return status
status = self.create_buy_order(mas_time, qty, mas_price) # ★ price is used for fake mode: execute buy order with market price
if status == 0:
# reload buy order sub file
buy_sub_df = self.csv.get_buy_order_sub()
# get the last buy order sub info
last_buy_time = buy_sub_df.iloc[-1]['buy_time']
# get the latest sell price from gmo master
ms_price = mas_sell_df.iloc[-1]['price']
### execute sell order (LIMIT)
# create_sell_order(sell_time: utcnow, ms_price: float, buy_time: utcnow) -> int:
status = self.create_sell_order(mas_time, mas_price, last_buy_time) # ★
break # exit while true loop
# end of while True:
return
#######################################
def buy_sell_algorithm_1(self) -> None:
"""
"""
self.gvar.callers_method_name = 'buy_sell(1):'
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}buy_sell_algorithm_2() has not been implemented yet{Bcolors.ENDC} - skip buy_sell(2) ")
self.debug.sound_alert(3)
return
#######################################
def buy_sell_algorithm_2(self) -> None:
"""
"""
self.gvar.callers_method_name = 'buy_sell(2):'
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}buy_sell_algorithm_2() has not been implemented yet{Bcolors.ENDC} - skip buy_sell(2) ")
self.debug.sound_alert(3)
return
#######################################
def buy_sell_algorithm_3(self) -> None:
"""
"""
self.gvar.callers_method_name = 'buy_sell(3):'
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}buy_sell_algorithm_3() has not been implemented yet{Bcolors.ENDC} - skip buy_sell(3) ")
self.debug.sound_alert(3)
return
##########################################################################################
def create_buy_order(self, buy_time: dt.datetime.utcnow, qty: float, price: float) -> int: # return status : price (latest buy market price)
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'create_buy_order():'
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}) ")
status = 0
# reset buy/sell position count
self.coin.position_count_0_10 = 0
self.coin.position_count_11_30 = 0
self.coin.position_count_31_50 = 0
self.coin.position_count_51_70 = 0
self.coin.position_count_71_90 = 0
self.coin.position_count_91_999 = 0
################################################
### 【1】CHECK DUPLICATE BUY ORDER
################################################
### check duplicate buy order
# OPEN (load the buy order file) : buy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order()
# find a buy order
find_mask = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# duplicate buy order ?
if dfx.shape[0] > 0:
status = 9 # set internal status code
self.debug.trace_write(f".... {self.gvar.callers_method_name} duplicate buy order ({buy_time=:%Y-%m-%d %H:%M:%S}) ▶ {status=} error return... ")
return status # error return
# reset sell_position_list
self.coin.sell_position_list = []
#############################################################
### 【2】EXECUTE BUY ORDER MARKET (FAK: Fill And Kill)
#############################################################
### execute buy order with market price : FAK (Fill And Kill)
buy_qty = qty # save original buy qty ★
# define buy order header list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
status = 0
loop_count = 0
while True:
loop_count += 1
# exe_buy_order_market_wait(qty: float, price: float, latest_close_price=0.0) -> tuple:
status, res_df, msg_code, msg_str = self.api.exe_buy_order_market_wait(buy_qty, price) # ★ FAK (Fill And Kill) : price is used for fake mode
res_df = pd.DataFrame(res_df) # cast
self.debug.print_log(f".... {self.gvar.callers_method_name} while({loop_count}): exe_buy_order_market_wait() ▶ {status=}, {res_df.shape=} ")
####################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_buy_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug1:
# ★ create two buy order subs for each buy order header ★
# update_get_price_response_for_debug1_1(res_df: pd.DataFrame, loop_count: int) -> pd.DataFrame:
res_df = self.trade.update_get_price_response_for_debug1_1(res_df, loop_count)
# loop_count == 1: return 2 rows => add 2 buy order subs
# loop_count == 2: return 1 row => add 1 buy order sub
# end of if not self.coin.real_mode:
###################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_buy_order()
# exe_buy_order_market_wait() error ?
if status != 0:
if status == 1 and msg_code == 'ERR-201': # Trading margin is insufficient ?
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {Bcolors.FAIL}the trading margin is insufficient error: ERR-201{Bcolors.ENDC} ")
self.debug.sound_alert(2)
break # exit while true loop => error return
elif status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}, {price=:,.{_p_}f})): ▶ {Bcolors.FAIL}get_price() time out error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop => error return
else: # misc error
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}, {price=:,.{_p_}f})): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop => error return
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single row or multiple rows)
################################################################################################################
### 【3】ADD A BUY ORDER CHILD
################################################################################################################
# res_df = get_price() response for exe_buy_order_market_wait()
# ---------------------------------------------------------
# # Column Dtype
# ---------------------------------------------------------
# 0 executionId object
# 1 fee float64
# 2 lossGain float64
# 3 orderId object
# 4 positionId object ★
# 5 price float64
# 6 settleType object (OPEN or CLOSE)
# 7 side object (BUY or SELL)
# 8 size float64
# 9 symbol object (BTC_JPY)
# 10 timestamp datetime64[ns, UTC]
# ---------------------------------------------------------
### write a buy order child
# define buy order child list
buy_order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response df for exe_buy_order_market_wait(FAK)
for k in range(len(res_df)):
buy_order_id = res_df.loc[k, 'orderId'] # buy order id (same order id)
position_id = res_df.loc[k, 'positionId'] # position id (unique id)
real_qty = res_df.loc[k, 'size'] # real qty ★
real_price = res_df.loc[k, 'price'] # real price ★
real_fee = res_df.loc[k, 'fee'] # real fee
buy_order_id_list.append(buy_order_id) # B9999-999999-999999
position_id_list.append(position_id) # P9999-999999-999999
real_qty_list.append(real_qty) # 10, 10, 20 = 40
real_price_list.append(real_price) # XRP_JPY(64.470)
real_fee_list.append(real_fee) # zero(0)
# write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_sell_time_ = buy_time; _sell_order_id_ = 'Dummy'; _qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(buy_time, buy_order_id, _sell_time_, _sell_order_id_, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
first_buy_order_id = buy_order_id_list[0] # same order ids
position_ids_by_semicolon = '' # 'positionId1;positionId2;positionId3'
for k, position_id_x in enumerate(position_id_list):
if k == 0:
position_ids_by_semicolon += position_id_x
else:
position_ids_by_semicolon += ';' + position_id_x
sub_sum_real_qty = round(sum(real_qty_list), _q_) # XRP_JP(9999)
sub_sum_real_fee = round(sum(real_fee_list), 0) # zero(0)
if sum(real_price_list) > 0:
sub_mean_real_price = round(statistics.mean(real_price_list), _p_) # XRP_JPY(99.123)
else:
sub_mean_real_price = 0
header_real_qty_list.append(sub_sum_real_qty)
header_real_price_list.append(sub_mean_real_price)
header_real_fee_list.append(sub_sum_real_fee)
###################################################################################################################
### 【4】ADD A BUY ORDER SUB (buy_time, sell_time,...) ★ sell_time <= buy_time
###################################################################################################################
### write a buy order sub
# write_buy_order_sub(buy_time: utcnow, sell_time: utcnow, buy_order_id: str, sell_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_sell_time_ = buy_time; _sell_order_id_ = 'Dummy'; _qty_ = sub_sum_real_qty # positionId = 'positionId1;positionId2;positionId3'
self.csv.write_buy_order_sub(buy_time, _sell_time_, first_buy_order_id, _sell_order_id_, position_ids_by_semicolon, _qty_, price, sub_sum_real_qty, sub_mean_real_price, sub_sum_real_fee) # ★
### check exit condition
header_sum_real_qty = round(sum(header_real_qty_list), _q_) # XRP_JPY(9999)
# self.debug.trace_write(f".... {self.gvar.callers_method_name} while({loop_count}): buy_qty={buy_qty}, header_sum_real_qty={header_sum_real_qty} ")
############################## TEMP TEMP TEMP
# temp_qty = round(qty, _q_)
# temp_real_qty = round(header_sum_real_qty, _q_)
# self.debug.trace_warn(f".... DEBUG: {temp_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
############################# TEMP TEMP TEMP
# no outstanding buy_qty ?
if round(qty, _q_) == round(header_sum_real_qty, _q_):
# increment buy count
self.gvar.buy_count += 1
self.coin.buy_count += 1
break # exit while true loop
else: # outstanding buy_qty exists
pass # skip (do nothing)
# end of if round(buy_qty, _q_) == round(header_sum_real_qty, _q_):
### partial exe_buy_order_market_wait() was executed => continue to buy remaining qty
remaining_qty = buy_qty - header_sum_real_qty # calculate new buy qty
buy_qty = remaining_qty # update new buy qty
# continue exe_buy_order_market_wait() ★
# end of while True:
#################################################################################
### 【5】ADD A BUY ORDER HEADER (buy_time, sell_time) ★ sell_time <= buy_time
#################################################################################
### update buy order header(real_qty, real_price, real_fee)
header_sum_real_qty = round(sum(header_real_qty_list), _q_) # XRP_JPY(9999)
header_sum_real_fee = round(sum(header_real_fee_list), 0) # zero(0)
if sum(header_real_price_list) > 0:
header_mean_real_price = round(statistics.mean(header_real_price_list), _p_) # XRP_JPY(99.123)
else:
header_mean_real_price = 0
### write a buy order
# write_buy_order(buy_time: utcnow, sell_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_sell_time_ = buy_time
self.csv.write_buy_order(buy_time, _sell_time_, qty, price, header_sum_real_qty, header_mean_real_price, header_sum_real_fee)
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_order() ▶ {status=} normal return... ")
return status
#################################################################################################################
def create_sell_order(self, sell_time: dt.datetime.utcnow, ms_price: float, buy_time: dt.datetime.utcnow) -> int: # return status : ms_price (latest market sell price)
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'create_sell_order():'
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, {ms_price=:,.{_p_}f}, {buy_time=:%Y-%m-%d %H:%M:%S}) ")
status = 0
################################################
### 【1】CHECK DUPLICATE SELL ORDER
################################################
### check duplicate sell order
# get the sell order : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order()
# find a sell order
find_mask = sell_df['sell_time'] == sell_time # PK(sell_time)
dfx = sell_df[find_mask]
# duplicate sell order ?
if dfx.shape[0] > 0:
status = 9 # set internal status code
self.debug.trace_write(f".... {self.gvar.callers_method_name} duplicate sell order ({sell_time=:%Y-%m-%d %H:%M:%S}) ▶ {status=} error return... ")
return status # error return
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub()
### OPEN (load the buy order child csv file ) : buy_order_child(BTC_JPY).csv
buy_ch_df = self.csv.get_buy_order_child()
######################################################################
### 【2】READ ALL BUY ORDER SUBs (filter by buy_time)
######################################################################
# define sell order sub list
sub_qty_list = []
sub_price_list = []
# filter the buy order sub by buy_time
filter_mask = buy_sub_df['buy_time'] == buy_time
buy_subx_df = buy_sub_df[filter_mask]
if buy_subx_df.shape[0] > 0:
buy_subx_df.reset_index(inplace=True)
# get all buy order sub filtered by buy_time
for i in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[i, 'buy_time'] # non unique
sub_buy_order_id = buy_subx_df.loc[i, 'buy_order_id'] # unique id
sub_qty = buy_subx_df.loc[i, 'qty']
sub_price = buy_subx_df.loc[i, 'price']
sub_real_qty = buy_subx_df.loc[i, 'real_qty']
sub_real_price = buy_subx_df.loc[i, 'real_price']
sub_real_fee = buy_subx_df.loc[i, 'real_fee']
#################################################################################
### 【3】READ ALL BUY ORDER CHILD (filter by buy_time + buy_order_id)
#################################################################################
# define sell order child list
ch_buy_order_id_list = []
ch_sell_order_id_list = []
ch_position_id_list = []
ch_qty_list = []
ch_price_list = []
### filter the buy order child by buy_time + buy_order_id
# buy order child is empty ?
if buy_ch_df.shape[0] == 0:
buy_chx_df = pd.DataFrame()
else: # filter buy order child
filter_mask = (buy_ch_df['buy_time'] == buy_time) & (buy_ch_df['buy_order_id'] == sub_buy_order_id)
buy_chx_df = buy_ch_df[filter_mask]
if buy_chx_df.shape[0] > 0:
buy_chx_df.reset_index(inplace=True)
# end of if buy_ch_df.shape[0] == 0:
# get all buy order child filtered by buy_time + buy_order_id
for j in range(len(buy_chx_df)): # ★ buy_chx_df ★
### get buy order child info
ch_buy_time = buy_chx_df.loc[j, 'buy_time'] # non unique time
ch_buy_order_id = buy_chx_df.loc[j, 'buy_order_id'] # non unique id (same buy_order_id)
ch_position_id = buy_chx_df.loc[j, 'position_id'] # unique id
ch_qty = buy_chx_df.loc[j, 'qty']
ch_price = buy_chx_df.loc[j, 'price']
ch_real_qty = buy_chx_df.loc[j, 'real_qty']
ch_real_price = buy_chx_df.loc[j, 'real_price']
ch_real_fee = buy_chx_df.loc[j, 'real_fee']
#############################################################################################
### 【4】EXECUTE SELL CLOSE ORDER LIMIT (FAS:Fill And Store) FOR EACH BUY ORDER CHILD
#############################################################################################
sell_qty = ch_real_qty
sell_price = self.trade.get_sell_price(ch_real_price, trace=True)
# market price is greater than sell price
if ms_price > sell_price:
sell_price = ms_price
### execute a sell close order with limit(FAS)
# exe_sell_close_order_limit(position_id: str, qty: float, price: float) -> float:
status, res_sell_order_id, msg_code, msg_str = self.api.exe_sell_close_order_limit(ch_position_id, sell_qty, sell_price) # ★
# res_sell_order_id => 'S9999-999999-999999'
# exe_sell_close_order_limit() error ?
if status != 0:
if status == 1 and msg_code == 'ERR-208': # Exceeds the available balance
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}the available balance exceeded ERR-208: continue...{Bcolors.ENDC} ")
self.debug.sound_alert(2)
continue # continue to next buy order child
else: # misc error
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
self.debug.sound_alert(2)
continue # continue to next buy order child
# end of if status != 0:
### no error for exe_sell_close_order_limit()
################################################################################################################
### 【5】ADD A SELL ORDER SUB
################################################################################################################
### write a sell order sub
# write_sell_order_sub(sell_time: utcnow, buy_time: utcnow, sell_order_id: str, buy_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order_sub(sell_time, buy_time, res_sell_order_id, ch_buy_order_id, ch_position_id, sell_qty, sell_price)
ch_buy_order_id_list.append(ch_buy_order_id)
ch_sell_order_id_list.append(res_sell_order_id)
ch_position_id_list.append(ch_position_id)
ch_qty_list.append(sell_qty)
ch_price_list.append(sell_price)
# continue to next buy order child
# end of for j in range(len(buy_chx_df)):
ch_sum_qty = round(sum(ch_qty_list), _q_)
if sum(ch_price_list) > 0:
ch_mean_price = round(statistics.mean(ch_price_list), _p_)
else:
ch_mean_price = 0
sub_qty_list.append(ch_sum_qty)
sub_price_list.append(ch_mean_price)
#####################################################################################
### 【6】UPDATE BUY ORDER SUB (sell_order_id, sell_time)
#####################################################################################
sell_order_ids_by_semicolon = '' # 'SellOrderID1;SellOrderID2;SellOrderID3'
for k, sell_order_id_x in enumerate(ch_sell_order_id_list):
if k == 0:
sell_order_ids_by_semicolon += sell_order_id_x
else:
sell_order_ids_by_semicolon += ';' + sell_order_id_x
### update buy order sub (sell_order_id, sell_time)
find_mask = (buy_sub_df['buy_time'] == buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}(): {Bcolors.FAIL} buy order sub not found ▶ {sub_buy_order_id=} {Bcolors.ENDC} ")
else: # found buy order sub => update sell_order_id, sell_time
buy_sub_df.loc[find_mask, 'sell_order_id'] = sell_order_ids_by_semicolon # 'SellOrderID1;SellOrderID2;SellOrderID3'
buy_sub_df.loc[find_mask, 'sell_time'] = sell_time
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({buy_time=:%Y-%m-%d %H:%M:%S}, {sub_buy_order_id=}, {sell_order_ids_by_semicolon=}, {sell_time=:%Y-%m-%d %H:%M:%S}) ")
# end of if dfx.shape[0] == 0:
# continue to next buy order sub
# end of for i in range(len(buy_subx_df)):
#################################################################################
### 【7】SAVE BUY ORDER SUB TO CSV FILE
#################################################################################
### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
################################################################################
### 【8】ADD A SELL ORDER
################################################################################
### update sell order (real_qty, real_price, real_fee) ★
sub_sum_qty = round(sum(sub_qty_list), _q_)
if sum(sub_price_list) > 0:
sub_mean_price = round(statistics.mean(sub_price_list), _p_)
else:
sub_mean_price = 0
# write_sell_order(sell_time: utcnow, buy_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
self.csv.write_sell_order(sell_time, buy_time, sub_sum_qty, sub_mean_price)
################################################################################
### 【9】UPDATE A BUY ORDER (sell_time)
################################################################################
# update_buy_order(buy_time: utcnow, col_name_list: list, col_value_list: list) -> None: # PK(buy_time)
self.csv.update_buy_order(buy_time, col_name_list=['sell_time'], col_value_list=[sell_time])
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_sell_order() ▶ {status=} normal return... ")
return status
###############################
def update_order(self) -> None:
"""
"""
########################################################################
### 【1】UPDATE CLOSED SELL ORDER
########################################################################
### update closed sell order
self.update_closed_order()
########################################################################
### 【2】UPDATE ORDER PROFIT (PART) : first_pass(True)
########################################################################
### update order profit
self.update_order_profit(first_pass=True)
########################################################################
### 【3】CHECK STOP LOSS : gvar.stoploss_method_id : 0, 1,...
########################################################################
### check latest price(GMO) and stop loss price
if self.gvar.stop_loss:
self.stop_loss(self.gvar.stoploss_method_id) # 0, 1,...
########################################################################
### 【4】CANCEL PENDING ORDERS OR CLOSE PENDING ORDERS
########################################################################
### cancel pending orders
# cancel pending order ? => call cancel_pending_orders() method
if self.gvar.cancel_pending_orders:
self.cancel_pending_order()
elif self.gvar.close_pending_orders: # close pending orders (current time is between 05:00AM - 06:00AM)
self.close_pending_order()
########################################################################
### 【5】 UPDATE ORDER PROFIT (PART2) : first_pass(False)
########################################################################
### update the order file ( order(BTC).csv )
if self.gvar.stop_loss or self.gvar.cancel_pending_orders or self.gvar.close_pending_orders:
self.update_order_profit(first_pass=False)
# if self.gvar.stop_loss:
# self.update_order_profit(first_pass=False)
# elif self.coin.cancel_pending_orders: # cancel pending order by coin (one time request)
# self.update_order_profit(first_pass=False)
# elif self.gvar.close_pending_orders: # close pending orders (current time is between 05:00AM - 06:00AM)
# self.update_order_profit(first_pass=False)
return
######################################
def update_closed_order(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'update_closed_order():'
order_completed = self.trade.get_order_status(exclude=False) # include stop_loss, cancelled, closed order info
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast df (master dataframe)
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}last order completed({order_completed}){Bcolors.ENDC}, mas_df.shape({mas_df.shape[0]}), cancel({self.gvar.cancel_pending_orders}) ")
# mas_df is empty ? => SYTEM ERROR
if mas_df.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_df({mas_df.shape[0]}) is empty. skip update_order() ")
self.debug.sound_alert(3)
return
### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order() # PK(buy_time)
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
# get_orderbooks_try() -> tuple: # return (sell_df, buy_df)
sell_bk, buy_bk = self.api.get_orderbooks_try()
# sleep(0.5)
sell_bk_df = pd.DataFrame(sell_bk) # ascending order of price [-1, price] : low to high price 100, 110, 120, 130, 140, 150
buy_bk_df = pd.DataFrame(buy_bk) # descending order of price iloc[0, price] : high to low price 150, 140, 130, 120, 110, 100
# not empty order books ?
if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:
# get latest sell/buy price from the order books
bk_sell_price = sell_bk_df.iloc[0]['price'] # get lowest sell price : 100
bk_buy_price = buy_bk_df.iloc[0]['price'] # get highest buy price : 150
else:
bk_sell_price = 0.0
bk_buy_price = 0.0
# incomplete sell order (pending sell order) ?
if not order_completed:
### print GMO orderbooks info (sell price, buy price)
# # get_orderbooks_try() -> tuple: # return (sell_df, buy_df)
# sell_bk, buy_bk = self.api.get_orderbooks_try()
# sleep(0.5)
# sell_bk_df = pd.DataFrame(sell_bk) # ascending order of price [-1, price] : low to high price 100, 110, 120, 130, 140, 150
# buy_bk_df = pd.DataFrame(buy_bk) # descending order of price iloc[0, price] : high to low price 150, 140, 130, 120, 110, 100
# not empty order books ?
if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:
# get latest sell/buy price from the order books
bk_sell_price = sell_bk_df.iloc[0]['price'] # get lowest sell price : 100
bk_buy_price = buy_bk_df.iloc[0]['price'] # get highest buy price : 150
# get the last sell price from the sell order sub
last_sell_price = sell_sub_df.iloc[-1]['price'] # 120
# get sell position number (buy => sell)
filter_mask = sell_bk_df['price'] < last_sell_price # < 120
dfx = sell_bk_df[filter_mask] # 100, 110
sell_position = dfx.shape[0] # 2
# update sell position count
if sell_position >= 91:
self.coin.position_count_91_999 += 1
elif sell_position >= 71:
self.coin.position_count_71_90 += 1
elif sell_position >= 51:
self.coin.position_count_51_70 += 1
elif sell_position >= 31:
self.coin.position_count_31_50 += 1
elif sell_position >= 11:
self.coin.position_count_11_30 += 1
else:
self.coin.position_count_0_10 += 1
self.coin.sell_position_list.append(sell_position) # append sell_position : coin.sell_position=[10, 8, 5, 3, 1, 0]
self.debug.trace_write(f".... {self.gvar.callers_method_name}{Bcolors.WARNING} {sell_position=}{Bcolors.ENDC} => close condition [{last_sell_price=:,.{_p_}f} <= {bk_buy_price=:,.{_p_}f}] ") # 120 <= 150
reverse_sell_position_list = self.coin.sell_position_list[::-1] # [0, 1, 3, 5, 8, 10]
if len(reverse_sell_position_list) < 21:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_sell_position_list=} ")
else:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_sell_position_list[0:21]=} ")
else: # empty order books
self.coin.sell_position_list = [] # reset sell position list
# end of if not order_completed:
#################################################
### 【1】READ ALL SELL ORDER
#################################################
### get all sell order
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # unique time
buy_time = sell_df.loc[i, 'buy_time'] # unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell order sub
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell order sub ?
if dfx.shape[0] > 0:
dfx.reset_index(inplace=True)
sell_order_id = str(dfx.loc[0, 'sell_order_id'])
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_order_id}) has been closed: sell_order_closed={sell_order_closed} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_time:%Y-%m-%d %H:%M:%S}) has been closed: sell_order_closed={sell_order_closed} ")
# end of if dfx.shape[0] > 0:
continue # continue to next sell order
# end of if sell_order_closed:
### open or incomplete sell order
##########################################################################################
### 【2】READ ALL SELL ORDER SUB & EXECUTE GET_PRICE() FOR EACH SELL ORDER SUB
##########################################################################################
### for sell order sub : use pandas dataframe groupby()
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order : 2 rows
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
sell_order_sub_seq = j + 1 # 1, 2, 3,....
### get sell order sub info
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★ (unique id) used for get_price() api
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★ (non unique id)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # (unique id)
sub_sell_qty = sell_subx_df.loc[j, 'qty'] # 10
sub_sell_price = sell_subx_df.loc[j, 'price']
sub_sell_real_qty = sell_subx_df.loc[j, 'real_qty'] # 10
sub_sell_real_price = sell_subx_df.loc[j, 'real_price']
sub_sell_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_sell_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_sell_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_sell_order_open = False
sub_sell_order_closed = False
sub_sell_order_incomplete = False
if sub_sell_real_qty == 0:
sub_sell_order_open = True
elif sub_sell_qty == sub_sell_real_qty:
sub_sell_order_closed = True
else:
sub_sell_order_incomplete = True
# fake sell sub order ? => SKIP
if sub_sell_order_id.startswith('Fake'):
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id})... ")
continue
# cancelled sell sub order ? => SKIP
if sub_sell_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled ▶ sub_sell_cancelled={sub_sell_cancelled} ")
continue
# stop loss sell sub order ? => SKIP
if sub_sell_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss ▶ sub_sell_stop_loss={sub_sell_stop_loss} ")
continue
# closed sell sub order ? => SKIP
if sub_sell_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed ▶ sub_sell_order_closed={sub_sell_order_closed} ")
continue
### open or incomplete sell order sub
############################################################
### 【3】UPDATE SELL ORDER SUB (get_price_count+1)
############################################################
### update sell order sub (get_price_count += 1) ★
# DO NOT USE: self.csv.update_sell_order_sub_count(sell_time, sell_order_id)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub_count({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub
sell_sub_df.loc[find_mask, 'get_price_count'] += 1 # increment get_price_count by 1
ix = dfx.index[0]
sub_sell_get_price_count = sell_sub_df.loc[ix, 'get_price_count']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub_count({sub_sell_order_id=}, {sub_sell_get_price_count=}) ")
# end of if dfx.shape[0] == 0:
### get order_status
qty = sub_sell_qty
price = sub_sell_price
# get_order_status_try(order_id: str, qty: float, price: float) ->tuple: # return (status, order_status)
status, order_status = self.api.get_order_status_try(sub_sell_order_id, qty, price) # ★ qty, price are used for fake mode only
# status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)
### status = 0; order_status = 'EXPIRED' # DEBUG DEBUG DEBUG
# get_order_status() error ?
if status != 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")
continue # continue to next sell order sub
### get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
# pending exe_sell_close_order_limit() request ? : ORDERED(指値注文有効中), EXECUTE(全量約定)
if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status_try({sub_sell_order_id}, {qty:,.{_q_}f}, {price:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_sell_close_order_limit() request ")
continue # continue to next sell order sub
############################################# DEBUG DEBUG DEBUG
if self.coin.debug_mode_gmo_stop_loss:
order_status = 'EXPIRED'
############################################# DEBUG DEBUG DEBUG
### executed exe_sell_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'
if order_status in 'EXPIRED,CANCELED':
### update sell order, sell order sub and order csv file
if order_status == 'CANCELED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}): {Bcolors.FAIL}the order has been cancelled by GMO. please confirm the reason{Bcolors.ENDC} ")
self.debug.sound_alert(3)
continue # continue to next sell order sub
if order_status == 'EXPIRED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")
self.debug.sound_alert(3)
###########################################
### 1: close sell order sub
###########################################
# update a sell order sub (update real_qty, real_price, stop_loss)
# DO NOT USE: self.csv.update_sell_order_sub(sell_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update sell order sub (real_qty, real_price, real_fee, stop_loss)
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
sell_stop_loss = True
sell_sub_df.loc[find_mask, 'real_qty'] = sub_sell_qty
sell_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
sell_sub_df.loc[find_mask, 'real_fee'] = gmo_loss_cut_fee # gmo loss cut fee
sell_sub_df.loc[find_mask, 'stop_loss'] = sell_stop_loss
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, real_qty={sub_sell_qty:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {sell_stop_loss=}) ")
# end of if dfx.shape[0] == 0:
#######################################
### 2: close sell order
#######################################
# update sell order (real_qty, real_price, real_fee)
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL} ({sell_time=:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
sell_df.loc[find_mask, 'real_qty'] = sell_qty
sell_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
sell_df.loc[find_mask, 'real_fee'] = gmo_loss_cut_fee # gmo loss cut fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={sell_qty:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}) ")
# end of if dfx.shape[0] == 0:
###########################################
### 3: update order csv (update stop_loss)
###########################################
### OPEN (load the order csv csv file) : order(BTC_JPY).csv
ord_df = self.csv.get_order_csv()
if ord_df.shape[0] > 0:
# find the order csv by buy_order_id
find_mask = ord_df['buy_order_id'] == sub_buy_order_id
dfx = ord_df[find_mask]
if dfx.shape[0] > 0:
# update_order_csv_buy(buy_time: utcnow, buy_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(buy_time + buy_order_id)
self.csv.update_order_csv_buy(sub_buy_time, sub_buy_order_id, col_name_list=['stop_loss'], col_value_list=[True])
continue # continue to next sell order sub
# end of if order_status == 'EXPIRED':
# end of if order_status in 'EXPIRED,CANCELED':
### get highest buy price from buy orderbooks
latest_book_price = bk_buy_price # bk_buy_price = buy_bk_df.iloc[0]['price']
# ### get real qty, sell price, fee from the GMO
# ### get the latest close price from the GMO master
# ### mas_latest_close_price = mas_df.iloc[-1]['close'] # get latest close price (GMO)
_debug1_ = self.coin.debug_mode_debug1; _debug2_ = self.coin.debug_mode_debug2
######################################################################################### DEBUG(1), DEBUG(2)
# _debug1_ = True; _debug2_ = True # ★ TEMP TEMP TEMP ★
# self.debug.trace_warn(f".... DEBUG _debug1_ = True; _debug2_ = True ")
######################################################################################### DEBUG(1), DEBUG(2)
### get sell real qty, price & fee from the GMO : get_price() for exe_sell_close_order_limit() ★
sleep(1) # sleep 1 sec
# get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:
status, res_df, msg_code, msg_str = self.api.get_price_try(sub_sell_order_id, sub_sell_qty, sub_sell_price, latest_book_price, _debug1_, _debug2_) # ★ qty, price, close_price are used for fake mode
# status: 0(ok), 8(retry error), 9(empty dataframe or internal error), ?(misc error)
sleep(1) # sleep 1 sec
res_df = pd.DataFrame(res_df) # cast dataframe
# not real mode and debug (stop loss or cancel pending orders) ?
######################################################################################### DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug1:
if res_df.shape[0] > 0:
# ★ create two sell order subs for each sell order header ★
# update_get_price_response_for_debug1_2(res_df: pd.DataFrame, sell_order_sub_seq: int) -> pd.DataFrame
res_df = self.trade.update_get_price_response_for_debug1_2(res_df, sell_order_sub_seq) # 1, 2, 3, ...
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({sell_order_sub_seq=}) ▶ {res_df.shape=} ")
######################################################################################## DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
####################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug2:
# ★ create outstanding sell order sub depending on get_price_count ★
# get sell 'get_price_count'
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# found sell order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
get_price_count = sell_sub_df.loc[ix, 'get_price_count'] # sell_sub_df.loc[find_mask, 'get_price_count'].values[0] # 0,1,2,3,4,5,...
# update_get_price_response_for_debug2(res_df: pd.DataFrame, get_price_count: int) -> pd.DataFrame:
res_df = self.trade.update_get_price_response_for_debug2(res_df, get_price_count)
if res_df.shape[0] == 0:
status = 9 # set internal status code
msg_code = 'ERR-999' # null => ERR-999
msg_str = 'DataFrame is empty' # null => DataFrame is empty
else:
status = 0
msg_code = 'OK'
msg_str = 'Normal Return'
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug2({get_price_count=}) ▶ {status=}, {res_df.shape=} ")
###################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
# get_price() error ?
if status != 0:
if status == 9: # pending exe_sell_close_order_limit() request ?
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_sell_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_sell_close_order_limit() request: continue to next sell order sub... ")
status = 0 # reset status code
continue # continue to next sell order sub
else: # status == 1 'ERR-5008' Timestamp for this request is too late
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_price({sub_sell_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
continue # continue to next sell order sub
# end of if status == 9:
else: # normal return => executed exe_sell_close_order_limit() request
pass # self.debug.sound_alert(1)
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
####################################################################
### 【4】UPDATE A SELL ORDER SUB (real_qty, real_price, real_fee)
####################################################################
# res_df = get_price() response for exe_sell_close_order_limit()
# ----------------------------------------------------------------
# # Column Dtype
# ----------------------------------------------------------------
# 0 executionId object
# 1 fee float64
# 2 lossGain float64
# 3 orderId object (non unique id => same id)
# 4 positionId object (unique id)
# 5 price float64
# 6 settleType object (OPEN or CLOSE)
# 7 side object (BUY or SELL)
# 8 size float64
# 9 symbol object (BTC_JPY)
# 10 timestamp datetime64[ns, UTC]
# ----------------------------------------------------------------
### update a sell order sub ★
sub_sum_real_qty = round(res_df.groupby('orderId')['size'].sum().values[0], _q_) # round(999.12, _q_)
sub_sum_real_fee = round(res_df.groupby('orderId')['fee'].sum().values[0], 0) # zero(0)
sub_mean_real_price = round(res_df.groupby('orderId')['price'].mean().values[0], _p_) # round(999999.123, _p_)
# DO NOT USED: self.csv.update_sell_order_sub(sell_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update sell order sub (real_qty, real_price, real_fee)
sell_sub_df.loc[find_mask, 'real_qty'] = sub_sum_real_qty
sell_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price
sell_sub_df.loc[find_mask, 'real_fee'] = sub_sum_real_fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, real_qty={sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={sub_sum_real_fee:,.0f}) ")
# end of if dfx.shape[0] == 0:
##############################################################################################################################
### 【5】ADD A SELL ORDER CHILD (sell_time, sell_order_id, real_qty, real_price, real_fee) + (buy_time, buy_order_id)
##############################################################################################################################
### check exit condition : sub_sell_qty=sub_sell_qty, sub_sum_real_qty=sum(res_df['size'])
# ######################################### TEMP TEMP TEMP
# temp_sell_qty = round(sub_sell_qty, _q_)
# temp_real_qty = round(sub_sum_real_qty, _q_)
# self.debug.trace_warn(f".... DEBUG: {temp_sell_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
# ######################################## TEMP TEMP TEMP
# completed exe_sell_close_order_limit() request ?
if round(sub_sell_qty, _q_) == round(sub_sum_real_qty, _q_):
# changed open => closed
if self.coin.real_mode:
self.debug.sound_alert(1)
# increment sell count
self.gvar.sell_count += 1
self.coin.sell_count += 1
# response df for get_price(): exe_sell_close_order_limit()
for k in range(len(res_df)):
sell_order_id = res_df.loc[k, 'orderId'] # sell order id (unique id)
position_id = res_df.loc[k, 'positionId'] # position id (non unique id => same id)
real_qty = res_df.loc[k, 'size'] # real qty
real_price = res_df.loc[k, 'price'] # real price
real_fee = res_df.loc[k, 'fee'] # real fee
if not self.coin.real_mode:
position_id = sub_position_id
### write a sell order child ★
# write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sub_sell_time, sell_order_id, sub_buy_time, sub_buy_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
# ### update sell order sub (bought=True) ★ DO NOT USE update_sell_order_sub() : SUSPEND SUSPEND SUSPEND
else: # outstanding sell_qty exists => incomplete status (sell order / sell order sub will be updated by update_order()
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} incomplete sell order sub({sub_sell_order_id}) ▶ sell_qty={sub_sell_qty:,.{_q_}f}, sell_real_qty={sub_sum_real_qty:,.{_q_}f} ")
# pass # skip (do nothing)
# end of if sub_sell_qty == total_real_qty:
# continue to next sell order sub
# end of for j in range(len(sell_subx_df)):
#################################################################################
### 【6】SAVE BUY/SELL ORDER SUB TO CSV FILES
#################################################################################
### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【7】UPDATE SELL ORDER (real_qty, real_price, real_fee)
#################################################################################
### calculate total sell qty / fee and mean price ★ sell_time ★
filter_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[filter_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL} sell order sub({sell_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub
header_sum_real_qty = dfx.groupby('sell_time')['real_qty'].sum().values[0]
header_sum_real_fee = dfx.groupby('sell_time')['real_fee'].sum().values[0]
header_mean_real_price = dfx.groupby('sell_time')['real_price'].mean().values[0]
header_sum_real_qty = round(header_sum_real_qty, _q_) # round(990.12, _q_)
header_sum_real_fee = round(header_sum_real_fee, 0) # zero(0)
header_mean_real_price = round(header_mean_real_price, _p_) # round(999999.123, _p_)
### update sell order (real_qty, real_price, real_fee) ★
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL} sell order({sell_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order => update
sell_df.loc[find_mask, 'real_qty'] = header_sum_real_qty
sell_df.loc[find_mask, 'real_price'] = header_mean_real_price
sell_df.loc[find_mask, 'real_fee'] = header_sum_real_fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={header_sum_real_fee:,.0f}) ")
# end of if dfx.shape[0] == 0:
# end of if dfx.shape[0] == 0:
# continue to next sell order... ★
# end of for i in range(len(sell_df)):
#################################################################################
### 【8】SAVE BUY/SELL ORDER TO CSV FILES
#################################################################################
### CLOSE (save the buy order csv file) : buy_order(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order(BTC_JPY).csv
buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order csv file) : sell_order(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order({symbol}).csv'
# desktop-pc/bot//buy_sell/sell_order(BTC_JPY).csv
sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
return
################################################
def update_partially_closed_order(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = ':: update_partially_closed_order():'
order_completed = self.trade.get_order_status(exclude=False) # include stop_loss, cancelled, closed order info
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast df (master dataframe)
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}last order completed({order_completed}){Bcolors.ENDC}, mas_df.shape({mas_df.shape[0]}), cancel({self.gvar.cancel_pending_orders}) ")
# mas_df is empty ? => SYTEM ERROR
if mas_df.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_df({mas_df.shape[0]}) is empty. skip update_order() ")
self.debug.sound_alert(3)
return
# ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
# buy_df = self.csv.get_buy_order() # PK(buy_time)
# ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
# buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
# get_orderbooks_try() -> tuple: # return (sell_df, buy_df)
sell_bk, buy_bk = self.api.get_orderbooks_try()
# sleep(0.5)
sell_bk_df = pd.DataFrame(sell_bk) # ascending order of price [-1, price] : low to high price 100, 110, 120, 130, 140, 150
buy_bk_df = pd.DataFrame(buy_bk) # descending order of price iloc[0, price] : high to low price 150, 140, 130, 120, 110, 100
# not empty order books ?
if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:
# get latest sell/buy price from the order books
bk_sell_price = sell_bk_df.iloc[0]['price'] # get lowest sell price : 100
bk_buy_price = buy_bk_df.iloc[0]['price'] # get highest buy price : 150
else:
bk_sell_price = 0.0
bk_buy_price = 0.0
#################################################
### 【1】READ ALL SELL ORDER
#################################################
### get all sell order
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # unique time
buy_time = sell_df.loc[i, 'buy_time'] # unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell order sub
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell order sub ?
if dfx.shape[0] > 0:
dfx.reset_index(inplace=True)
sell_order_id = str(dfx.loc[0, 'sell_order_id'])
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_order_id}) has been closed: sell_order_closed={sell_order_closed} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_time:%Y-%m-%d %H:%M:%S}) has been closed: sell_order_closed={sell_order_closed} ")
# end of if dfx.shape[0] > 0:
continue # continue to next sell order
# end of if sell_order_closed:
### open or incomplete sell order
##########################################################################################
### 【2】READ ALL SELL ORDER SUB & EXECUTE GET_PRICE() FOR EACH SELL ORDER SUB
##########################################################################################
### for sell order sub : use pandas dataframe groupby()
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order : 2 rows
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
sell_order_sub_seq = j + 1 # 1, 2, 3,....
### get sell order sub info
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★ (unique id) used for get_price() api
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★ (non unique id)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # (unique id)
sub_sell_qty = sell_subx_df.loc[j, 'qty'] # 10
sub_sell_price = sell_subx_df.loc[j, 'price']
sub_sell_real_qty = sell_subx_df.loc[j, 'real_qty'] # 10
sub_sell_real_price = sell_subx_df.loc[j, 'real_price']
sub_sell_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_sell_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_sell_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_sell_order_open = False
sub_sell_order_closed = False
sub_sell_order_incomplete = False
if sub_sell_real_qty == 0:
sub_sell_order_open = True
elif sub_sell_qty == sub_sell_real_qty:
sub_sell_order_closed = True
else:
sub_sell_order_incomplete = True
# fake sell sub order ? => SKIP
if sub_sell_order_id.startswith('Fake'):
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id})... ")
continue
# cancelled sell sub order ? => SKIP
if sub_sell_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled ▶ sub_sell_cancelled={sub_sell_cancelled} ")
continue
# stop loss sell sub order ? => SKIP
if sub_sell_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss ▶ sub_sell_stop_loss={sub_sell_stop_loss} ")
continue
# closed sell sub order ? => SKIP
if sub_sell_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed ▶ sub_sell_order_closed={sub_sell_order_closed} ")
continue
### open or incomplete sell order sub
##########################################################################################
### 【3】GET ORDER STATUS
##########################################################################################
### get order_status
qty = sub_sell_qty
price = sub_sell_price
# get_order_status_try(order_id: str, qty: float, price: float) ->tuple: # return (status, order_status)
status, order_status = self.api.get_order_status_try(sub_sell_order_id, qty, price) # ★ qty, price are used for fake mode only
# status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)
### status = 0; order_status = 'EXPIRED' # DEBUG DEBUG DEBUG
# get_order_status() error ?
if status != 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")
continue # continue to next sell order sub
### get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
# pending exe_sell_close_order_limit() request ? : ORDERED(指値注文有効中), EXECUTE(全量約定)
if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status_try({sub_sell_order_id}, {qty:,.{_q_}f}, {price:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_sell_close_order_limit() request ")
continue # continue to next sell order sub
### executed exe_sell_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'
if order_status in 'EXPIRED,CANCELED':
### update sell order, sell order sub and order csv file
if order_status == 'CANCELED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}): {Bcolors.FAIL}the order has been cancelled by GMO. please confirm the reason{Bcolors.ENDC} ")
self.debug.sound_alert(3)
continue # continue to next sell order sub
if order_status == 'EXPIRED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status_try({sub_sell_order_id}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")
self.debug.sound_alert(3)
###########################################
### 1: close sell order sub
###########################################
# update a sell order sub (update real_qty, real_price, stop_loss)
# DO NOT USE: self.csv.update_sell_order_sub(sell_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update sell order sub (real_qty, real_price, real_fee, stop_loss)
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
sell_stop_loss = True
sell_sub_df.loc[find_mask, 'real_qty'] = sub_sell_qty
sell_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
sell_sub_df.loc[find_mask, 'real_fee'] = gmo_loss_cut_fee # gmo loss cut fee
sell_sub_df.loc[find_mask, 'stop_loss'] = sell_stop_loss
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, real_qty={sub_sell_qty:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {sell_stop_loss=}) ")
# end of if dfx.shape[0] == 0:
#######################################
### 2: close sell order
#######################################
# update sell order (real_qty, real_price, real_fee)
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL} ({sell_time=:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
sell_df.loc[find_mask, 'real_qty'] = sell_qty
sell_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
sell_df.loc[find_mask, 'real_fee'] = gmo_loss_cut_fee # gmo loss cut fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={sell_qty:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}) ")
# end of if dfx.shape[0] == 0:
###########################################
### 3: update order csv (update stop_loss)
###########################################
### OPEN (load the order csv csv file) : order(BTC_JPY).csv
ord_df = self.csv.get_order_csv()
if ord_df.shape[0] > 0:
# find the order csv by buy_order_id
find_mask = ord_df['buy_order_id'] == sub_buy_order_id
dfx = ord_df[find_mask]
if dfx.shape[0] > 0:
# update_order_csv_buy(buy_time: utcnow, buy_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(buy_time + buy_order_id)
self.csv.update_order_csv_buy(sub_buy_time, sub_buy_order_id, col_name_list=['stop_loss'], col_value_list=[True])
continue # continue to next sell order sub
# end of if order_status == 'EXPIRED':
# end of if order_status in 'EXPIRED,CANCELED':
### get highest buy price from buy orderbooks
latest_book_price = bk_buy_price # bk_buy_price = buy_bk_df.iloc[0]['price']
##########################################################################################
### 【4】GET ORDER PRICE
##########################################################################################
# ### get real qty, sell price, fee from the GMO
# ### get the latest close price from the GMO master
# ### mas_latest_close_price = mas_df.iloc[-1]['close'] # get latest close price (GMO)
_debug1_ = self.coin.debug_mode_debug1; _debug2_ = self.coin.debug_mode_debug2
######################################################################################### DEBUG(1), DEBUG(2)
# _debug1_ = True; _debug2_ = True # ★ TEMP TEMP TEMP ★
# self.debug.trace_warn(f".... DEBUG _debug1_ = True; _debug2_ = True ")
######################################################################################### DEBUG(1), DEBUG(2)
### get sell real qty, price & fee from the GMO : get_price() for exe_sell_close_order_limit() ★
sleep(1) # sleep 1 sec
# get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:
status, res_df, msg_code, msg_str = self.api.get_price_try(sub_sell_order_id, sub_sell_qty, sub_sell_price, latest_book_price, _debug1_, _debug2_) # ★ qty, price, close_price are used for fake mode
# status: 0(ok), 8(retry error), 9(empty dataframe or internal error), ?(misc error)
sleep(1) # sleep 1 sec
res_df = pd.DataFrame(res_df) # cast dataframe
# not real mode and debug (stop loss or cancel pending orders) ?
######################################################################################### DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug1:
if res_df.shape[0] > 0:
# ★ create two sell order subs for each sell order header ★
# update_get_price_response_for_debug1_2(res_df: pd.DataFrame, sell_order_sub_seq: int) -> pd.DataFrame
res_df = self.trade.update_get_price_response_for_debug1_2(res_df, sell_order_sub_seq) # 1, 2, 3, ...
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({sell_order_sub_seq=}) ▶ {res_df.shape=} ")
######################################################################################## DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
####################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug2:
# ★ create outstanding sell order sub depending on get_price_count ★
# get sell 'get_price_count'
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id) # PK(sell_time + sell_order_id)
dfx = sell_sub_df[find_mask]
# found sell order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
get_price_count = sell_sub_df.loc[ix, 'get_price_count'] # sell_sub_df.loc[find_mask, 'get_price_count'].values[0] # 0,1,2,3,4,5,...
# update_get_price_response_for_debug2(res_df: pd.DataFrame, get_price_count: int) -> pd.DataFrame:
res_df = self.trade.update_get_price_response_for_debug2(res_df, get_price_count)
if res_df.shape[0] == 0:
status = 9 # set internal status code
msg_code = 'ERR-999' # null => ERR-999
msg_str = 'DataFrame is empty' # null => DataFrame is empty
else:
status = 0
msg_code = 'OK'
msg_str = 'Normal Return'
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug2({get_price_count=}) ▶ {status=}, {res_df.shape=} ")
###################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
# get_price() error ?
if status != 0:
if status == 9: # pending exe_sell_close_order_limit() request ?
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_sell_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_sell_close_order_limit() request: continue to next sell order sub... ")
status = 0 # reset status code
continue # continue to next sell order sub
else: # status == 1 'ERR-5008' Timestamp for this request is too late
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_price({sub_sell_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
continue # continue to next sell order sub
# end of if status == 9:
else: # normal return => executed exe_sell_close_order_limit() request
pass # self.debug.sound_alert(1)
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
##############################################################################################################################
### 【5】ADD A SELL ORDER CHILD (sell_time, sell_order_id, real_qty, real_price, real_fee) + (buy_time, buy_order_id)
##############################################################################################################################
# response df for get_price(): exe_sell_close_order_limit()
for k in range(len(res_df)):
sell_order_id = res_df.loc[k, 'orderId'] # sell order id (unique id)
position_id = res_df.loc[k, 'positionId'] # position id (non unique id => same id)
real_qty = res_df.loc[k, 'size'] # real qty
real_price = res_df.loc[k, 'price'] # real price
real_fee = res_df.loc[k, 'fee'] # real fee
if not self.coin.real_mode:
position_id = sub_position_id
### write a sell order child ★
# write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sub_sell_time, sell_order_id, sub_buy_time, sub_buy_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
# continue to next sell order sub
# end of for j in range(len(sell_subx_df)):
# continue to next sell order... ★
# end of for i in range(len(sell_df)):
return
#######################################################
def update_order_profit(self, first_pass=True) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'update_order_profit():'
order_completed = self.trade.get_order_status(exclude=False) # include stop_loss, cancelled, closed order info
self.debug.trace_write(f".... {self.gvar.callers_method_name} first_pass({first_pass}), last_order_completed({order_completed}) ")
######################################################################################################################################################################
### 【1】IMPORT BUY ORDER SUB & APPEND/UPDATE ORDER CSV FILE (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee) & (sell_time, sell_order_id)
######################################################################################################################################################################
### OPEN (load the order csv file) : order(BTC_JPY).csv
ord_df = self.csv.get_order_csv()
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub()
### import buy order sub file
# get all buy order sub
for i in range(len(buy_sub_df)):
### get buy order sub info
sub_buy_time = buy_sub_df.loc[i, 'buy_time'] # ★ (non unique)
sub_sell_time = buy_sub_df.loc[i, 'sell_time'] # ★ (non unique)
sub_buy_order_id = buy_sub_df.loc[i, 'buy_order_id'] # ★ 'B9999-999999-999999' (unique)
sub_sell_order_id = buy_sub_df.loc[i, 'sell_order_id'] # ★ 'S9999-999999-999999;S9999-999999-999999;S9999-999999-999999' (list of sell_order_ids) : buy sub(one) <= sell sub(many)
sub_buy_qty = buy_sub_df.loc[i, 'qty']
sub_buy_price = buy_sub_df.loc[i, 'price']
sub_buy_real_qty = buy_sub_df.loc[i, 'real_qty']
sub_buy_real_price = buy_sub_df.loc[i, 'real_price']
sub_buy_real_fee = buy_sub_df.loc[i, 'real_fee']
sub_buy_get_price_count = buy_sub_df.loc[i, 'get_price_count']
sub_buy_stop_loss = buy_sub_df.loc[i, 'stop_loss']
sub_buy_order_cancelled = buy_sub_df.loc[i, 'cancelled']
sub_buy_order_closed = True if sub_buy_qty == sub_buy_real_qty else False
# fake sub order ? => SKIP
if sub_buy_order_id.startswith('Fake'):
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id})... ")
continue
# empty order csv file ?
if ord_df.shape[0] == 0:
dfx = pd.DataFrame()
else: # not empty dataframe => find the order csv file by buy_order_id
find_mask = ord_df['buy_order_id'] == sub_buy_order_id # B9999-999999-999999
dfx = ord_df[find_mask]
# not found order csv file ?
if dfx.shape[0] == 0:
### append order csv file ★IMPORTANT★ DO NOT APEEND 'Null' Value => sell_order_id = 'S9999-999999-999999'
columns = Gmo_dataframe.ORDER_CSV
_sell_real_qty_=0.0; _sell_real_price_=0.0; _sell_real_fee_=0.0; _real_profit_=0.0
_accumulated_leverage_fee_=0.0; _close_count_=0; _close_update_date_=dt.datetime.now()
_order_closed_ = False; _stop_loss_=sub_buy_stop_loss; _cancelled_=sub_buy_order_cancelled
_order_open_date_ = dt.datetime.now(); _order_close_date_ = dt.datetime.now(); _log_time_=dt.datetime.now()
data = [[sub_buy_time, sub_sell_time, sub_buy_order_id, sub_sell_order_id, sub_buy_real_qty, _sell_real_qty_, sub_buy_real_price, _sell_real_price_, sub_buy_real_fee, _sell_real_fee_, _real_profit_, _accumulated_leverage_fee_, _close_count_, _close_update_date_, _order_closed_, _stop_loss_, _cancelled_, _order_open_date_, _order_close_date_, _log_time_]]
new_df = pd.DataFrame(data, columns=columns)
# convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
new_df['buy_time'] = pd.to_datetime(new_df['buy_time'], utc=True) # object => utc time (aware)
new_df['sell_time'] = pd.to_datetime(new_df['sell_time'], utc=True) # object => utc time (aware)
new_df['close_update_date'] = pd.to_datetime(new_df['close_update_date']) # object => ns time (jp time)
new_df['order_open_date'] = pd.to_datetime(new_df['order_open_date']) # object => ns time (jp time)
new_df['order_close_date'] = pd.to_datetime(new_df['order_close_date']) # object => ns time (jp time)
new_df['log_time'] = pd.to_datetime(new_df['log_time']) # object => ns time (jp time)
new_df['buy_order_id'] = new_df['buy_order_id'].astype(str) # int64 => string
new_df['sell_order_id'] = new_df['sell_order_id'].astype(str) # int64 => string
# append a new row (order row)
ord_buy_order_id = sub_buy_order_id
ord_buy_real_qty = sub_buy_real_qty
ord_buy_real_price = sub_buy_real_price
ord_buy_real_fee = sub_buy_real_fee
ord_order_closed = False
ord_df = ord_df.append(new_df, ignore_index=True)
self.debug.trace_write(f".... {self.gvar.callers_method_name} write_order_csv_buy({ord_buy_order_id=}, {ord_buy_real_qty=:,.{_q_}f}, {ord_buy_real_price=:,.{_p_}f}, {ord_buy_real_fee=:,.0f}, {ord_order_closed=}) ")
else: # found order csv file => update order csv file
# get order csv info
ix = dfx.index[0] # get index of the first row
# ord_buy_order_id = ord_df.loc[ix, 'buy_order_id']
# ord_buy_real_qty = ord_df.loc[ix, 'buy_real_qty']
# ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']
# order_closed = ord_df.loc[ix, 'order_closed']
ord_buy_order_id = ord_df.loc[ix, 'buy_order_id']
ord_sell_order_id = ord_df.loc[ix, 'sell_order_id']
ord_buy_real_qty = ord_df.loc[ix, 'buy_real_qty']
ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']
ord_buy_real_price = ord_df.loc[ix, 'buy_real_price']
ord_sell_real_price = ord_df.loc[ix, 'sell_real_price']
ord_buy_real_fee = ord_df.loc[ix, 'buy_real_fee']
ord_sell_real_fee = ord_df.loc[ix, 'sell_real_fee']
ord_order_closed = ord_df.loc[ix, 'order_closed']
# completed order csv file ?
if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_):
if not ord_order_closed:
# update order csv file
ord_order_closed = True
ord_df.loc[find_mask, 'order_closed'] = ord_order_closed
ord_df.loc[find_mask, 'order_close_date'] = dt.datetime.now()
ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
ord_df['order_close_date'] = pd.to_datetime(ord_df['order_close_date']) # object => ns time (jp time)
ord_df['log_time'] = pd.to_datetime(ord_df['log_time']) # object => ns time (jp time)
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_buy({ord_buy_order_id=}, {ord_buy_real_qty=:,.{_q_}f}, {ord_buy_real_price=:,.{_p_}f}, {ord_buy_real_fee=:,.0f}, {ord_order_closed=}) ")
# self.debug.trace_write(f".... update_order2(): pass update order info (buy): buy_order_id={sub_buy_order_id}, buy_real_qty={sub_buy_real_qty:,.{_q_}f}, buy_real_price={sub_buy_real_price:,.{_x_}f}, buy_real_fee={sub_buy_real_fee:,.0f} ")
else: # incomplete order => update order csv file
# update order csv
ord_buy_time = sub_buy_time
ord_buy_order_id = sub_buy_order_id
ord_buy_real_qty = sub_buy_real_qty
ord_buy_real_price = sub_buy_real_price
ord_buy_real_fee = sub_buy_real_fee
ord_df.loc[find_mask, 'buy_time'] = ord_buy_time
ord_df.loc[find_mask, 'buy_order_id'] = ord_buy_order_id
ord_df.loc[find_mask, 'buy_real_qty'] = ord_buy_real_qty
ord_df.loc[find_mask, 'buy_real_price'] = ord_buy_real_price
ord_df.loc[find_mask, 'buy_real_fee'] = ord_buy_real_fee
ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
### update order_closed flag
# get buy_real_qty and sell_real_qty
ix = dfx.index[0] # get index of the first row
# ord_buy_order_id = ord_df.loc[ix, 'buy_order_id']
# ord_buy_real_qty = ord_df.loc[ix, 'buy_real_qty']
# ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']
# order_closed = ord_df.loc[ix, 'order_closed']
if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_):
ord_order_closed = True
ord_df.loc[find_mask, 'order_closed'] = ord_order_closed
# convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
ord_df['buy_time'] = pd.to_datetime(ord_df['buy_time'], utc=True) # object => utc time (aware)
ord_df['sell_time'] = pd.to_datetime(ord_df['sell_time'], utc=True) # object => utc time (aware)
ord_df['close_update_date'] = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)
ord_df['order_open_date'] = pd.to_datetime(ord_df['order_open_date']) # object => ns time (jp time)
ord_df['order_close_date'] = pd.to_datetime(ord_df['order_close_date']) # object => ns time (jp time)
ord_df['log_time'] = pd.to_datetime(ord_df['log_time']) # object => ns time (jp time)
ord_df['buy_order_id'] = ord_df['buy_order_id'].astype(str) # int64 => string
ord_df['sell_order_id'] = ord_df['sell_order_id'].astype(str) # int64 => string
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_buy({ord_buy_order_id=}, {ord_buy_real_qty=:,.{_q_}f}, {ord_buy_real_price=:,.{_p_}f}, {ord_buy_real_fee=:,.0f}, {ord_order_closed=}) ")
# end of if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:
# end of if dfx.shape[0] == 0:
# continue to next buy sub orders
# end of for i in range(len(buy_sub_df)):
#######################################################################################################################################
### 【2】IMPORT SELL ORDER SUB & UPDATE ORDER CSV FILE (sell_time, sell_order_id, sell_real_qty, sell_real_price, sell_real_fee)
#######################################################################################################################################
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub()
### import sell order sub file
for i in range(len(sell_sub_df)):
### get sell order sub info
sub_sell_time = sell_sub_df.loc[i, 'sell_time'] # ★ (non unique)
sub_buy_time = sell_sub_df.loc[i, 'buy_time'] # ★ (non unique)
sub_sell_order_id = sell_sub_df.loc[i, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_buy_order_id = sell_sub_df.loc[i, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : sell sub(many) => buy_sub(one)
sub_sell_qty = sell_sub_df.loc[i, 'qty']
sub_sell_price = sell_sub_df.loc[i, 'price']
sub_sell_real_qty = sell_sub_df.loc[i, 'real_qty']
sub_sell_real_price = sell_sub_df.loc[i, 'real_price']
sub_sell_real_fee = sell_sub_df.loc[i, 'real_fee']
sub_sell_get_price_count = sell_sub_df.loc[i, 'get_price_count']
sub_sell_stop_loss = sell_sub_df.loc[i, 'stop_loss']
sub_sell_order_cancelled = sell_sub_df.loc[i, 'cancelled']
sub_sell_order_open = False
sub_sell_order_closed = False
sub_sell_order_incomplete = False
if sub_sell_real_qty == 0:
sub_sell_order_open = True
elif sub_sell_qty == sub_sell_real_qty:
sub_sell_order_closed = True
else:
sub_sell_order_incomplete = True
# fake sub order ? => SKIP
if sub_sell_order_id.startswith('Fake'):
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id})... ")
continue
# empty order csv file ?
if ord_df.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - order csv file is empty: continue... ")
self.debug.sound_alert(3)
continue
# find the order csv file by buy_order_id
find_mask = ord_df['buy_order_id'] == sub_buy_order_id
dfx = ord_df[find_mask]
# not found order csv file ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}ERROR{Bcolors.ENDC} order csv file not found: buy_order_id={sub_buy_order_id} ")
self.debug.sound_alert(3)
continue
### found order csv file
# get order csv info
ix = dfx.index[0] # get index of the first row
ord_buy_order_id = ord_df.loc[ix, 'buy_order_id']
ord_sell_order_id = ord_df.loc[ix, 'sell_order_id']
ord_buy_real_qty = ord_df.loc[ix, 'buy_real_qty']
ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']
ord_buy_real_price = ord_df.loc[ix, 'buy_real_price']
ord_sell_real_price = ord_df.loc[ix, 'sell_real_price']
ord_buy_real_fee = ord_df.loc[ix, 'buy_real_fee']
ord_sell_real_fee = ord_df.loc[ix, 'sell_real_fee']
ord_order_closed = ord_df.loc[ix, 'order_closed']
# self.debug.trace_warn(f".... DEBUG(1): {ord_buy_real_qty=}, {ord_sell_real_qty=}, {round(ord_buy_real_qty, _q_)=}, {round(ord_sell_real_qty, _q_)=} ")
# completed order csv file ?
if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_):
if not ord_order_closed:
# update order csv file
ord_order_closed = True
ord_df.loc[find_mask, 'order_closed'] = ord_order_closed
ord_df.loc[find_mask, 'order_close_date'] = dt.datetime.now()
ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
ord_df['order_close_date'] = pd.to_datetime(ord_df['order_close_date']) # object => ns time (jp time)
ord_df['log_time'] = pd.to_datetime(ord_df['log_time']) # object => ns time (jp time)
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_sell({ord_sell_order_id=}, {ord_sell_real_qty=:,.{_q_}f}, {ord_sell_real_price=:,.{_p_}f}, {ord_sell_real_fee=:,.0f}, {ord_order_closed=}) ")
# self.debug.trace_write(f".... update_order2(): pass update order csv file (sell): sell_order_id={sub_sell_order_id}, sell_real_qty={sub_sell_real_qty:,.{_q_}f}, sell_real_price={sub_sell_real_price:,.{_x_}f}, sell_real_fee={sub_sell_real_fee:,.0f} ")
else: # incomplete order => update order csv file
### get a list of sell_order_id from the sell order sub ★ buy_order_id ★
filter_mask = sell_sub_df['buy_order_id'] == sub_buy_order_id
dfx = sell_sub_df[filter_mask]
sell_order_id_list = dfx.groupby('buy_order_id')['sell_order_id'].apply(list)[0]
sell_order_ids_by_semicolon = ''
for k, order_id in enumerate(sell_order_id_list):
if k == 0:
sell_order_ids_by_semicolon += order_id
else:
sell_order_ids_by_semicolon += ';' + order_id
### calculate total sell qty / fee and mean price ★ buy_order_id ★
sell_sum_real_qty = dfx.groupby('buy_order_id')['real_qty'].sum().values[0]
sell_sum_real_fee = dfx.groupby('buy_order_id')['real_fee'].sum().values[0]
sell_mean_real_price = dfx.groupby('buy_order_id')['real_price'].mean().values[0]
### update order csv file sell info (sell_time, sell_order_id, sell_real_qty, sell_real_price, sell_real_fee) OK
ord_sell_real_qty = round(sell_sum_real_qty, _q_) # round(999.12, _q_)
ord_sell_real_price = round(sell_mean_real_price, _p_) # round(999999.123, _p_)
ord_sell_real_fee = round(sell_sum_real_fee, 0) # zero(0)
# order completed ?
if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_):
ord_order_closed = True
ord_df.loc[find_mask, 'order_closed'] = ord_order_closed
ord_df.loc[find_mask, 'sell_time'] = sub_sell_time
ord_df.loc[find_mask, 'sell_order_id'] = sell_order_ids_by_semicolon # 'OrderId1;OrderId2;OrderId3'
ord_df.loc[find_mask, 'sell_real_qty'] = ord_sell_real_qty # round(sell_sum_real_qty, _q_)
ord_df.loc[find_mask, 'sell_real_price'] = ord_sell_real_price # round(sell_mean_real_price, _p_)
ord_df.loc[find_mask, 'sell_real_fee'] = ord_sell_real_fee # round(sell_sum_real_fee, 0)
# convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
ord_df['buy_time'] = pd.to_datetime(ord_df['buy_time'], utc=True) # object => utc time (aware)
ord_df['sell_time'] = pd.to_datetime(ord_df['sell_time'], utc=True) # object => utc time (aware)
ord_df['close_update_date'] = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)
ord_df['order_open_date'] = pd.to_datetime(ord_df['order_open_date']) # object => ns time (jp time)
ord_df['order_close_date'] = pd.to_datetime(ord_df['order_close_date']) # object => ns time (jp time)
ord_df['log_time'] = pd.to_datetime(ord_df['log_time']) # object => ns time (jp time)
ord_df['buy_order_id'] = ord_df['buy_order_id'].astype(str) # int64 => string
ord_df['sell_order_id'] = ord_df['sell_order_id'].astype(str) # int64 => string
# self.debug.trace_warn(f".... DEBUG(2): {ord_buy_real_qty=}, {ord_sell_real_qty=}, {round(ord_buy_real_qty, _q_)=}, {round(ord_sell_real_qty, _q_)=} ")
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_sell({ord_sell_order_id=}, {ord_sell_real_qty=:,.{_q_}f}, {ord_sell_real_price=:,.{_p_}f}, {ord_sell_real_fee=:,.0f}, {ord_order_closed=}) ")
# end of if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:
# continue to next sell order sub
# end of for i in range(len(sell_sub_df)):
#######################################################################################################################################
### 【3】SAVE ORDER TO CSV/EXCEL FILES
#######################################################################################################################################
### CLOSE (save to the order csv file) : order(BTC_JPY).csv
if ord_df.shape[0] > 0:
### CLOSE (order csv file)
csv_file = self.folder_trading_type + f'order({symbol}).csv'
# desktop-pc/bot//buy_sell/order(BTC_JPY).csv
ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file
### SAVE (order excel file)
# save to the order excel file
excel_file = self.folder_trading_type + f'order({symbol}).xlsx'
# desktop-pc/bot/buy_sell/order(BTC_JPY).xlsx
excel_df = ord_df.copy()
# (1) convert time to string
excel_df['buy_time'] = excel_df['buy_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))
excel_df['sell_time'] = excel_df['sell_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))
excel_df['close_update_date'] = excel_df['close_update_date'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))
excel_df['log_time'] = excel_df['log_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))
# (2) drop real_profit column
excel_df = excel_df.drop(['real_profit'], axis=1)
# (3) add new columns
excel_df['buy(SUM)'] = '=ROUND(E2*G2,3)' # add a new column
excel_df['sell(SUM)'] = '=ROUND(F2*H2,3)' # add a new column
excel_df['profit(AMT)'] = '=IF((H2-G2)>0,ROUNDDOWN((H2 - G2) * F2, 0),ROUNDUP((H2 - G2) * F2, 0))' # add a new column : rounddown(profit), roundup(loss)
excel_df['profit(%)'] = '=M2/K2' # add a new column
# (4) update excel formula
# get all excel order info
row = 2
for i in range(len(excel_df)):
order_closed = excel_df.loc[i, 'order_closed'] # True or False
stop_loss = excel_df.loc[i, 'stop_loss'] # True or False
cancelled = excel_df.loc[i, 'cancelled'] # True or False
if order_closed or stop_loss or cancelled:
cell = str(row)
excel_df.loc[i, 'buy(SUM)'] = f'=ROUND(E{cell}*G{cell},3)'
excel_df.loc[i, 'sell(SUM)'] = f'=ROUND(F{cell}*H{cell},3)'
excel_df.loc[i, 'profit(AMT)'] = f'=IF((H{cell}-G{cell})>0,ROUNDDOWN((H{cell} - G{cell}) * F{cell}, 0),ROUNDUP((H{cell} - G{cell}) * F{cell}, 0))'
excel_df.loc[i, 'profit(%)'] = f'=M{cell}/K{cell}'
else:
excel_df.loc[i, 'buy(SUM)'] = ''
excel_df.loc[i, 'sell(SUM)'] = ''
excel_df.loc[i, 'profit(AMT)'] = ''
excel_df.loc[i, 'profit(%)'] = ''
row += 1
# end of for j in range(len(excel_df)):
# (5) re-order columns
columns = [
'buy_time','sell_time','buy_order_id','sell_order_id','buy_real_qty','sell_real_qty','buy_real_price','sell_real_price','buy_real_fee','sell_real_fee',
'buy(SUM)','sell(SUM)','profit(AMT)','profit(%)',
'accumulated_leverage_fee','close_count','close_update_date',
'order_closed','stop_loss','cancelled','order_open_date','order_close_date','log_time'
]
excel_df = excel_df[columns]
# print('excel_df.info()\n', excel_df.info())
# ------------------------------------------------------------------------------------------
# # Column Dtype
# ------------------------------------------------------------------------------------------
# 1 time(B) datetime64[utc] => Convert to string
# 2 time(S) datetime64[utc] => Convert to string
# 3 order(B) object
# 4 order(S) object
# 5 qty(B) float64
# 6 qty(S) float64
# 7 price(B) float64
# 8 price(S) float64
# 9 fee(B) float64
# 10 fee(S) float64
# real_profit float64 => REMOVE
# 11 buy(SUM) float64 => ADD =ROUNDUP(E2*G2,3)+I2
# 12 sell(SUM) float64 => ADD =ROUNDDOWN(F2*H2,3)+J2
# 13 profit(AMT) float64 => ADD =L2-K2
# 14 profit(%) float64 => ADD =M2/K2
# 15 fee(L) float64
# 16 count(L) int
# 17 close(L) datetime64[ns]
# 18 order_closed bool
# 19 stop bool
# 20 cancel bool
# 21 open_date datetime64[ns]
# 22 close_date datetime64[ns]
# 23 log_time datetime64[ns] => Convert to string
# ----------------------------------------------------------------------------------------
# (5) rename column names & export to excel file order(BTC_JPY).xlsx
excel_df.columns = Gmo_dataframe.ORDER_XLSX
excel_df.to_excel(excel_file, sheet_name='GMO Orders', index=False)
# end of if ord_df.shape[0] > 0:
return
############################################
def stop_loss(self, method_id: int) -> None:
"""
"""
if method_id <= 1:
eval(f'self.stop_loss_{str(method_id)}()') # call 'self.stop_loss_?() method
else:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid stoploss_method_id {method_id=} {Bcolors.ENDC} - skip stop_loss() ")
self.debug.sound_alert(3)
# if method_id == 0:
# self.stop_loss_0() # stop loss method 0
# elif method_id == 1:
# self.stop_loss_1() # stop loss method 1
# else: #
# self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid stoploss_method_id {method_id=} {Bcolors.ENDC} - skip stop_loss() ")
# self.debug.sound_alert(3)
return
##############################
def stop_loss_0(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'stop_loss(0):'
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast df (master dataframe)
self.debug.trace_write(f".... {self.gvar.callers_method_name} mas_df.shape({mas_df.shape[0]}), loss cut: 18%({self.coin.buy_sell_action14}), 15%({self.coin.buy_sell_action13}), 10%({self.coin.buy_sell_action12}) ")
# ### OPEN (load the order csv file) : order(BTC_JPY).csv
# ord_df = self.csv.get_order_csv()
### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order() # PK(buy_time)
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
#################################################
### 【1】READ ALL SELL ORDERs
#################################################
# define sell order list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
### get all sell orders
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # ★ unique time
buy_time = sell_df.loc[i, 'buy_time'] # ★ unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell order sub
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
sell_order_id = str(dfx.loc[ix, 'sell_order_id'])
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sell_order_id}) has been closed: {sell_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{BBcolors.ENDC} sell order({sell_time:%Y-%m-%d %H:%M:%S}) has been closed: ={sell_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue # skip closed order
# end of if sell_order_closed:
#################################################################
### 【2】READ SELL ORDER SUB (open or incomplete status)
#################################################################
### for sell order sub (open or incomplete status)
# define sell order sub list
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
### get sell order sub info
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★ (unique id)
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★ (non unique id) : sell sub(many) => buy_sub(one)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = sell_subx_df.loc[j, 'qty'] # 40
sub_price = sell_subx_df.loc[j, 'price']
sub_real_qty = sell_subx_df.loc[j, 'real_qty'] # 20
sub_real_price = sell_subx_df.loc[j, 'real_price']
sub_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_get_price_count = sell_subx_df.loc[j, 'get_price_count']
sub_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_order_open = False
sub_order_closed = False
sub_order_incomplete = False
if sub_real_qty == 0:
sub_order_open = True
elif sub_qty == sub_real_qty:
sub_order_closed = True
else:
sub_order_incomplete = True
# fake sell sub order ? => SKIP
if sub_sell_order_id.startswith('Fake') :
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id}) ")
continue
# sell sub order has been stop loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss: {sub_stop_loss=} ")
continue
# sell sub order has been cancelled ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled: {sub_cancelled=} ")
continue
# closed sell sub order ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed: {sub_order_closed=} ")
continue
#######################################################################################################
### 【3】CHECK STOP LOSS CONDITIONS FOR EACH SELL ORDER SUB (open or incomplte sell order sub)
#######################################################################################################
### open or incomplete sell order sub
stop_loss_detected = False
while True:
#####################################################
### Check stop loss debug mode
#####################################################
if self.coin.debug_mode_stop_loss:
stop_loss_detected = True
break # exit while true loop
#####################################################
### Unconditional stop loss
#####################################################
### check loss cut price : buy => sell
### calculate stop loss amount
stop_loss_amt = 0
latest_sell_price = 0
filter_mask = mas_df['side'] == 'SELL'
dfx = mas_df[filter_mask]
if dfx.shape[0] > 0:
# get latest sell price from GMO master dataframe
sell_price = dfx.iloc[-1]['price']
latest_sell_price = sell_price
# get last buy qty & price from the buy order header
qty = buy_df.iloc[-1]['qty']
buy_price = buy_df.iloc[-1]['real_price'] # get a real buy price
# calculate a stop loss amount : stop_loss_amt = math.floor((sell_price - buy_price) * qty)
stop_loss_amt = math.floor((sell_price - buy_price) * qty) # positive => profit, negative => loss
stop_loss_amt = math.floor(stop_loss_amt) # positive => profit, negative => loss
### calculate loss cut price from the buy sub order : loss_cut_price = last_buy_real_price - (last_buy_real_price * 0.20)
last_buy_real_price = buy_sub_df.iloc[-1]['real_price']
### buy_sell : sell position => calculate down limit for 5%, 10%, 15%, 18%, 20%, 23%, 25%(GMO loss cut rate)
# calculate loss cut price from the sell order sub : loss_cut_price = last_buy_real_price - (last_buy_real_price * 0.20)
loss_cut_price_05 = last_buy_real_price - (last_buy_real_price * 0.05) # 0.25(GMO loss cut rate) => 0.05
loss_cut_price_10 = last_buy_real_price - (last_buy_real_price * 0.10) # 0.25(GMO loss cut rate) => 0.10
loss_cut_price_15 = last_buy_real_price - (last_buy_real_price * 0.15) # 0.25(GMO loss cut rate) => 0.15
loss_cut_price_18 = last_buy_real_price - (last_buy_real_price * 0.18) # 0.25(GMO loss cut rate) => 0.18
loss_cut_price_20 = last_buy_real_price - (last_buy_real_price * 0.20) # 0.25(GMO loss cut rate) => 0.20 BOT LOSS CUT
loss_cut_price_23 = last_buy_real_price - (last_buy_real_price * 0.23) # 0.25(GMO loss cut rate) => 0.23 DO NOT USE THIS
loss_cut_price_25 = last_buy_real_price - (last_buy_real_price * 0.25) # 0.25(GMO loss cut rate) => 0.25 GMO LOSS CUT
# gmo master latest sell price is greater than zero ?
if latest_sell_price > 0:
if latest_sell_price <= loss_cut_price_25: # <= 75
self.coin.buy_sell_lc25 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 25%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_25%({loss_cut_price_25:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_23: # <= 79
self.coin.buy_sell_lc23 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 23%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_23%({loss_cut_price_23:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_20: # <= 80
self.coin.buy_sell_lc20 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 20%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_20%({loss_cut_price_20:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_18: # <= 85
self.coin.buy_sell_lc18 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 18%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_18%({loss_cut_price_18:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_15: # <= 90
self.coin.buy_sell_lc15 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 15%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_15%({loss_cut_price_15:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_10: # <= 95
self.coin.buy_sell_lc10 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 10%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_10%({loss_cut_price_10:,.{_x_}f}) ")
elif latest_sell_price <= loss_cut_price_05: # <= 99
self.coin.buy_sell_lc5 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 5%: latest_sell_price({latest_sell_price:,.{_x_}f}) <= loss_cut_5%({loss_cut_price_05:,.{_x_}f}) ")
### check down limit based on buy price
# GMO latest sell price is less than or equal to loss cut price (20%) ? => EXECUTE STOP LOSS
if latest_sell_price <= loss_cut_price_20:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}STOP LOSS DETECTED REAL(1-5) EXECUTE{Bcolors.ENDC} loss cut 20%: latest_sell_price({latest_sell_price:,.{_p_}f}) <= loss_cut_20({loss_cut_price_20:,.{_p_}f}) ")
stop_loss_detected = True
break # exit while true loop
# end of if latest_sell_price > 0:
break # exit while true loop
# end of while True:
if not stop_loss_detected: continue
#######################################################
# ★ STOP LOSS DETECTED ★ #
#######################################################
# increment stop loss count
self.gvar.stop_loss_count += 1
self.coin.stop_loss_count += 1
######################################################
### 【4】UPDATE PARTIALLY CLOSED ORDER
######################################################
if sub_order_incomplete:
### add partially closed sell order child
self.update_partially_closed_order()
self.gvar.callers_method_name = 'stop_loss(0):'
######################################################
### 【5】CANCEL CLOSED BUY ORDER SUB
######################################################
### cancel closed buy order sub
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed buy order sub({sub_buy_order_id}) ")
# increment buy cancel count
self.gvar.buy_cancel_count += 1 # cancel closed buy order (with sell order)
self.coin.buy_cancel_count += 1 # cancel closed buy order (with sell order)
### set a cancelled flag for buy order sub: PK(buy_time + buy_order_id) ★
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
buy_sub_df.loc[find_mask, 'cancelled'] = True
######################################################
### 【6】CANCEL OPEN/INCOMPLETE SELL ORDER SUB
######################################################
### for open or incomplete sell sub order
# cancel open/incomplete sell order sub
status, msg_code, msg_str = self.api.exe_cancel_order(sub_sell_order_id) # ★
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} open/incomplete sell order sub({sub_sell_order_id}): exe_cancel_order() ▶ {status=} ")
sleep(0.5) # sleep 0.5 sec
###########################################################################################
### 【7】RESELL OUTSTANDING SELL ORDER SUB QTY : EXECUTE SELL CLOSE ORDER MARKET (FAK)
###########################################################################################
### resell open/incomplete sell order sub with market price (stop loss price)
outstanding_qty = sub_qty - sub_real_qty
new_sell_qty = outstanding_qty
qty = new_sell_qty
if sub_real_price > 0:
new_sell_price = sub_real_price + sub_real_fee
else:
new_sell_price = sub_price
### get the latest close price from the GMO master
# get a column position of the GMO master
mas_latest_close_price = mas_df.iloc[-1]['close'] # get latest close price (GMO)
status = 0
loop_count = 0
while True:
loop_count += 1
### resell sell order sub outstanding qty
status, res_df, msg_code, msg_str = self.api.exe_sell_close_order_market_wait(sub_position_id, qty, new_sell_price, mas_latest_close_price) # ★ FAK(Fill and Kill): new_sell_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}RESELL({loop_count}){Bcolors.ENDC} open/incomplete sell order sub({sub_sell_order_id}): exe_sell_close_order_market_wait() ▶ {status=} ")
# error ?
if status != 0:
if status == 1 and msg_code == 'ERR-208': # Exceeds the available balance
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC}{Bcolors.FAIL} the available balance exceeded error... ERR-208{Bcolors.ENDC} ")
self.debug.sound_alert(1)
break # exit while true loop and continue to read next sell order sub
elif status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_sell_close_order_market_wait() time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next sell order sub
else:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_sell_close_order_market_wait() ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while tue loop and continue to read next sell order sub
# end of if status == 1 and msg_code == 'ERR-201':
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
######################################################################################################
### 【8】ADD SELL ORDER CHILD ★ DO NOT CREATE RESELL VERSION OF NEW SELL ORDER SUB ★
######################################################################################################
# res_df = get_price() response for exe_sell_order_market_wait()
# ----------------------------------------------------------------
# # Column Dtype
# ----------------------------------------------------------------
# 0 executionId int64
# 1 fee float64
# 2 lossGain float64
# 3 orderId object(string)
# 4 posiitonId object(string)
# 5 price float64
# 6 settleType object('OPEN' or 'CLOSE')
# 7 side object('BUY' or 'SELL')
# 8 size float64
# 9 symbol object('BTC')
# 10 timestamp datetime64[ns]
# ----------------------------------------------------------------
### IMPORTANT ★ DO NOT CREATE RESELL VERSION OF NEW SELL ORDER SUB ★
### append a new record into the sell order child (★ this is the resell version of buy order ★)
# define sell order child list
order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response df for get_price for exe_sell_close_order_market()
for k in range(len(res_df)):
order_id = res_df.loc[k, 'orderId'] # orderId (same orderId): resell version of orderId
position_id = res_df.loc[k, 'positionId'] # positionId (same positionId)
real_qty = res_df.loc[k, 'size'] # real qty :
real_price = res_df.loc[k, 'price'] # real price
real_fee = res_df.loc[k, 'fee'] # real fee
order_id_list.append(order_id) # S9999-999999-999999
position_id_list.append(position_id) # P9999-999999-999999
real_qty_list.append(real_qty)
real_price_list.append(real_price)
real_fee_list.append(real_fee)
### write a sell order child (resell version) ★
# write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sell_time, order_id, buy_time, sub_buy_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
sum_real_qty = round(sum(real_qty_list), _q_) # round(999.12, _q_)
sum_real_fee = round(sum(real_fee_list), 0) # zero(0)
if sum(real_price_list) > 0:
mean_real_price = round(statistics.mean(real_price_list), _p_) # round(999999.123, _p_)
else:
mean_real_price = 0
sub_real_qty_list.append(sum_real_qty)
sub_real_price_list.append(mean_real_price)
sub_real_fee_list.append(sum_real_fee)
### check exit condition:
sub_sum_real_qty = round(sum(sub_real_qty_list), _q_) # round(9999.12, _q_)
new_sub_sum_real_qty = sub_real_qty + sub_sum_real_qty
# ####################################################### TEMP TEMP TEMP
# temp_qty = round(sub_qty, _q_)
# temp_real_qty = round(new_sub_sum_real_qty, _q_)
# self.debug.trace_warn(f".... DEBUG: {temp_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
# ####################################################### TEMP TEMP TEMP
if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
# increment sell cancel count
self.gvar.sell_cancel_count += 1 # cancel open/incomplete sell order
self.coin.sell_cancel_count += 1 # cancel open/incomplete sell order
break # exit while true loop
else: # outstanding buy_qty exists
pass # skip (do nothing)
# end of round(sub_qty, digits) == round(new_sub_sum_real_qty, digits):
### partial exe_sell_order_market_wait() = continue to sell remaining qty
remaining_qty = new_sell_qty - new_sub_sum_real_qty
qty = remaining_qty
# continue exe_sell_close_order_market_wait() ★
# end of while True:
#################################################################################
### 【9】UPDATE SELL ORDER SUB (real_qty, real_price, real_fee, stop_loss)
#################################################################################
sub_sum_real_qty = round(sum(sub_real_qty_list), _q_) # round(999.12,_q_)
sub_sum_real_fee = round(sum(sub_real_fee_list), 0) # zero(0)
if sum(sub_real_price_list) > 0:
sub_mean_real_price = round(statistics.mean(sub_real_price_list), _p_) # round(999999.123, _p_)
else:
sub_mean_real_price = 0
header_real_qty_list.append(sub_sum_real_qty)
header_real_price_list.append(sub_mean_real_price)
header_real_fee_list.append(sub_sum_real_fee)
### update the sell order sub (real_qty, real_price, real_fee, stop_loss) ★
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL} sell order sub not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update
sell_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty # add real_qty (resell version)
sell_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price # update real_price (resell version)
sell_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee # add real_fee (resell version)
sell_sub_df.loc[find_mask, 'stop_loss'] = True
ix = dfx.index[0]
new_sub_sum_real_qty = sell_sub_df.loc[ix, 'real_qty']
new_sub_sum_real_fee = sell_sub_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub(sell_order_id={sub_sell_order_id}, real_qty={new_sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={new_sub_sum_real_fee:,.0f}, stop_loss=True) ")
# end of if dfx.shape[0] == 0:
#################################################################################
### 【10】UPDATE ORDER CSV FILE (stop_loss, order_closed)
#################################################################################
### update order info (stop_loss=True, order_closed) ★
# update_order_csv_buy(buy_time: utcnow, buy_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(buy_time + buy_order_id)
self.csv.update_order_csv_buy(sub_buy_time, sub_buy_order_id, col_name_list=['stop_loss'], col_value_list=[True])
# reset sell order sub list values
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# continue to next sell order sub... ★
# end of for j in range(len(sell_sub_df)):
#################################################################################
### 【11】SAVE BUY/SELL ORDER SUB TO CSV FILES
#################################################################################
### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【12】UPDATE SELL ORDER (real_qty, real_price, real_fee)
#################################################################################
if len(header_real_qty_list) > 0:
### update sell order (real_qty, real_price, real_fee) ★
header_sum_real_qty = round(sum(header_real_qty_list), _q_) # round(999.12, _q_
header_sum_real_fee = round(sum(header_real_fee_list), 0) # zero(0)
if sum(header_real_price_list) > 0:
header_mean_real_price = round(statistics.mean(header_real_price_list), _p_) # round(999999.123, _p_)
else:
header_mean_real_price = 0
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order({sell_time:%Y-%m-%d %H:%M:%S}) ▶ {Bcolors.FAIL} order({sell_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order header => update
sell_df.loc[find_mask, 'real_qty'] += header_sum_real_qty # add real_qty (resell version)
sell_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (resell version)
sell_df.loc[find_mask, 'real_fee'] += header_sum_real_fee # add real_fee (resell version)
ix = dfx.index[0]
new_header_sum_real_qty = sell_df.loc[ix, 'real_qty']
new_header_sum_real_fee = sell_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={new_header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={new_header_sum_real_fee:,.0f}) ")
# end of if dfx.shape[0] == 0:
# end of if len(header_real_qty_list) > 0:
# reset list values
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
# continue to next sell order
# end of for i in range(len(sell_df)):
#################################################################################
### 【13】SAVE BUY/SELL ORDER TO CSV FILES
#################################################################################
### CLOSE (save the buy order csv file) : buy_order(BTC_JPY).csv
csv_file = self.folder_trading_type + f'buy_order({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order(BTC_JPY).csv
buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order csv file) : sell_order(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order(BTC_JPY).csv
sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
return
##############################
def stop_loss_1(self) -> None:
"""
"""
return
########################################
def cancel_pending_order(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'cancel_pending_order():'
self.debug.trace_write(f".... {self.gvar.callers_method_name} ")
df = self.coin.master2_df
mas_df = pd.DataFrame(df) # cast df (master dataframe)
### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order() # PK(buy_time)
### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
#################################################
### 【1】READ ALL SELL ORDERs
#################################################
# define sell order list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
### get all sell order
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # ★ unique time
buy_time = sell_df.loc[i, 'buy_time'] # ★ unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell order sub
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
sell_order_id = dfx.loc[ix, 'sell_order_id']
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_order_id}) has been closed: {sell_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_time:%Y-%m-%d %H:%M:%S}) has been closed: {sell_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue # skip closed order
# end of if sell_order_closed:
#################################################################
### 【2】READ ALL SELL ORDER SUB (OPEN OR INCOMPLETE STATUS)
#################################################################
### for sell order sub (open or incomplete status)
# define sell order sub list
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
### get sell order sub info
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : sell sub(many) => buy_sub(one)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = sell_subx_df.loc[j, 'qty']
sub_price = sell_subx_df.loc[j, 'price']
sub_real_qty = sell_subx_df.loc[j, 'real_qty']
sub_real_price = sell_subx_df.loc[j, 'real_price']
sub_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_order_open = False
sub_order_closed = False
sub_order_incomplete = False
if sub_real_qty == 0:
sub_order_open = True
elif sub_qty == sub_real_qty:
sub_order_closed = True
else:
sub_order_incomplete = True
# fake sell order sub ? => SKIP
if sub_sell_order_id.startswith('Fake') :
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id}) ")
continue
# sell order sub has been stopped loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss ▶ {sub_stop_loss=} ")
continue
# cancelled sell order sub ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled: {sub_cancelled=} ")
continue
# closed sell order sub ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed: {sub_order_closed=} ")
continue
### open or incomplete sell order sub
######################################################
### 【3】UPDATE PARTIALLY CLOSED ORDER
######################################################
if sub_order_incomplete:
### add partially closed sell order child
self.update_partially_closed_order()
self.gvar.callers_method_name = 'cancel_pending_order():'
##########################################################################################
### 【4】CANCEL CLOSED BUY ORDER SUB : ★ Sell Order Sub(many) => Buy Order Sub(one) ★
##########################################################################################
### for closed buy order sub
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed buy order sub({sub_buy_order_id}) ")
# increment buy cancel count
self.gvar.buy_cancel_count += 1 # cancel closed buy order
self.coin.buy_cancel_count += 1 # cancel closed buy order
### set a cancelled flag for buy order sub : ★ PK(buy_time + buy_order_id) ★
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
buy_sub_df.loc[find_mask, 'cancelled'] = True
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub(buy_order_id={sub_buy_order_id}, cancelled=True) ")
######################################################
### 【5】CANCEL OPEN/INCOMPLETE SELL ORDER SUB
######################################################
### cancel open or incomplete sell order sub
### cancel sell order sub
status, msg_code, msg_str = self.api.exe_cancel_order(sub_sell_order_id) # ★
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CANCEL{Bcolors.ENDC} open/incomplete sell order sub({sub_sell_order_id}): exe_cancel_order() ▶ {status=} ")
sleep(0.5) # sleep 0.5 sec
##################################################################################################
### 【6】RESELL OUTSTANDING SELL ORDER SUB QTY : EXECUTE SELL CLOSE ORDER MARKET WAIT (FAK)
##################################################################################################
### resell open or incomplete sell order sub with market price
outstanding_qty = sub_qty - sub_real_qty # calculate outstanding qty
new_sell_qty = outstanding_qty
qty = new_sell_qty
if sub_real_price > 0:
new_sell_price = sub_real_price + sub_real_fee
else:
new_sell_price = sub_price
### get the latest close price from the GMO master
# get a column position of the GMO master
mas_latest_close_price = mas_df.iloc[-1]['close'] # get latest close price (GMO)
status = 0
loop_count = 0
while True:
loop_count += 1
# exe_sell_close_order_market_wait(position_id: str, qty: float, price: float, latest_close_price=0.0) -> tuple: # return (status, df, msg_code, msg_str)
status, res_df, msg_code, msg_str = self.api.exe_sell_close_order_market_wait(sub_position_id, qty, new_sell_price, mas_latest_close_price) # ★ FAK(Fill and Kill): new_sell_price_str, mas_latest_close_price_str ares used for fake mode ; 4BTC; 1BTC
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}RESELL({loop_count}){Bcolors.ENDC} open/incomplete sell order sub({sub_sell_order_id}): exe_sell_close_order_market_wait() ▶ {status=} ")
# error ?
if status != 0:
if status == 1 and msg_code == 'ERR-208': # Exceeds the available balance
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC}{Bcolors.FAIL} the available balance exceeded error... ERR-208{Bcolors.ENDC} ")
self.debug.sound_alert(1)
break # exit while true loop and continue to read next sell sub orders
elif status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_sell_close_order_market_wait() time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next sell sub orders
else: # misc error
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_sell_close_order_market_wait() misc error ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next sell order sub
# end of if status == 1 and msg_code == 'ERR-201':
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
##################################################################################################
### 【7】ADD SELL ORDER CHILD ★ DO NOT CREATE RESELL VERSION OF NEW SELL ORDER SUB ★
##################################################################################################
# res_df = response for exe_sell_close_order_market_wait()
# -----------------------------------------------------------
# # Column Dtype
# -----------------------------------------------------------
# 0 executionId int64
# 1 fee float64
# 2 lossGain float64
# 3 orderId object(string) (non unique)
# 4 positionId object(string) (non unique)
# 5 price float64
# 6 settleType object('OPEN' or 'CLOSE')
# 7 side object('BUY' or 'SELL')
# 8 size float64
# 9 symbol object('BTC_JPY')
# 10 timestamp datetime64[ns]
# ----------------------------------------------------------
### IMPORTANT ★ DO NOT CREATE RESELL VERSION OF NEW SELL ORDER SUB ★
### append a new record into the sell order child (★ this is the resell version of sell order child ★)
# define sell order child list
order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response for exe_sell_close_order_market_wait()
for k in range(len(res_df)):
order_id = res_df.loc[k, 'orderId'] # orderId (same orderId) : resell version of orderId
position_id = res_df.loc[k, 'positionId'] # positionId (same positionId)
real_qty = res_df.loc[k, 'size'] # real qty
real_price = res_df.loc[k, 'price'] # real price
real_fee = res_df.loc[k, 'fee'] # real fee
order_id_list.append(order_id) # S9999-999999-999999
position_id_list.append(position_id) # P9999-999999-999999
real_qty_list.append(real_qty) #
real_price_list.append(real_price)
real_fee_list.append(real_fee)
### write a sell order child (resell version) ★
# write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
_qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sell_time, order_id, buy_time, sub_buy_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
sum_real_qty = round(sum(real_qty_list), _q_) # round(999.12, _q_)
sum_real_fee = round(sum(real_fee_list), 0) # zero(0)
if sum(real_price_list) > 0:
mean_real_price = round(statistics.mean(real_price_list), _p_) # round(999999.123, _p_)
else:
mean_real_price = 0
sub_real_qty_list.append(sum_real_qty)
sub_real_price_list.append(mean_real_price)
sub_real_fee_list.append(sum_real_fee)
### check exit condition:
sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)
new_sub_sum_real_qty = sub_real_qty + sub_sum_real_qty
# no outstanding sell_qty ?
if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
# increment sell cancel count
self.gvar.sell_cancel_count += 1 # cancel open/incomplete sell order
self.coin.sell_cancel_count += 1 # cancel open/incomplete sell order
break # exit while true loop
else: # outstanding sell_qty exists
pass # skip (do nothing)
# end of if round(sub_qty, digits) == round(new_sub_sum_real_qty, digits):
### partial exe_sell_close_order_market_wait() = continue to sell remaining qty
remaining_qty = new_sell_qty - new_sub_sum_real_qty
qty = remaining_qty
# continue exe_sell_close_order_market_wait() ★
# end of while True:
#################################################################################
### 【8】UPDATE SELL ORDER SUB (real_qty, real_price, real_fee, cancelled)
#################################################################################
sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)
sub_sum_real_fee = round(sum(sub_real_fee_list), 0)
if sum(sub_real_price_list) > 0:
sub_mean_real_price = round(statistics.mean(sub_real_price_list), _p_)
else:
sub_mean_real_price = 0
header_real_qty_list.append(sub_sum_real_qty)
header_real_price_list.append(sub_mean_real_price)
header_real_fee_list.append(sub_sum_real_fee)
### update the sell order sub (real_qty, real_price, real_fee, cancelled)
find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
dfx = sell_sub_df[find_mask]
# not found sell order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order_sub({sub_sell_order_id}) ▶ {Bcolors.FAIL}sell order sub not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order sub => update
sell_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty # add real_qty (resell version)
sell_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price # update real_price (resell version)
sell_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee # add real_fee (resell version)
sell_sub_df.loc[find_mask, 'cancelled'] = True
ix = dfx.index[0]
new_sub_sum_real_qty = sell_sub_df.loc[ix, 'real_qty']
new_sub_sum_real_fee = sell_sub_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub(sell_order_id={sub_sell_order_id}, real_qty={new_sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={new_sub_sum_real_fee:,.0f}, cancelled=True) ")
# end of if dfx.shape[0] == 0:
#################################################################################
### 【9】UPDATE ORDER CSV FILE (cancelled, order_closed)
#################################################################################
### update order csv file (cancelled=True, order_closed=True) ★
# update_order_info_buy(buy_time, buy_order_id, symbol='BTC_JPY', col_name=any, col_value=any):
# update_order_csv_buy(buy_time: utcnow, buy_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(buy_time + buy_order_id)
self.csv.update_order_csv_buy(sub_buy_time, sub_buy_order_id, col_name_list=['cancelled'], col_value_list=[True]) # PK(buy_time + buy_order_id)
# reset sell order sub list values
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# continue to next sell order sub
# end of for j in range(len(sell_subx_df)):
#################################################################################
### 【10】SAVE BUY/SELL ORDER SUB TO CSV FILES
#################################################################################
### CLOSE (save the buy order sub csv file)
csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order sub csv file)
csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【11】UPDATE SELL ORDER (real_qty, real_price, real_fee)
#################################################################################
if len(header_real_qty_list) > 0:
### update sell order (real_qty, real_price, real_fee) ★
header_sum_real_qty = round(sum(header_real_qty_list), _q_)
header_sum_real_fee = round(sum(header_real_fee_list), 0)
if sum(header_real_price_list) > 0:
header_mean_real_price = round(statistics.mean(header_real_price_list), _p_)
else:
header_mean_real_price = 0
find_mask = sell_df['sell_time'] == sell_time
dfx = sell_df[find_mask]
# not found sell order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_sell_order() ▶ {Bcolors.FAIL}sell order({sell_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found sell order => update
sell_df.loc[find_mask, 'real_qty'] += header_sum_real_qty # add real_qty (resell version)
sell_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (resell version)
sell_df.loc[find_mask, 'real_fee'] += header_sum_real_fee # add real_fee (resell version)
ix = dfx.index[0]
new_header_sum_real_qty = sell_df.loc[ix, 'real_qty']
new_header_sum_real_fee = sell_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, real_qty={new_header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={new_header_sum_real_fee:,.0f}) ")
# end of if dfx.shape[0] == 0:
# end of if len(header_real_qty_list) > 0:
# reset sell header list values
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
# continue to next sell order... ★
# end of for i in range(len(sell_df)):
#################################################################################
### 【12】SAVE BUY/SELL ORDER TO CSV FILES
#################################################################################
### CLOSE (save the buy order csv file)
csv_file = self.folder_trading_type + f'buy_order({symbol}).csv'
# desktop-pc/bot/buy_sell/buy_order(BTC_JPY).csv
buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order csv file)
csv_file = self.folder_trading_type + f'sell_order({symbol}).csv'
# desktop-pc/bot/buy_sell/sell_order(BTC_JPY).csv
sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
return
#######################################
def close_pending_order(self) -> None:
"""
"""
symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
self.gvar.callers_method_name = 'close_pending_order():'
self.debug.trace_write(f".... {self.gvar.callers_method_name} ")
# df = self.coin.master2_df
# mas_df = pd.DataFrame(df) # cast df (master dataframe)
### OPEN (load the order csv file) : order(BTC_JPY).csv
ord_df = self.csv.get_order_csv()
# ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv
# buy_df = self.csv.get_buy_order() # PK(buy_time)
# ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
# buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)
### OPEN (load the buy order child log csv file) : buy_order_child(BTC_JPY).csv
buy_ch_df = self.csv.get_buy_order_child() # PK(buy_time + buy_order_id + position_id)
### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order() # PK(sell_time)
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)
# ### OPEN (load the sell order child csv file) : sell_order_child(BTC_JPY).csv
# sell_ch_df = self.csv.get_sell_order_child() # PK(sell_time + sell_order_id + position_id)
#################################################
### 【1】READ ALL SELL ORDER
#################################################
### get all sell orders
for i in range(len(sell_df)):
### get sell order info
sell_time = sell_df.loc[i, 'sell_time'] # ★ unique time
buy_time = sell_df.loc[i, 'buy_time'] # ★ unique time
sell_qty = sell_df.loc[i, 'qty']
sell_price = sell_df.loc[i, 'price']
sell_real_qty = sell_df.loc[i, 'real_qty']
sell_real_price = sell_df.loc[i, 'real_price']
sell_real_fee = sell_df.loc[i, 'real_fee']
sell_order_open = False
sell_order_closed = False
sell_order_incomplete = False
if sell_real_qty == 0:
sell_order_open = True
elif sell_qty == sell_real_qty:
sell_order_closed = True
else:
sell_order_incomplete = True
# closed sell order ? => SKIP
if sell_order_closed:
# find the sell sub order log
find_mask = sell_sub_df['sell_time'] == sell_time
dfx = sell_sub_df[find_mask]
# found the sell sub order ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
sell_order_id = dfx.loc[ix, 'sell_order_id']
if sell_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sell_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_order_id}) has been closed: {sell_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order({sell_time=:%Y-%m-%d %H:%M:%S}) has been closed: {sell_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue # skip closed sell order
# end of if sell_order_closed:
#################################################################
### 【2】READ ALL SELL ORDER SUB (OPEN OR INCOMPLETE STATUS)
#################################################################
### for sell order sub (open or incomplete status)
# filter the sell order sub by sell_time
filter_mask = sell_sub_df['sell_time'] == sell_time
sell_subx_df = sell_sub_df[filter_mask]
if sell_subx_df.shape[0] > 0:
sell_subx_df.reset_index(inplace=True)
# get all sell order sub for this sell order
for j in range(len(sell_subx_df)): # ★ sell_subx_df ★
### get sell order sub info
sub_sell_time = sell_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_time = sell_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_order_id = sell_subx_df.loc[j, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_buy_order_id = sell_subx_df.loc[j, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : sell sub(many) => buy_sub(one)
sub_position_id = sell_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = sell_subx_df.loc[j, 'qty']
sub_price = sell_subx_df.loc[j, 'price']
sub_real_qty = sell_subx_df.loc[j, 'real_qty']
sub_real_price = sell_subx_df.loc[j, 'real_price']
sub_real_fee = sell_subx_df.loc[j, 'real_fee']
sub_get_price_count = sell_subx_df.loc[j, 'get_price_count']
sub_stop_loss = sell_subx_df.loc[j, 'stop_loss']
sub_cancelled = sell_subx_df.loc[j, 'cancelled']
sub_order_open = False
sub_order_closed = False
sub_order_incomplete = False
if sub_real_qty == 0:
sub_order_open = True
elif sub_qty == sub_real_qty:
sub_order_closed = True
else:
sub_order_incomplete = True
# fake sell order sub ? => SKIP
if sub_sell_order_id.startswith('Fake') :
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id}) ")
continue
# sell order sub has been stopped loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been stopped loss ▶ {sub_stop_loss=} ")
continue
# cancelled sell order sub ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been cancelled: {sub_cancelled=} ")
continue
# closed sell order sub ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} sell order sub({sub_sell_order_id}) has been closed: {sub_order_closed=} ")
continue
### open or incomplete sell order sub
#################################################################
### 【3】CALCULATE LEVERAGE FEE AND UPDATE ORDER CSV FILE
#################################################################
### calculate leverage fee
# get buy order child
find_mask = (buy_ch_df['buy_time'] == sub_buy_time) & (buy_ch_df['buy_order_id'] == sub_buy_order_id) & (buy_ch_df['position_id'] == sub_position_id)
dfx = buy_ch_df[find_mask]
# not found buy order child ?
if dfx.shape[0] == 0:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found buy order child {Bcolors.ENDC} : {sub_buy_time=:%Y-%m-%d %H:%M:%S}, {sub_buy_order_id=}, {sub_position_id=} ")
else: # found buy order child
ix = dfx.index[0]
ch_buy_real_qty = dfx.loc[ix, 'real_qty']
ch_buy_real_price = dfx.loc[ix, 'real_price']
ch_leverage_fee = math.ceil((ch_buy_real_price * ch_buy_real_qty) * 0.0004) # leverage fee (0.04%)
### update order csv file (accumulated_leverage_fee, close_count, close_update_date)
# not empty order csv file ?
if ord_df.shape[0] > 0:
find_mask = ord_df['buy_order_id'] == sub_buy_order_id # B9999-999999-999999
dfx = ord_df[find_mask]
# not found order csv file ?
if dfx.shape[0] == 0:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found order csv file{Bcolors.ENDC} : {sub_buy_order_id=} ")
else: # found order csv file
update_order_csv = False
ix = dfx.index[0]
close_count = ord_df.loc[ix, 'close_count']
close_update_date = ord_df.loc[ix, 'close_update_date']
accumulated_leverage_fee = ord_df.loc[ix, 'accumulated_leverage_fee']
if close_count == 0:
update_order_csv = True
# self.debug.trace_warn(f"DEBUG: update_order_csv={update_order_csv}, close_count={close_count} ")
else: # close_count > 0
now_date_str = f"{dt.datetime.now():%Y%m%d}" # YYYYMMDD
close_update_date_str = f"{close_update_date:%Y%m%d}" # YYYYMMDD
if close_update_date_str != now_date_str:
update_order_csv = True
# self.debug.trace_warn(f"DEBUG: update_order_csv={update_order_csv}, close_count={close_count}, now_date_str={now_date_str}, close_update_date_str={close_update_date_str}, leverage_fee={accumulated_leverage_fee:,.0f} ")
# update_order_csv = True # DEBUG DEBUG DEBUG
# end of if close_count == 0:
if update_order_csv:
# update order csv file
ord_df.loc[find_mask, 'accumulated_leverage_fee'] += ch_leverage_fee
ord_df.loc[find_mask, 'close_count'] += 1
ord_df.loc[find_mask, 'close_update_date'] = dt.datetime.now()
ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
ord_df['close_update_date'] = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)
new_accumulated_leverage_fee = ord_df.loc[ix, 'accumulated_leverage_fee']
new_close_count = ord_df.loc[ix, 'close_count']
new_close_update_date = ord_df.loc[ix, 'close_update_date']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_buy(): {sub_buy_order_id=}, {new_accumulated_leverage_fee=:,.0f}, {new_close_count=}, {new_close_update_date=:%Y-%m-%d %H:%M:%S} ")
# end of if ord_df.shape[0] > 0:
# end of if dfx.shape[0] == 0:
# continue to next sell order sub...
# end of for j in range(len(sell_subx_df)):
# continue to next sell order... ★
# end of for i in range(len(sell_df)):
#################################################################################
### 【4】SAVE ORDER CSV TO CSV FILE
#################################################################################
### CLOSE (save to the order csv file) : order(BTC_JPY).csv
if ord_df.shape[0] > 0:
### CLOSE (order csv file)
csv_file = self.folder_trading_type + f'order({symbol}).csv'
# desktop-pc/bot/buy_sell/order(BTC_JPY).csv
ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file
return