-
SellBuyクラスを作成して__init__(), __str__()メソッドを追加する
ここではSellBuyクラスを作成して__init__(), __str__()メソッドを追加します。
SellBuyクラスには、BOTが自動トレードするための各種メソッドがまとめられています。
レバレッジ取引には「買い▶売り」と「空売り▶買戻し」の2種類ありますが、
SellBuyクラスでは後者の「空売り▶買戻し」の取引に関連するメソッドがまとめられています。
__init__()メソッドはSellBuyクラスのインスタンスを生成したときに自動的に呼ばれます。
__str__()メソッドはprint()等でSellBuyクラスを表示したときに呼ばれます。
__init__()、__str__()の詳しいことは、
「記事(Article085)」、
「記事(Article087)」で詳しく解説しています。
sell_buy.py:
# sell_buy.py
"""
SellBuy 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')
########################### sell buy class
class SellBuy:
"""
__init__(), __str__(),
reset_restart_process(),
sell_buy(), sell_buy_algorithm_0(), sell_buy_algorithm_1, sell_buy_algorithm_2(), sell_buy_algorithm_3(),...
create_sell_order(), create_buy_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/sell_buy/
self.debug.print_log(f"{Bcolors.OKGREEN}SellBuy({self.gvar.althorithm_id},{self.gvar.sellbuy_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"SellBuy({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とSellBuyクラスの各種メソッドの関連図です。
BOTのmain()から関数「auto_trade()」を呼びます。
auto_trade()では、GMOコインから取引履歴をロードします。
そしてSellBuyクラスのsell_buy()メソッドを呼び出して取引データを渡します。
sell_buy()メソッドではアルゴリズムを使用して取引のタイミングを決めてcreate_sell_order(), create_buy_order()メソッドを呼びます。
取引の処理が完了するとauto_trade()に戻ります。
auto_trade()は、次にSellBuyクラスのupdate_order()メソッドを呼びます。
update_order()では、update_closed_order()メソッドを呼んで取引が約定(執行)された注文を更新します。
さらにコール元の要求があれば、
stop_loss(), cancel_pending_order(), close_pending_order()等の処理を行います。
最後にupdate_order_profit()メソッドを呼び出して取引の損益を計算してauto_trade()に戻ります。
図1-2はデモプログラムの実行結果です。
ここではSellBuyクラスの各種プロパティを表示しています。
print()でSellBuyのオブジェクトを指定するとSellBuyクラスの__str__()メソッドが呼ばれます。
__str__()はSellBuy, Gvar, Coinクラスから各種プロパティを取得して返します。
-
SellBuyクラスにreset_restart_process()メソッドを追加する
ここではSellBuyクラスにreset_restart_process()メソッドを追加します。
このメソッドはBOTによる自動トレードで必要となる各種CSVファイルを作成して初期化します。
sell_buy.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 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 sell/buy 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 sell/buy order files...")
### 1) add a fake data into the sell order, sell order sub 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)
### 2) 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)
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
図2はデモプログラムの実行結果です。
ここではGvarクラスのreset_modeプロパティに「True」を設定して初期化モードで実行しています。
この場合、BOTのサブフォルダ「sell_buy」に売りの注文ファイル「sell_order({symbol}).csv, sell_order_sub({symbol}).csv」
と買いの注文ファイル「buy_order({symbol}).csv, buy_order_sub({symbol}).csv」が作成されて初期化されます。
CSVファイルは仮想通貨ごとに作成されます。
たとえば、「sell_order(BTC_JPY).csv, sell_order(ETH_JPY).csv, sell_order(BCH_JPY).csv,...」のようなCSVファイルが作成されます。
-
SellBuyクラスにsell_buy(), sell_buy_algorithm_0()メソッドを追加する
ここではSellBuyクラスにsell_buy()メソッドを追加します。
このメソッドはBOTの肝になる部分です。
sell_buy()メソッドの引数には「algorithm_id」を指定します。
「algorithm_id=0」はデフォルトで用意されています。
BOTのテストを行うときは「algorithm_id=0」を指定して行います。
BOTのテストが完了したら独自のアルゴリズムを作成して
新規メソッドとして「sell_buy_algorithm_1()」、「sell_buy_algorithm_2()」のように追加します。
仮想通貨を売買(トレード)するアルゴリズムは、
「記事(Article108)」で紹介しているさまざまなテクニカルインジケーターを組み合わせて作ります。
具体的な新規メソッドの作り方については後述します。
デフォルトで用意されている「sell_buy_algorithm_0()」メソッドは単純なアルゴリズムを組み込んでいます。
まずは、前回取引(トレード)した注文が約定しているか調べます。
まだ、約定していないときは何もしないで戻ります。
前回の取引が既に約定しているときは、成行きで空売りの注文を入れます。
通常、成行きの注文はすぐに約定するので注文が約定するまで待ちます。
空売りの注文が約定したら、今度は指値で買戻しの注文を入れてポジションを決済します。
指値の買戻しの注文を入れるときの金額は、
単純に空売りの金額(レート)に「1 - 利益率」を掛けて計算します。
利益率はCoinクラスの「coin.profit_rate」プロパティに設定されています。
具体的には、
空売りの注文を入れるときは「create_sell_order()」メソッド、
買戻しの注文を入れるときは「create_buy_order()」メソッドを呼びます。
sell_buy.py:
##############################################
def sell_buy(self, algorithm_id: int) -> None:
"""
"""
if algorithm_id <= 3:
eval(f'self.sell_buy_algorithm_{str(algorithm_id)}()') # call 'self.sell_buy_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 sell_buy ")
self.debug.sound_alert(3)
# if algorithm_id == 0:
# self.sell_buy_algorithm_0()
# elif algorithm_id == 1:
# self.sell_buy_algorithm_1()
# elif algorithm_id == 2:
# self.sell_buy_algorithm_2()
# elif algorithm_id == 3:
# self.sell_buy_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 sell_buy ")
# self.debug.sound_alert(3)
return
#######################################
def sell_buy_algorithm_0(self) -> None:
"""
"""
self.gvar.callers_method_name = 'sell_buy(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.shape[0]=} is less than {self.coin.trend_search_count}. skip sell_buy() ")
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.shape[0]=} is less than {self.coin.trend_search_count}. skip sell_buy() ")
self.debug.sound_alert(3)
return
self.debug.trace_write(f".... {self.gvar.callers_method_name} sell_buy_condition({self.gvar.sellbuy_condition}), {mas_buy_df.shape[0]=}, {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 files
### get the last sell order sub info
last_buy_time = sell_sub_df.iloc[-1]['buy_time']
last_sell_time = sell_sub_df.iloc[-1]['sell_time']
last_sell_real_qty = sell_sub_df.iloc[-1]['real_qty']
last_sell_real_price = sell_sub_df.iloc[-1]['real_price']
### get the last buy order (pair of the sell order)
# find the pair of the buy order
find_mask = buy_df['buy_time'] == last_buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
# self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ buy order not found: {last_buy_time:%Y-%m-%d %H:%M:%S=} ")
last_buy_real_qty = 0
else: ### found pair of the buy order
# get buy order real_qty
ix = dfx.index[0]
last_buy_real_qty = dfx.loc[ix, 'real_qty']
# closed sell/buy order ?
if round(last_buy_real_qty, _q_) == round(last_sell_real_qty, _q_):
position = 'sell' # buy order not required => position(SELL)
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}NEW SELL ORDER REQUEST{Bcolors.ENDC} ")
else: # not found buy order or incomplete buy order
return # skip return
# end of if last_buy_real_qty == last_sell_real_qty:
# sell position ?
if position.startswith('sell'):
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SELL{Bcolors.ENDC} position (short position) ")
### SELL position
### check sell condition and create a sell order
# get the latest sell_time & latest sell_price from GMO master dataframe
mas_time = mas_sell_df.iloc[-1]['time'] # gmo time ★
mas_price = mas_sell_df.iloc[-1]['price'] # gmo price ★
while True:
###########################################################
### 1: check duplicate sell order
###########################################################
# mas_time(gmo) is less than or equal to last_sell_time ? => SKIP
if mas_time <= last_sell_time:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(1){Bcolors.ENDC} mas_time(gmo) is less than or equal to last_sell_time: {mas_time=:%Y-%m-%d %H:%M:%S} <= {last_sell_time=:%Y-%m-%d %H:%M:%S} ")
break # exit while true loop
# found sell order ? => SKIP
# find_order(df: pd.DataFrame, col: str, val=any) -> bool:
if self.trade.find_order(sell_df, col='sell_time', val=mas_time):
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(2){Bcolors.ENDC} duplicate sell order: {mas_time=:%Y-%m-%d %H:%M:%S} ")
break # exit while true loop
### no duplicate sell order
###########################################################
### 2: execute sell order (MARKET) & buy order (LIMIT)
###########################################################
qty = self.coin.qty
### execute sell order with qty (MARKET)
# create_sell_order(sell_time: utcnow, qty: float, price: float) -> int:
status = self.create_sell_order(mas_time, qty, mas_price) # ★ price is used for fake mode: execute sell order with market price
if status == 0:
### reload sell order sub file
sell_sub_df = self.csv.get_sell_order_sub() # ★
### get the last sell order sub info
last_sell_time = sell_sub_df.iloc[-1]['sell_time']
### execute buy order (LIMIT)
# create_buy_order( buy_time: utcnow, ms_price: float, sell_time: utcnow) -> int:
status = self.create_buy_order(mas_time, mas_price, last_sell_time) # ★
break # exit while true loop
# end of while True:
return
########################################
def sell_buy_algorithm_1(self) -> None:
"""
"""
return
########################################
def sell_buy_algorithm_2(self) -> None:
"""
"""
return
#######################################
def sell_buy_algorithm_3(self) -> None:
"""
"""
return
図3はデモプログラムの実行結果です。
画面にはログ情報が表示されています。
BOTからSellBuyクラスのsell_buy(0)メソッドが呼ばれています。
さらにsell_buy(0)からcreate_sell_order(), create_buy_order()メソッドが呼ばれています。
sell_buy(0)では一定のアルゴリズムに従って空売りと買戻しの注文を出します。
このsell_buy()がBOTの肝になる部分です。
sell_buy()には、「記事(Article108)」
で解説しているさまざまなテクニカルインジケーターを組み合わせて新規のメソッドを
「sell_buy_algorithm_1(), sell_buy_algorithm_2(),...」のようなメソッド名で追加します。
なお、デフォルトとして「sell_buy_algorithm_0()」メソッドが用意されています。
このメソッドはBOTをテストするときに使用します。
デモプログラムでは、デフォルトのアルゴリズムを使用しています。
-
SellBuyクラスにcreate_sell_order()メソッドを追加する
ここではSellBuyクラスにcreate_sell_order()メソッドを追加します。
このメソッドでは、新規の空売りの注文を成行きの指定で入れます。
成行き注文の場合、FAK(Fill and Kill」が適用されて、
全ての数量が執行されないで終了することがあります。
この場合、create_sell_order()では残数の数量に対して再度成行きの注文を入れます。
すべての数量が執行されたらコール元に戻ります。
sell_buy.py:
############################################################################################
def create_sell_order(self, sell_time: dt.datetime.utcnow, qty: float, price: float) -> int:
"""
"""
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}, {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 SELL ORDER
################################################
### check duplicate sell order
# OPEN (load the sell order file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order()
# find a sell order
find_mask = sell_df['sell_time'] == 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
# reset buy_position_list
self.coin.buy_position_list = []
#############################################################
### 【2】EXECUTE SELL ORDER MARKET (FAK: Fill And Kill)
#############################################################
### execute sell order with market price : FAK (Fill And Kill)
sell_qty = qty # save original sell qty ★
# define sell 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_sell_order_market_wait(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_order_market_wait(sell_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_sell_order_market_wait() ▶ {status=}, {res_df.shape=} ")
####################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_sell_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug1:
# ★ create two sell order subs for each sell 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 subs
# loop_count == 2: return 1 row => add 1 buy sub
# end of if not self.coin.real_mode:
###################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_sell_order()
# exe_sell_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_sell_order{Bcolors.ENDC}({sell_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_sell_order{Bcolors.ENDC}({sell_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_sell_order{Bcolors.ENDC}({sell_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 SELL 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 sell order child
# define sell order child list
sell_order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response df for exe_sell_order_market_wait(FAK)
for k in range(len(res_df)):
sell_order_id = res_df.loc[k, 'orderId'] # sell 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
sell_order_id_list.append(sell_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_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:
_buy_time_ = sell_time; _buy_order_id_ = 'Dummy'; _qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sell_time, sell_order_id, _buy_time_, _buy_order_id_, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
first_sell_order_id = sell_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_JPY(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_) # 999.999
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 SELL ORDER SUB (buy_time, sell_time,...) ★ buy_time <= sell_time
###################################################################################################################
### 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:
_buy_time_ = sell_time; _buy_order_id_ = 'Dummy'; _qty_ = sub_sum_real_qty # positionId = 'positionId1;positionId2;positionId3'
self.csv.write_sell_order_sub(sell_time, _buy_time_, first_sell_order_id, _buy_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 sell_qty ?
if round(qty, _q_) == round(header_sum_real_qty, _q_):
# increment sell count
self.gvar.sell_count += 1
self.coin.sell_count += 1
break # exit while true loop
else: # outstanding sell_qty exists
pass # skip (do nothing)
# end of if round(buy_qty, digits) == round(header_sum_real_qty, digits):
### partial exe_sell_order_market_wait() was executed => continue to sell remaining qty
remaining_qty = sell_qty - header_sum_real_qty # calculate new sell qty
sell_qty = remaining_qty # update new sell qty
# continue exe_sell_order_market_wait() ★
# end of while True:
#################################################################################
### 【5】ADD A SELL ORDER HEADER (buy_time, sell_time) ★ buy_time <= sell_time
#################################################################################
### update sell 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(9999.123)
else:
header_mean_real_price = 0
### write a sell order
# 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:
_buy_time_ = sell_time
self.csv.write_sell_order(sell_time, _buy_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_sell_order() ▶ {status=} normal return... ")
return status
図4-1はデモプログラムの実行結果です。
ここでは関数「auto_trade()」からSellBuyクラスのsell_buy(althorithm_id=0)メソッドを呼び出しています。
sell_buy()メソッドの引数に「althorithm_id=0」を指定しているので「アルゴリズム0」が適用されて空売り・買戻しの取引を行います。
空売りのタイミングになるとSellBuyクラスのcreate_sell_order()メソッドを呼び出して成行きの空売りの注文を入れます。
図4-2はcreate_sell_order()で作成したCSVファイルをExcelで開いた画像です。
「sell_order(XRP_JPY).csv」ファイルには「sell_time, qty, price, real_qty, real_price,...」などが格納されています。
「real_qty, real_price」には約定したときの数量と価格(レート)が格納されます。
ここでは数量(10XRP_JPY)を注文しています。
「sell_order_sub(XRP_JPY).csv」ファイルには「sell_time, sell_order_id, position_id, qty, price, real_qty, real_price,...」などが格納されています。
「position_id」のポジションIDは買戻しの注文を入れるときに使います。空売りの注文は成行きなので通常すぐに約定します。
「sell_order_child(XRP_JPY).csv」ファイルには「sell_time, sell_order_id, position_id, qty, price, reqal_qty, real_price,...」などが格納されています。
空売りの注文のCSVファイルがなぜ3段階に階層化されているかと言えば、注文が約定されるとき数量が分割されるからです。
たとえば、数量「100XRP_JPY」を成行きで注文すると「20, 30, 50」のように分割されて約定されることがあります。
さらに、成行き注文のときは(FAK: Fill and Kill)が適用されて全数約定しないことがあります。
このような場合、create_sell_order()は残数の成行き注文を再度入れて全数約定するようにしています。
たとえば、数量「100」を成行きで注文して「70」だけ約定したときは、再度「30」で成行き注文を入れます。
この場合、1回の空売りの注文で2個の注文IDが生成されることになります。
こういった理由で、空売りの注文のCSVファイルは3階層になっています。
ちなみに、買い注文のCSVファイルも同様に3階層になっています。
-
SellBuyクラスにcreate_buy_order()メソッドを追加する
ここではSellBuyクラスにcreate_buy_order()メソッドを追加します。
このメソッドは前出のcreate_sell_order()で空売りした仮想通貨を決済するために、
指値で買戻しの注文を入れます。
指値の場合、成行きの注文と異なり「Fill and Store」が適用されます。
したがって、成行きのように途中で注文が終了することはありません。
sell_buy.py:
################################################################################################################
def create_buy_order(self, buy_time: dt.datetime.utcnow, ms_price: float, sell_time: dt.datetime.utcnow) -> int:
"""
"""
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}, {ms_price=:,.{_p_}f}, {sell_time=:%Y-%m-%d %H:%M:%S}) ")
status = 0
################################################
### 【1】CHECK DUPLICATE BUY ORDER
################################################
### check duplicate buy order
# get the buy order : sbuy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order()
# find a buy order
find_mask = buy_df['buy_time'] == buy_time # PK(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
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub()
### OPEN (load the sell order child csv file ) : sell_order_child(BTC_JPY).csv
sell_ch_df = self.csv.get_sell_order_child()
######################################################################
### 【2】READ ALL SELL ORDER SUBs (filter by sell_time)
######################################################################
# define sell order sub list
sub_qty_list = []
sub_price_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 filtered by sell_time
for i in range(len(sell_subx_df)): # ★ sell_subx_df ★
### get sell order sub info
sub_sell_time = sell_subx_df.loc[i, 'sell_time'] # non unique
sub_sell_order_id = sell_subx_df.loc[i, 'sell_order_id'] # unique id
sub_qty = sell_subx_df.loc[i, 'qty']
sub_price = sell_subx_df.loc[i, 'price']
sub_real_qty = sell_subx_df.loc[i, 'real_qty']
sub_real_price = sell_subx_df.loc[i, 'real_price']
sub_real_fee = sell_subx_df.loc[i, 'real_fee']
#################################################################################
### 【3】READ ALL SELL ORDER CHILD (filter by sell_time + sell_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 sell order child by sell_time + selll_order_id
# sell order child is empty ?
if sell_ch_df.shape[0] == 0:
sell_chx_df = pd.DataFrame()
else: # filter sell order child
filter_mask = (sell_ch_df['sell_time'] == sell_time) & (sell_ch_df['sell_order_id'] == sub_sell_order_id)
sell_chx_df = sell_ch_df[filter_mask]
if sell_chx_df.shape[0] > 0:
sell_chx_df.reset_index(inplace=True)
# end of if sell_ch_df.shape[0] == 0:
# get all sell order child filtered by sell_time + sell_order_id
for j in range(len(sell_chx_df)): # ★ sell_chx_df ★
### get buy order child info
ch_sell_time = sell_chx_df.loc[j, 'sell_time'] # non unique time
ch_sell_order_id = sell_chx_df.loc[j, 'sell_order_id'] # non unique id (same sell_order_id)
ch_position_id = sell_chx_df.loc[j, 'position_id'] # unique id
ch_qty = sell_chx_df.loc[j, 'qty']
ch_price = sell_chx_df.loc[j, 'price']
ch_real_qty = sell_chx_df.loc[j, 'real_qty']
ch_real_price = sell_chx_df.loc[j, 'real_price']
ch_real_fee = sell_chx_df.loc[j, 'real_fee']
#############################################################################################
### 【4】EXECUTE BUY CLOSE ORDER LIMIT (FAS:Fill And Store) FOR EACH SELL ORDER CHILD
#############################################################################################
buy_qty = ch_real_qty
buy_price = self.trade.get_buy_price(ch_real_price, trace=True)
# market price is less than than buy price
if ms_price < buy_price:
buy_price = ms_price
### execute a buy close order with limit(FAS)
# exe_buy_close_order_limit(position_id: str, qty: float, price: float) ->tuple: # return (status, res_order_id, msg_code, msg_str)
status, res_buy_order_id, msg_code, msg_str = self.api.exe_buy_close_order_limit(ch_position_id, buy_qty, buy_price) # ★
# res_buy_order_id => 'B9999-999999-999999'
# exe_buy_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_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}the available balance exceeded ERR-208: continue...{Bcolors.ENDC} ")
self.debug.sound_alert(2)
continue # continue to next sell order child
else: # misc error
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
self.debug.sound_alert(2)
continue # continue to next sell order child
# end of if status != 0:
### no error for exe_buy_close_order_limit()
################################################################################################################
### 【5】ADD A BUY ORDER SUB
################################################################################################################
### 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:
self.csv.write_buy_order_sub(buy_time, sell_time, res_buy_order_id, ch_sell_order_id, ch_position_id, buy_qty, buy_price)
ch_buy_order_id_list.append(res_buy_order_id)
ch_sell_order_id_list.append(ch_sell_order_id)
ch_position_id_list.append(ch_position_id)
ch_qty_list.append(buy_qty)
ch_price_list.append(buy_price)
# continue to next sell order child
# end of for j in range(len(sell_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 SELL ORDER SUB (buy_order_id, buy_time)
#####################################################################################
buy_order_ids_by_semicolon = '' # 'BuyOrderID1;BuyOrderID2;BuyOrderID3'
for k, buy_order_id_x in enumerate(ch_buy_order_id_list):
if k == 0:
buy_order_ids_by_semicolon += buy_order_id_x
else:
buy_order_ids_by_semicolon += ';' + buy_order_id_x
### update sell order sub (buy_order_id, buy_time)
find_mask = (sell_sub_df['sell_time'] == 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".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}(): {Bcolors.FAIL} sell order sub not found ▶ {sub_sell_order_id=} {Bcolors.ENDC} ")
else: # found sell order sub => update buy_order_id, buy_time
sell_sub_df.loc[find_mask, 'buy_order_id'] = buy_order_ids_by_semicolon # 'BuyOrderID1;BuyOrderID2;BuyOrderID3'
sell_sub_df.loc[find_mask, 'buy_time'] = buy_time
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sell_time=:%Y-%m-%d %H:%M:%S}, {sub_sell_order_id=}, {buy_order_ids_by_semicolon=}, {buy_time=:%Y-%m-%d %H:%M:%S}) ")
# end of if dfx.shape[0] == 0:
# continue to next sell order sub
# end of for i in range(len(sell_subx_df)):
#################################################################################
### 【7】SAVE SELL ORDER SUB TO CSV FILE
#################################################################################
### 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/sell_buy/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
################################################################################
### 【8】ADD A BUY ORDER HEADER
################################################################################
### update buy 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_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, sub_sum_qty, sub_mean_price)
################################################################################
### 【9】UPDATE A SELL ORDER HEADER (buy_time)
################################################################################
# update_sell_order(sell_time: utcnow, col_name_list: list, col_value_list: list) -> object: # PK(sell_time)
self.csv.update_sell_order(sell_time, col_name_list=['buy_time'], col_value_list=[buy_time])
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_order() ▶ {status=} normal return... ")
return status
図5-1はデモプログラムの実行結果です。
ここでは関数「auto_trade()」からSellBuyクラスのsell_buy(althorithm_id=0)メソッドを呼び出しています。
sell_buy()メソッドの引数に「althorithm_id=0」を指定しているの「アルゴリズム0」が適用されて空売り・買戻しの取引を行います。
買戻しのタイミングになるとSellBuyクラスのcreate_buy_order()メソッドを呼び出して指値の買戻しの注文(売りポジションの決済)を入れます。
つまり、前出のcreate_sell_order()メソッドで注文した売りポジションを決済しています。
画面に表示されているログから分かるように内部的には、
Apiクラスのexe_buy_close_order_limit()メソッドを呼び出して指値の買戻しの注文を入れています。
図5-2はcreate_buy_order()メソッドで作成した買戻し注文のCSVファイルをExcelで開いた画像です。
「buy_order(XRP_JPY).csv」ファイルには「buy_time, qty, price, real_qty, real_price,...」などが格納されています。
「qty, price」は指値で買戻し注文を入れたときの数量と価格です。
「real_qty, real_price」は注文が約定したときの数量、価格(レート)です。
「buy_order_sub(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, qty, price, real_qty, real_price, sell_order_id,..」
などが格納されています。
「real_qty, real_price」には約定したときの数量と価格(レート)が格納されます。
買戻しの注文はすでに約定しているので「real_qty, real_price」に約定した数量と金額(レート)が格納されています。
このファイルには対応する空売りの注文ID(sell_order_id)も格納されます。
「buy_order_child(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, postition_id, real_qty, real_price,...」などが格納されます。
「real_qty, real_price」には約定したときの数量と価格(レート)が格納されます。
買戻しの注文はすでに約定しているので「real_qty, real_price」に約定した数量と金額(レート)が格納されています。
-
SellBuyクラスにupdate_order(), update_closed_order(), update_partially_closed_order()メソッドを追加する
ここではSellBuyクラスに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ファイルを更新して保存します。
具体的には約定したときの数量(執行数量)、金額(レート)等を更新します。
sell_buy.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
# 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 : low to high price 100, 110, 120, 130, 140, 150
buy_bk_df = pd.DataFrame(buy_bk) # descending order of 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 sell_close_order
# 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
# incomplet buy order (pending buy 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
# buy_bk_df = pd.DataFrame(buy_bk) # descending order of price
# not empty order books ?
if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:
### get sell_close_order
# 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 buy price from the buy order sub
last_buy_price = buy_sub_df.iloc[-1]['price'] # 120
# get buy position number (sell => buy)
buy_bk_df.sort_values('price', ascending=True, inplace=True) # sort in ascending order of price ★
filter_mask = buy_bk_df['price'] > last_buy_price
dfx = buy_bk_df[filter_mask]
buy_position = dfx.shape[0]
# update buy position count
if buy_position >= 91:
self.coin.position_count_91_999 += 1
elif buy_position >= 71:
self.coin.position_count_71_90 += 1
elif buy_position >= 51:
self.coin.position_count_51_70 += 1
elif buy_position >= 31:
self.coin.position_count_31_50 += 1
elif buy_position >= 11:
self.coin.position_count_11_30 += 1
else:
self.coin.position_count_0_10 += 1
self.coin.buy_position_list.append(buy_position) # append buy_position : coin.buy_position=[10, 8, 5, 3, 1, 0]
self.debug.trace_write(f".... {self.gvar.callers_method_name}{Bcolors.WARNING} {buy_position=}{Bcolors.ENDC} => close condition [{last_buy_price=:,.{_p_}f} >= {bk_sell_price=:,.{_p_}f}] ") # 120 >= 100
reverse_buy_position_list = self.coin.buy_position_list[::-1] # [0, 1, 3, 5, 8, 10]
if len(reverse_buy_position_list) < 21:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_buy_position_list=} ")
else:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_buy_position_list[0:21]=} ")
else: # empty order books
self.coin.buy_position_list = [] # reset buy position list
# end of if not order_completed:
#################################################
### 【1】READ ALL BUY ORDER HEADER
#################################################
### get all buy order header
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # unique time
sell_time = buy_df.loc[i, 'sell_time'] # unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
dfx.reset_index(inplace=True)
buy_order_id = str(dfx.loc[0, 'buy_order_id'])
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id}) has been closed: {buy_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
continue
# end of if buy_order_closed:
### open or incomplete buy order header
##########################################################################################
### 【2】READ ALL BUY ORDER SUB & EXECUTE GET_PRICE() FOR EACH BUY ORDER SUB
##########################################################################################
### for buy order sub : use pandas dataframe groupby()
buy_order_sub_seq = 0
# 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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★
sub_position_id = buy_subx_df.loc[j, 'position_id'] # (unique id)
sub_buy_qty = buy_subx_df.loc[j, 'qty']
sub_buy_price = buy_subx_df.loc[j, 'price']
sub_buy_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_buy_real_price = buy_subx_df.loc[j, 'real_price']
sub_buy_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_buy_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_buy_cancelled = buy_subx_df.loc[j, 'cancelled']
sub_buy_order_open = False
sub_buy_order_closed = False
sub_buy_order_incomplete = False
if sub_buy_real_qty == 0:
sub_buy_order_open = True
elif sub_buy_qty == sub_buy_real_qty:
sub_buy_order_closed = True
else:
sub_buy_order_incomplete = True
# fake buy order sub ? => 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
# cancelled buy order sub ? => SKIP
if sub_buy_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been cancelled ▶ {sub_buy_cancelled=} ")
continue
# stop loss buy order sub ? => SKIP
if sub_buy_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been stopped loss ▶ {sub_buy_stop_loss=} ")
continue
# closed buy order sub ? => SKIP
if sub_buy_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed ▶ {sub_buy_order_closed=} ")
continue
### open or incomplete buy order sub
############################################################
### 【3】UPDATE BUY ORDER SUB (get_price_count+1)
############################################################
### update buy order sub (get_price_count += 1) ★
# DO NOT USE: self.csv.update_buy_order_sub_count(buy_time, buy_order_id, symbol)
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub_count({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub
buy_sub_df.loc[find_mask, 'get_price_count'] += 1 # increment get_price_count by 1
ix = dfx.index[0]
sub_buy_get_price_count = buy_sub_df.loc[ix, 'get_price_count']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub_count({sub_buy_order_id=}, {sub_buy_get_price_count=}) ")
# end of if dfx.shape[0] == 0:
buy_order_sub_seq = j + 1 # 1, 2, 3,....
### get order_status
qty = sub_buy_qty
price = sub_buy_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(sub_buy_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({sub_buy_order_id=}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")
continue # continue to next buy order sub
## get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
# pending exe_buy_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({sub_buy_order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_buy_close_order_limit() request ")
continue # continue to next buy order sub
############################################# DEBUG DEBUG DEBUG
if self.coin.debug_mode_gmo_stop_loss:
order_status = 'EXPIRED'
############################################# DEBUG DEBUG DEBUG
### executed exe_buy_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'
if order_status in 'EXPIRED,CANCELED':
### update buy order, buy 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({sub_buy_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 buy order sub
if order_status == 'EXPIRED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")
self.debug.sound_alert(3)
###########################################
### 1: close buy order sub
###########################################
# update a buy order sub (update real_qty, real_price, stop_loss)
# DO NOT USE: self.csv.update_buy_order_sub(buy_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee, stop_loss)
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
buy_stop_loss = True
buy_sub_df.loc[find_mask, 'real_qty'] = sub_buy_qty
buy_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
buy_sub_df.loc[find_mask, 'real_fee'] = gmo_loss_cut_fee # gmo loss cut fee
buy_sub_df.loc[find_mask, 'stop_loss'] = buy_stop_loss # buy order stop loss
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, {sub_buy_qty=:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {buy_stop_loss=}) ")
# end of if dfx.shape[0] == 0:
#######################################
### 2: close buy order
#######################################
# update buy order (real_qty, real_price, real_fee)
find_mask = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
buy_df.loc[find_mask, 'real_qty'] = buy_qty
buy_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
buy_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_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={buy_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 sell_order_id
find_mask = ord_df['sell_order_id'] == sub_sell_order_id
dfx = ord_df[find_mask]
if dfx.shape[0] > 0:
# update_order_csv_sell(sell_time: utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(sell_time + sell_order_id)
self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])
continue # continue to next buy order sub
# end of if order_status == 'EXPIRED':
# end of if order_status in 'EXPIRED,CANCELED':
### get lowest sell price from sell orderbooks
latest_book_price = bk_sell_price # bk_sell_price = sell_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) => usef for fake mode
_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 buy real qty, price & fee from the GMO : get_price() for exe_buy_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_buy_order_id, sub_buy_qty, sub_buy_price, latest_book_price, _debug1_, _debug2_) # ★ qty, price, close_price are used for fake mode
# BUG status, res_df, msg_code, msg_str = self.api.get_price_try(sub_sell_order_id, sub_buy_qty, sub_buy_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, buy_order_sub_seq) # 1, 2, 3, ...
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({buy_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 buy order sub depending on get_price_count ★
# get buy 'get_price_count'
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# found buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
get_price_count = buy_sub_df.loc[ix, 'get_price_count'] # buy_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_buy_close_order_limit() request ?
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_buy_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_buy_close_order_limit() request: continue to next buy order sub... ")
status = 0 # reset status code
continue # continue to next buy 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_buy_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
continue # continue to next buy order sub
# end of if status == 9:
else: # normal return => executed exe_buy_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 BUY ORDER SUB (real_qty, real_price, real_fee)
####################################################################
# res_df = get_price() response for exe_buy_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 buy 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_buy_order_sub(buy_time, sub_order_id, col_name_list, col_value_list)
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee)
buy_sub_df.loc[find_mask, 'real_qty'] = sub_sum_real_qty
buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price
buy_sub_df.loc[find_mask, 'real_fee'] = sub_sum_real_fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_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 BUY ORDER CHILD (buy_time, buy_order_id, real_qty, real_price, real_fee) + (sell_time, sell_order_id)
##############################################################################################################################
### check exit condition : sub_buy_qty=sub_buy_qty, sub_sum_real_qty=sum(res_df['size'])
########################################### TEMP TEMP TEMP
# temp_buy_qty = round(sub_buy_qty, _q_)
# temp_real_qty = round(sub_sum_real_qty, _q_)
# self.debug.trace_warn(f".... DEBUG: {temp_buy_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
########################################## TEMP TEMP TEMP
# completed exe_buy_close_order_limit() request ?
if round(sub_buy_qty, _q_) == round(sub_sum_real_qty, _q_):
# changed open => closed
if self.coin.real_mode:
self.debug.sound_alert(1)
# increment buy count
self.gvar.buy_count += 1
self.coin.buy_count += 1
# response df for get_price(): exe_buy_close_order_limit()
for k in range(len(res_df)):
buy_order_id = res_df.loc[k, 'orderId'] # buy 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 buy order child ★
# 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:
_qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(sub_buy_time, buy_order_id, sub_sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
else: # outstanding buy_qty exists => incomplete status
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} incomplete buy order sub({sub_buy_order_id=}) ▶ buy_qty={sub_buy_qty:,.{_q_}f}, buy_real_qty={sub_sum_real_qty:,.{_q_}f} ")
# pass # skip (do nothing)
# end of if round(sub_buy_qty, digits) == round(sub_sum_real_qty, digits):
# continue to next buy order sub
# end of for j in range(len(buy_subx_df)):
#################################################################################
### 【6】SAVE BUY/SELL ORDERS 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/sell_buy/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/sell_buy/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【7】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
#################################################################################
### calculate total buy qty / fee and mean price ★ buy_time ★
filter_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[filter_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order sub({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub
header_sum_real_qty = dfx.groupby('buy_time')['real_qty'].sum().values[0]
header_sum_real_fee = dfx.groupby('buy_time')['real_fee'].sum().values[0]
header_mean_real_price = dfx.groupby('buy_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 buy order (real_qty, real_price, real_fee) ★
find_mask = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order
buy_df.loc[find_mask, 'real_qty'] = header_sum_real_qty
buy_df.loc[find_mask, 'real_price'] = header_mean_real_price
buy_df.loc[find_mask, 'real_fee'] = header_sum_real_fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_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 buy order... ★
# end of for i in range(len(buy_df)):
#################################################################################
### 【8】SAVE BUY/SELL ORDER HEADER 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/sell_buy/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/sell_buy/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 : low to high price 100, 110, 120, 130, 140, 150
buy_bk_df = pd.DataFrame(buy_bk) # descending order of 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 sell_close_order
# 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 BUY ORDER HEADER
#################################################
### get all buy order header
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # unique time
sell_time = buy_df.loc[i, 'sell_time'] # unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
dfx.reset_index(inplace=True)
buy_order_id = str(dfx.loc[0, 'buy_order_id'])
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id}) has been closed: {buy_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
continue
# end of if buy_order_closed:
### open or incomplete buy order header
##########################################################################################
### 【2】READ ALL BUY ORDER SUB & EXECUTE GET_PRICE() FOR EACH BUY ORDER SUB
##########################################################################################
### for buy order sub : use pandas dataframe groupby()
buy_order_sub_seq = 0
# 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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
buy_order_sub_seq = j + 1 # 1, 2, 3,....
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★
sub_position_id = buy_subx_df.loc[j, 'position_id'] # (unique id)
sub_buy_qty = buy_subx_df.loc[j, 'qty']
sub_buy_price = buy_subx_df.loc[j, 'price']
sub_buy_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_buy_real_price = buy_subx_df.loc[j, 'real_price']
sub_buy_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_buy_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_buy_cancelled = buy_subx_df.loc[j, 'cancelled']
sub_buy_order_open = False
sub_buy_order_closed = False
sub_buy_order_incomplete = False
if sub_buy_real_qty == 0:
sub_buy_order_open = True
elif sub_buy_qty == sub_buy_real_qty:
sub_buy_order_closed = True
else:
sub_buy_order_incomplete = True
# fake buy order sub ? => 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
# cancelled buy order sub ? => SKIP
if sub_buy_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been cancelled ▶ {sub_buy_cancelled=} ")
continue
# stop loss buy order sub ? => SKIP
if sub_buy_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been stopped loss ▶ {sub_buy_stop_loss=} ")
continue
# closed buy order sub ? => SKIP
if sub_buy_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed ▶ {sub_buy_order_closed=} ")
continue
### open or incomplete buy order sub
############################################################
### 【3】GET ORDER STATUS
############################################################
### get order_status
qty = sub_buy_qty
price = sub_buy_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(sub_buy_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({sub_buy_order_id=}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")
continue # continue to next buy order sub
## get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
# pending exe_buy_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({sub_buy_order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_buy_close_order_limit() request ")
continue # continue to next buy order sub
############################################# DEBUG DEBUG DEBUG
if self.coin.debug_mode_gmo_stop_loss:
order_status = 'EXPIRED'
############################################# DEBUG DEBUG DEBUG
### executed exe_buy_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'
if order_status in 'EXPIRED,CANCELED':
### update buy order, buy 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({sub_buy_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 buy order sub
if order_status == 'EXPIRED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")
self.debug.sound_alert(3)
###########################################
### 1: close buy order sub
###########################################
# update a buy order sub (update real_qty, real_price, stop_loss)
# DO NOT USE: self.csv.update_buy_order_sub(buy_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee, stop_loss)
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
buy_stop_loss = True
buy_sub_df.loc[find_mask, 'real_qty'] = sub_buy_qty
buy_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
buy_sub_df.loc[find_mask, 'real_fee'] = gmo_loss_cut_fee # gmo loss cut fee
buy_sub_df.loc[find_mask, 'stop_loss'] = buy_stop_loss # buy order stop loss
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, {sub_buy_qty=:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {buy_stop_loss=}) ")
# end of if dfx.shape[0] == 0:
#######################################
### 2: close buy order
#######################################
# update buy order (real_qty, real_price, real_fee)
find_mask = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
buy_df.loc[find_mask, 'real_qty'] = buy_qty
buy_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
buy_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_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={buy_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 sell_order_id
find_mask = ord_df['sell_order_id'] == sub_sell_order_id
dfx = ord_df[find_mask]
if dfx.shape[0] > 0:
# update_order_csv_sell(sell_time: utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(sell_time + sell_order_id)
self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])
continue # continue to next buy order sub
# end of if order_status == 'EXPIRED':
# end of if order_status in 'EXPIRED,CANCELED':
### get lowest sell price from sell orderbooks
latest_book_price = bk_sell_price # bk_sell_price = sell_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) => usef for fake mode
_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 buy real qty, price & fee from the GMO : get_price() for exe_buy_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_buy_order_id, sub_buy_qty, sub_buy_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, buy_order_sub_seq) # 1, 2, 3, ...
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({buy_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 buy order sub depending on get_price_count ★
# get buy 'get_price_count'
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# found buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
get_price_count = buy_sub_df.loc[ix, 'get_price_count'] # buy_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_buy_close_order_limit() request ?
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_buy_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_buy_close_order_limit() request: continue to next buy order sub... ")
status = 0 # reset status code
continue # continue to next buy 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_buy_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
continue # continue to next buy order sub
# end of if status == 9:
else: # normal return => executed exe_buy_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 BUY ORDER CHILD (buy_time, buy_order_id, real_qty, real_price, real_fee) + (sell_time, sell_order_id)
##############################################################################################################################
# response df for get_price(): exe_buy_close_order_limit()
for k in range(len(res_df)):
buy_order_id = res_df.loc[k, 'orderId'] # buy 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 buy order child ★
# 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:
_qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(sub_buy_time, buy_order_id, sub_sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
# continue to next buy order sub
# end of for j in range(len(buy_subx_df)):
# continue to next buy order... ★
# end of for i in range(len(buy_df)):
return
図6-1はデモプログラムの実行結果です。
この画面は指値の買戻し注文が約定したときの画面です。
Apiクラスのget_order_status()の戻り値が「EXECUTED」になっているので約定しています。
Apiクラスのget_price()では約定した買戻し注文の数量、金額(レート)等を取得しています。
図6-2は空売り・買戻し注文のときに作成したCSVファイルをExcelで開いた画面です。
「sell_order_(XRP_JPY).csv」ファイルには「sell_time, qty, price, real_qty, real_price,..」などが格納されています。
「real_qty, real_price」は約定した数量と金額(レート)です。
「sell_order_sub(XRP_JPY).csv」ファイルには「sell_order_id, position_id, real_qty, real_price,...」などが格納されています。
「position_id」は買戻しの注文を出すときに使います。
「sell_order_child(XRP_JPY).csv」ファイルには「sell_order_id, position_id, real_qty, real_price,...」などが格納されています。
「buy_order_child(XRP_JPY).csv」ファイルには「buy_order_id, position_id, qty, price, real_qty, real_price,...」などが格納されています。
ここでは数量「40」で買戻したの注文を出したのに数量が「20, 10, 10」に分割されて約定しています。
図6-3は買戻し注文で作成されたCSVファイルをExcelで開いた画像です。
「buy_order(XRP_JPY).csv」ファイルには「buy_time, qty, price, real_qty, real_price, buy_time,...」などが格納されています。
「real_qty, real_price」は約定した数量と金額(レート)です。
「buy_order_sub(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, qty, price, real_qty, real_price, sell_order_id,...」などが格納されています。
「buy_order_child(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, real_qty, real_price, sell_order_id,..」などが格納されています。
ここでは数量「40」で買戻しの注文を出したのに数量が「20, 10, 10」に分割されて約定しています。
-
SellBuyクラスにupdate_order_profit()メソッドを追加する
ここではSellBuyクラスにupdate_order_profit()メソッドを追加します。
このメソッドは取引の損益を計算してCSVファイルとExcelファイルに保存します。
sell_buy.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 SELL ORDER SUB INFO & APPEND/UPDATE ORDER CSV FILE (sell_time, sell_order_id, sell_real_qty, sell_real_price, sell_real_fee) & (buy_time, buy_order_id)
###############################################################################################################################################################################
### OPEN (load the order csv csv file) : order(BTC_JPY).csv
ord_df = self.csv.get_order_csv()
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub()
### import buy order sub file
# get all sell order sub
for i in range(len(sell_sub_df)):
### get sell sub order 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)
sub_buy_order_id = sell_sub_df.loc[i, 'buy_order_id'] # ★ 'B9999-999999-999999;B9999-999999-999999;B9999-999999-999999' (list of buy_order_ids) : sell sub(one) <= buy sub(many)
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_closed = True if sub_sell_qty == sub_sell_real_qty else False
# fake 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
# empty order csv ?
if ord_df.shape[0] == 0:
dfx = pd.DataFrame()
else: # not empty dataframe => find the order csv by sell_order_id
find_mask = ord_df['sell_order_id'] == sub_sell_order_id # S9999-999999-999999
dfx = ord_df[find_mask]
# not found order csv ?
if dfx.shape[0] == 0:
### append order csv ★IMPORTANT★ DO NOT APEND 'Null' Value => buy_order_id = 'B9999-999999-999999'
columns = Gmo_dataframe.ORDER_CSV
_buy_real_qty_=0.0; _buy_real_price_=0.0; _buy_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_sell_stop_loss; _cancelled_=sub_sell_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, _buy_real_qty_, sub_sell_real_qty, _buy_real_price_, sub_sell_real_price, _buy_real_fee_, sub_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 info row)
ord_sell_order_id = sub_sell_order_id
ord_sell_real_qty = sub_sell_real_qty
ord_sell_real_price = sub_sell_real_price
ord_sell_real_fee = sub_sell_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_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=}) ")
else: # found order csv => update order csv
# 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']
# ord_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_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=}) ")
else: # incomplete order csv => update order csv
# update order csv
ord_sell_time = sub_sell_time
ord_sell_order_id = sub_sell_order_id
ord_sell_real_qty = sub_sell_real_qty
ord_sell_real_price = sub_sell_real_price
ord_sell_real_fee = sub_sell_real_fee
ord_df.loc[find_mask, 'sell_time'] = ord_sell_time
ord_df.loc[find_mask, 'sell_order_id'] = ord_sell_order_id
ord_df.loc[find_mask, 'sell_real_qty'] = ord_sell_real_qty
ord_df.loc[find_mask, 'sell_real_price'] = ord_sell_real_price
ord_df.loc[find_mask, 'sell_real_fee'] = ord_sell_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_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:
# end of if dfx.shape[0] == 0:
# continue to next sell order sub
# end of for i in range(len(sell_sub_df)):
#######################################################################################################################################
### 【2】IMPORT BUY ORDER SUB INFO & UPDATE ORDER CSV FILE (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee)
#######################################################################################################################################
### 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
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' (non unique id) : sell sub(many) => buy_sub(one)
sub_sell_order_id = buy_sub_df.loc[i, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
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_sell_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_open = False
sub_buy_order_closed = False
sub_buy_order_incomplete = False
if sub_buy_real_qty == 0:
sub_buy_order_open = True
elif sub_buy_qty == sub_buy_real_qty:
sub_buy_order_closed = True
else:
sub_buy_order_incomplete = True
# fake buy order sub ? => 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 ?
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 is empty: continue... ")
self.debug.sound_alert(3)
continue
# find the order csv by sell_order_id
find_mask = ord_df['sell_order_id'] == sub_sell_order_id
dfx = ord_df[find_mask]
# not found order csv ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} order csv not found: sell_order_id={sub_sell_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_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 ?
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
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=}) ")
else: # incomplete order csv => update order csv
### get a list of buy_order_id from the buy sub orders ★ sell_order_id ★
filter_mask = buy_sub_df['sell_order_id'] == sub_sell_order_id
dfx = buy_sub_df[filter_mask]
buy_order_id_list = dfx.groupby('sell_order_id')['buy_order_id'].apply(list)[0]
buy_order_ids_by_semicolon = ''
for k, order_id in enumerate(buy_order_id_list):
if k == 0:
buy_order_ids_by_semicolon += order_id
else:
buy_order_ids_by_semicolon += ';' + order_id
### calculate total sell qty / fee and mean price ★ sell_order_id ★
buy_sum_real_qty = dfx.groupby('sell_order_id')['real_qty'].sum().values[0]
buy_sum_real_fee = dfx.groupby('sell_order_id')['real_fee'].sum().values[0]
buy_mean_real_price = dfx.groupby('sell_order_id')['real_price'].mean().values[0]
### update buy csv (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee)
ord_buy_real_qty = round(buy_sum_real_qty, _q_) # round(999.12, _q_)
ord_buy_real_price = round(buy_mean_real_price, _p_) # round(999999.123, _p_)
ord_buy_real_fee = round(buy_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, 'buy_time'] = sub_buy_time
ord_df.loc[find_mask, 'buy_order_id'] = buy_order_ids_by_semicolon # 'OrderId1;OrderId2;OrderId3'
ord_df.loc[find_mask, 'buy_real_qty'] = ord_buy_real_qty # round(buy_sum_real_qty, _q_)
ord_df.loc[find_mask, 'buy_real_price'] = ord_buy_real_price # round(buy_mean_real_price, _p_)
ord_df.loc[find_mask, 'buy_real_fee'] = ord_buy_real_fee # round(buy_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_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:
# continue to next buy order sub
# end of for i in range(len(buy_sub_df)):
#######################################################################################################################################
### 【3】SAVE ORDER CSV 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/sell_buy/order(BTC_JPY).csv
ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file
### SAVE (order csv excel file)
# save to the order excel file
excel_file = self.folder_trading_type + f'order({symbol}).xlsx'
# desktop-pc/bot/sell_buy/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 columns
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_by(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」を設定しています。
注文数は最小値で利益率も小さいので金額は無視して約定の回数に注目してください。
画像にはSellBuyクラスの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」が2回、「LTC_JPY」と「XRP_JPY」が1回約定しています。
BOTのログには仮想通貨ごとの約定回数と損益が集計されてリアルタイムで表示されます。
-
SellBuyクラスにstop_loss(), stop_loss_0()メソッドを追加する
ここではSellBuyクラスにstop_loss()メソッドを追加します。
このメソッドは価格が暴騰したときに強制的にポジションを決済して損失が大きくなるのを回避します。
stop_loss()もBOTの肝となる部分です。
sell_buy()とstop_loss()の作り方によって利益がでるかどうかが決まります。
stop_loss()の引数には「method_id」を指定します。
デフォルトで「method_id=0」が用意されています。
stop_loss_0()メソッドはBOTをテストするときに使用します。
BOTのテストが完了したら本番用のストップ・ロスのメソッドを作って
「stop_loss_1(), stop_loss_2(),...」のようなメソッド名を追加します。
sell_buy.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.sell_buy_action14}), 15%({self.coin.sell_buy_action13}), 10%({self.coin.sell_buy_action12}) ")
# ### OPEN (load the order csv 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 BUY ORDER HEADER
#################################################
# define buy order header list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
### get all buy order
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # ★ unique time
sell_time = buy_df.loc[i, 'sell_time'] # ★ unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
buy_order_id = dfx.loc[ix, 'buy_order_id']
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
else:
pass #self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({buy_order_id=}) has been closed: {buy_order_closed=} ")
else:
pass #self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue # skip closed order
# end of if buy_order_closed:
#################################################################
### 【2】READ BUY ORDER SUB (open or incomplete status)
#################################################################
### for buy order sub (open or incomplete status)
# define buy order sub list
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★ (non unique id) : buy sub(many) => sell sub(one)
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★ (unique id)
sub_position_id = buy_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = buy_subx_df.loc[j, 'qty']
sub_price = buy_subx_df.loc[j, 'price']
sub_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_real_price = buy_subx_df.loc[j, 'real_price']
sub_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_get_price_count = buy_subx_df.loc[j, 'get_price_count']
sub_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_cancelled = buy_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 buy order sub ? => 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
# buy order sub has been stop loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss: {sub_stop_loss=} ")
continue
# buy order sub has been cancelled ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ")
continue
# closed buy order sub ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed: {sub_order_closed=} ")
continue
##########################################################################################
### 【3】CHECK STOP LOSS FOR EACH BUY ORDER SUB (open or incomplte buy sub order)
##########################################################################################
### open or incomplete buy 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
#####################################################
### 1) check loss cut price : sell => buy
### calculate stop loss amount
stop_loss_amt = 0
latest_buy_price = 0
filter_mask = mas_df['side'] == 'BUY'
dfx = mas_df[filter_mask]
if dfx.shape[0] > 0:
# get latest buy price from the GMO master dataframe
buy_price = dfx.iloc[-1]['price']
latest_buy_price = buy_price
# get last sell qty & price from the sell order header
qty = sell_df.iloc[-1]['qty']
sell_price = sell_df.iloc[-1]['real_price'] # get a real sell 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
### sell_buy : buy position => calculate upper limit for 5%, 10%, 15%, 18%, 20%, 23%, 25%(GMO loss cut rate)
# calculate loss cut price from the sell sub order : loss_cut_price = last_sell_real_price + (last_sell_real_price * 0.20)
last_sell_real_price = sell_sub_df.iloc[-1]['real_price']
loss_cut_price_05 = last_sell_real_price + (last_sell_real_price * 0.05) # 0.25 => 0.05
loss_cut_price_10 = last_sell_real_price + (last_sell_real_price * 0.10) # 0.25 => 0.10
loss_cut_price_15 = last_sell_real_price + (last_sell_real_price * 0.15) # 0.25 => 0.15
loss_cut_price_18 = last_sell_real_price + (last_sell_real_price * 0.18) # 0.25 => 0.18
loss_cut_price_20 = last_sell_real_price + (last_sell_real_price * 0.20) # 0.25 => 0.20 BOT LOSS CUT
loss_cut_price_23 = last_sell_real_price + (last_sell_real_price * 0.23) # 0.25 => 0.23 DO NOT USE THIS
loss_cut_price_25 = last_sell_real_price + (last_sell_real_price * 0.25) # 0.25 => 0.25 GMO LOSS CUT
# gmo master latest buy price is greater than zero ?
if latest_buy_price > 0:
if latest_buy_price >= loss_cut_price_25: # >= 125
self.coin.sell_buy_lc25 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 25%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_25%({loss_cut_price_25:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_23: # >= 120
self.coin.sell_buy_lc23 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 23%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_23%({loss_cut_price_23:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_20: # >= 115
self.coin.sell_buy_lc20 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 20%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_20%({loss_cut_price_20:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_18: # >= 113
self.coin.sell_buy_lc18 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 18%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_18%({loss_cut_price_18:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_15: # >= 112
self.coin.sell_buy_lc15 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 15%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_15%({loss_cut_price_15:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_10: # >= 110
self.coin.sell_buy_lc10 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 10%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_10%({loss_cut_price_10:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_05: # >= 105
self.coin.sell_buy_lc5 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 5%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_5%({loss_cut_price_05:,.{_p_}f}) ")
### check upper limit based on sell price
# GMO latest buy price is greater than or equal to loss cut price (20%) ? => EXECUTE STOP LOSS
if latest_buy_price >= loss_cut_price_20:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}STOP LOSS DETECTED REAL(1-5) EXECUTE STOP LOSS {Bcolors.ENDC} loss cut 20%: latest_buy_price({latest_buy_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_buy_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 SELL ORDER SUB
######################################################
### cancel closed sell order
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed sell order sub({sub_sell_order_id=}) ")
# increment buy cancel count
self.gvar.sell_cancel_count += 1 # cancel closed sell order
self.coin.sell_cancel_count += 1 # cancel closed sell order
### set a cancelled flag for sell order sub : PK(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)
sell_sub_df.loc[find_mask, 'cancelled'] = True
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub(sell_order_id={sub_sell_order_id}, cancelled=True) ")
######################################################
### 【6】CANCEL OPEN/INCOMPLETE BUY ORDER SUB
######################################################
### for open or incomplete buy order sub
# cancel open/incomplete buy sub order
status, msg_code, msg_str = self.api.exe_cancel_order(sub_buy_order_id) # ★
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id}): exe_cancel_order() ▶ {status=} ")
sleep(0.5) # sleep 0.5 sec
###########################################################################################
### 【7】REBUY OUTSTANDING BUY ORDER SUB QTY : EXECUTE BUY CLOSE ORDER MARKET (FAK)
###########################################################################################
### rebuy open/incomplete buy order sub with market price (stop loss price)
outstanding_qty = sub_qty - sub_real_qty
new_buy_qty = outstanding_qty
qty = new_buy_qty
if sub_real_price > 0:
new_buy_price = sub_real_price + sub_real_fee
else:
new_buy_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
### rebuy buy sub order outstanding qty
# exe_buy_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_buy_close_order_market_wait(sub_position_id, qty, new_buy_price, mas_latest_close_price) # ★ FAK(Fill and Kill): new_buy_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}REBUY({loop_count}){Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_buy_close_order_market_wait() ▶ {status=} ")
# error ?
if status != 0:
if 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_buy_close_order_market_wait({sub_position_id=}) time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next buy sub orders
else: # misc error
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while tue loop and continue to read next buy sub orders
# end of if status == 8 and msg_code == 'ERR-888':
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
######################################################################################################
### 【8】ADD BUY ORDER CHILD ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★
######################################################################################################
# res_df = get_price() response for exe_buy_close_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 REBUY VERSION OF NEW BUY ORDER SUB ★
### append a new record into the buy order child (★ this is the rebuy version of buy order ★)
# define buy 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_buy_close_order_market()
for k in range(len(res_df)):
order_id = res_df.loc[k, 'orderId'] # orderId (same orderId): rebuy 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) # B9999-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 buy order child (rebuy version) ★
# 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:
_qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(buy_time, order_id, sell_time, sub_sell_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(999.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 buy cancel count
self.gvar.buy_cancel_count += 1 # cancel open/incomplete buy order
self.coin.buy_cancel_count += 1 # cancel open/incomplete buy order
break # exit while true loop
else: # outstanding buy_qty exists
pass # skip (do nothing)
# end of if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
### partial exe_buy_close_order_market_wait() = continue to buy remaining qty
remaining_qty = new_buy_qty - new_sub_sum_real_qty
qty = remaining_qty
# continue exe_buy_close_order_market_wait() ★
# end of while True:
#################################################################################
### 【9】UPDATE BUY 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 buy order sub (real_qty, real_price, real_fee, stop_loss) ★
find_mask = (buy_sub_df['buy_time'] == sub_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".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update
buy_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty # add real_qty (rebuy version)
buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price # update real_price (rebuy version)
buy_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee # add real_fee (rebuy version)
buy_sub_df.loc[find_mask, 'stop_loss'] = True
ix = dfx.index[0]
new_sub_sum_real_qty = buy_sub_df.loc[ix, 'real_qty']
new_sub_sum_real_fee = buy_sub_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub(buy_order_id={sub_buy_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=True) ★
# update_order_csv_sell(sell_time: dt.datetime.utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None:
self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])
# reset buy order sub list values
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# continue to next buy order sub ... ★
# end of for j in range(len(buy_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/sell_buy/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order sib csv file) : sell_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv'
# desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【12】UPDATE BUY ORDER HEADER (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 = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order => update
buy_df.loc[find_mask, 'real_qty'] += header_sum_real_qty # add real_qty (rebuy version)
buy_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (rebuy version)
buy_df.loc[find_mask, 'real_fee'] += header_sum_real_fee # add real_fee (rebuy version)
ix = dfx.index[0]
new_header_sum_real_qty = buy_df.loc[ix, 'real_qty']
new_header_sum_real_fee = buy_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_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 buy order
# end of for i in range(len(buy_df)):
#################################################################################
### 【13】SAVE BUY/SELL ORDER HEADER 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/sell_buy/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/sell_buy/sell_order(BTC_JPY).csv
sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
return
##############################
def stop_loss_1(self) -> None:
pass
図8-1はデモプログラムの実行結果です。
画面にはSellBuyクラスのstop_loss(0)メソッドのログが表示されています。
stop_loss(0)では未執行分の買戻し注文のキャンセルが実行されています。
さらに未執行の買いボジションに対して成行きの買い取り注文を出して強制的に決済しています。
詳細は図8-2で説明します。
図8-2は買い取り注文で作成されたCSVファイルをExcelで開いた画像です。
買い取り注文のCSVファイルも売り注文と同様3階層になっています。
ここでは3種類のCSVファイルを開いています。
「buy_order(XRP_JPY).csv」ファイルには「buy_time, qty, price, real_qry, real_price,...」などが格納されています。
「real_qty, real_price」には約定した数量と価格(レート)が格納されます。
「buy_order_sub(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, real_qty, real_price, stop_loss,...」などが格納されています。
「real_qty」には約定した数量(40)が格納されています。
「stop_loss」には「TRUE」が格納されているのでストップ・ロスが適用されています。
「buy_order_child(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, real_qty, real_price,...」などが格納されています。
実はこの買い取り注文は数量「40」で出されていますが、数量が「20」だけ執行された状態でストップ・ロスが適用されています。
この場合、未執行の数量「20」に対してキャンセル処理を行います。
さらに、未執行の買いポジションに対して成行きの買い注文を出して残数の「20」を強制的に決済します。
つまり、ストップ・ロスを適用します。
「buy_order_child(XRP_JPY).csv」のExcelシートの行2は取引所で部分的に執行された買い注文のデータで、
行3はストップ・ロスで強制的に決済したときの買い注文のデータです。
したがってシートの列(B)の「buy_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の「buy_order_sub(XRP_JPY).csv」と「buy_order_child(XRP_JPY).csv」ファイルを参照します。
-
SellBuyクラスにcancel_pending_order()メソッドを追加する
ここではSellBuyクラスにcancel_pending_order()メソッドを追加します。
このメソッドは、保留状態になっているポジションを強制的に決済してレバレッジ手数料が加算されるのを回避するときに使用します。
GMOコインの場合、レバレッジ手数料は午前6時を経過したときに加算されます。
したがって、レバレッジ手数料を回避するには午前6時前にポジションを決済させる必要があります。
sell_buy.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 BUY ORDER HEADER
#################################################
# define buy order header list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
### get all buy order
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # ★ unique time
sell_time = buy_df.loc[i, 'sell_time'] # ★ unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
buy_order_id = dfx.loc[ix, 'buy_order_id']
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id=}) has been closed: {buy_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue
# end of if buy_order_closed:
#################################################################
### 【2】READ ALL BUY ORDER SUB (OPEN OR INCOMPLETE STATUS)
#################################################################
### for buy order sub (open or incomplete status)
# define buy order sub list
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : buy sub(many) => sell sub(one)
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_position_id = buy_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = buy_subx_df.loc[j, 'qty']
sub_price = buy_subx_df.loc[j, 'price']
sub_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_real_price = buy_subx_df.loc[j, 'real_price']
sub_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_cancelled = buy_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 buy 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
# buy sub order has been stopped loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss ▶ {sub_stop_loss=} ")
continue
# cancelled buy sub order ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ")
continue
# closed buy sub order ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been closed: {sub_order_closed=} ")
continue
### open or incomplete buy 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 SELL ORDER SUB : ★ buy order sub (many) => sell order sub (one) ★
##############################################################################################
### for closed sell order sub
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed sell order sub({sub_sell_order_id=}) ")
# increment sell cancel count
self.gvar.sell_cancel_count += 1 # cancel closed sell order
self.coin.sell_cancel_count += 1 # cancel closed sell order
### set a cancelled flag for sell order sub : ★ PK(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)
sell_sub_df.loc[find_mask, 'cancelled'] = True
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, cancelled=True) ")
######################################################
### 【5】CANCEL OPEN/INCOMPLETE BUY ORDER SUB
######################################################
### cancel open or incomplete buy order sub
### cancel buy order sub
status, msg_code, msg_str = self.api.exe_cancel_order(sub_buy_order_id) # ★
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CANCEL{Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_cancel_order() ▶ {status=} ")
sleep(0.5) # sleep 0.5 sec
##################################################################################################
### 【6】REBUY OUTSTANDING BUY ORDER SUB QTY : EXECUTE BUY CLOSE ORDER MARKET WAIT (FAK)
##################################################################################################
### rebuy open or incomplete buy order sub with market price
outstanding_qty = sub_qty - sub_real_qty # calculate outstanding qty
new_buy_qty = outstanding_qty
qty = new_buy_qty
if sub_real_price > 0:
new_buy_price = sub_real_price + sub_real_fee
else:
new_buy_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_buy_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_buy_close_order_market_wait(sub_position_id, qty, new_buy_price, mas_latest_close_price) # ★ FAK(Fill and Kill): new_buy_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}REBUY({loop_count}){Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_buy_close_order_market_wait() ▶ {status=} ")
# error ?
if status != 0:
if 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_buy_close_order_market_wait({sub_position_id=}) time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next buy sub orders
else: # misc error
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) misc error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next buy sub orders
# end of if status == 8 and msg_code == 'ERR-888':
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
##################################################################################################
### 【7】ADD BUY ORDER CHILD ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★
##################################################################################################
# res_df = response for exe_buy_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 REBUY VERSION OF NEW BUY ORDER SUB ★
### append a new record into the buy order child (★ this is the rebuy version of buy order child ★)
# define buy order child list
order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response for exe_buy_close_order_market_wait()
for k in range(len(res_df)):
order_id = res_df.loc[k, 'orderId'] # orderId (same orderId) : rebuy 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) # B9999-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 buy order child (rebuy version) ★
# 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:
_qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(buy_time, order_id, sell_time, sub_sell_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_)
sum_real_fee = round(sum(real_fee_list), 0)
if sum(real_price_list) > 0:
mean_real_price = round(statistics.mean(real_price_list), _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 buy_qty ?
if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
# increment buy cancel count
self.gvar.buy_cancel_count += 1 # cancel open/incomplete buy order
self.coin.buy_cancel_count += 1 # cancel open/incomplete buy order
break # exit while true loop
else: # outstanding buy_qty exists
pass # skip (do nothing)
# end of if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
### partial exe_buy_close_order_market_wait() = continue to buy remaining qty
remaining_qty = new_buy_qty - new_sub_sum_real_qty
qty = remaining_qty
# continue exe_buy_close_order_market_wait() ★
# end of while True:
#################################################################################
### 【8】UPDATE BUY 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 buy order sub (real_qty, real_price, real_fee, cancelled)
find_mask = (buy_sub_df['buy_time'] == sub_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".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL}buy order sub not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update
buy_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty # add real_qty (rebuy version)
buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price # update real_price (rebuy version)
buy_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee # add real_fee (rebuy version)
buy_sub_df.loc[find_mask, 'cancelled'] = True
ix = dfx.index[0]
new_sub_sum_real_qty = buy_sub_df.loc[ix, 'real_qty']
new_sub_sum_real_fee = buy_sub_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_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 (cancelled=True, order_closed=True) ★
# update_order_csv_sell(sell_time: dt.datetime.utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None:
self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['cancelled'], col_value_list=[True]) # PK(sell_time + sell_order_id)
# reset buy order sub list values
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# continue to next buy order sub
# end of for j in range(len(buy_subx_df)):
#################################################################################
### 【10】SAVE BUY/SELL ORDERS 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/sell_buy/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/sell_buy/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【11】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
#################################################################################
if len(header_real_qty_list) > 0:
### update buy 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 = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL}buy order({buy_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order => update
buy_df.loc[find_mask, 'real_qty'] += header_sum_real_qty # add real_qty (rebuy version)
buy_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (rebuy version)
buy_df.loc[find_mask, 'real_fee'] += header_sum_real_fee # add real_fee (rebuy version)
ix = dfx.index[0]
new_header_sum_real_qty = buy_df.loc[ix, 'real_qty']
new_header_sum_real_fee = buy_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_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 buy header list values
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
# continue to next buy order... ★
# end of for i in range(len(buy_df)):
#################################################################################
### 【12】SAVE BUY/SELL ORDER HEADER TO CSV FILES
#################################################################################
### CLOSE (save the buy order csv file)
csv_file = self.folder_trading_type + f'buy_order({symbol}).csv'
# desktop-pc/bot/sell_buy/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/sell_buy/sell_order(BTC_JPY).csv
sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
return
図9-1はデモプログラムの実行結果です。
画面にはSellBuyクラスのcancel_pending_order()メソッドが実行されたログが表示されています。
このメソッドは後述するレバレッジ手数料を発生させたくない場合などに使用します。
GMOコインはポジションを保持した状態で午前6時を経過するとレバレッジ手数料を加算します。
BOTはこのレバレッジ手数料を回避するために強制的にポジションを決済する機能をサポートしています。
ポジションを強制的に決済させるには、
Gvarクラスのcancel_pending_ordersプロパティに「True」を設定して自動トレードします。
図9-2は買いのポジションを強制的に決済したときに作成されたCSVファイルと損益ファイルをExcelで表示した画面です。
「buy_order_sub(XRP_JPY).csv」ファイルの「real_qty」には買いの数量「40XRP_JPY」が格納されています。
「buy_order_child(XRP_JPY).csv」ファイルの「real_qty」には執行された数量が「20XRP_JPY」に分割されて格納されています。
Excelシートの行2(上段)はGMOコインが部分的に執行した数量で、
行3(下段)はBOTが強制的に残数「20XRP_JPY」を決済したときの数量です。
ポジションが強制的に決済されたかどうか調べるには、
まず「buy_order_sub(XRP_JPY).csv」の「cancelled」の値をチェックします。
「TRUE」が格納されていれば強制的に決済されています。
この場合、「buy_order_child(XRP_JPY).csv」の「buy_order_id」に異なる買い注文IDが格納されます。
-
SellBuyクラスにclose_pending_order()メソッドを追加する
ここではSellBuyクラスにclose_pending_order()メソッドを追加します。
このメソッドはレバレッジ手数料を計算させたいときに使用します。
BOTを使用するときは、午前5時~午前6時の時間帯に自動的にこのメソッドが呼ばれます。
close_pending_order()はポジションを保留している注文のレバレッジ手数料を計算して損益ファイルを更新します。
sell_buy.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 info csv file) : order_by(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 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 BUY ORDER HEADERS
#################################################
### get all buy order headers
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # ★ unique time
sell_time = buy_df.loc[i, 'sell_time'] # ★ unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
buy_order_id = dfx.loc[ix, 'buy_order_id']
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id=}) has been closed: {buy_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue
# end of if buy_order_closed:
#################################################################
### 【2】READ ALL BUY ORDER SUB (OPEN OR INCOMPLETE STATUS)
#################################################################
### for buy order sub (open or incomplete status)
# 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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : buy sub(many) => sell sub(one)
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_position_id = buy_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = buy_subx_df.loc[j, 'qty']
sub_price = buy_subx_df.loc[j, 'price']
sub_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_real_price = buy_subx_df.loc[j, 'real_price']
sub_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_get_price_count = buy_subx_df.loc[j, 'get_price_count']
sub_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_cancelled = buy_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 buy order sub ? => 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
# buy 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} buy order sub({sub_buy_order_id=}) has been stopped loss ▶ {sub_stop_loss=} ")
continue
# cancelled buy order sub ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ")
continue
# closed buy order sub ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been closed: {sub_order_closed=} ")
continue
### open or incomplete buy order sub
#################################################################
### 【3】CALCULATE LEVERAGE FEE AND UPDATE ORDER CSV FILE
#################################################################
### calculate leverage fee
# get a sell order child
find_mask = (sell_ch_df['sell_time'] == sub_sell_time) & (sell_ch_df['sell_order_id'] == sub_sell_order_id) & (sell_ch_df['position_id'] == sub_position_id)
dfx = sell_ch_df[find_mask]
# not found sell order child ?
if dfx.shape[0] == 0:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found sell order child{Bcolors.ENDC} : {sub_sell_time=:%Y-%m-%d %H:%M:%S}, {sub_sell_order_id=}, {sub_position_id=} ")
else: # found sell order child
ix = dfx.index[0]
ch_sell_real_qty = dfx.loc[ix, 'real_qty']
ch_sell_real_price = dfx.loc[ix, 'real_price']
ch_leverage_fee = math.ceil((ch_sell_real_price * ch_sell_real_qty) * 0.0004) # leverage fee (0.04%)
### update order csv (accumulated_leverage_fee, close_count, close_update_date)
# not empty order csv ?
if ord_df.shape[0] > 0:
find_mask = ord_df['sell_order_id'] == sub_sell_order_id # S9999-999999-999999
dfx = ord_df[find_mask]
# not found order csv ?
if dfx.shape[0] == 0:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found order csv{Bcolors.ENDC} : {sub_sell_order_id=} ")
else: # found order csv
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
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
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, 'close_update_date'] = 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_sell(): sell_order_id={sub_sell_order_id}, leverage_fee={new_accumulated_leverage_fee:,.0f}, close_count={new_close_count}, close_update_date={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 buy order sub...
# end of for j in range(len(buy_subx_df)):
# continue to next buy order... ★
# end of for i in range(len(buy_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/sell_buy/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で開いたときの画面です。
「sell_order_child(XRP_JPY).csv」ファイルの「real_qty」には空売りの注文で執行された数量「40XRP_JPY」が格納されています。
「buy_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)」を減算すると純利益が計算できます。
-
SellBuyクラスの全てのソースコードを掲載
ここではSellBuyクラスの全てのソースコードを掲載しています。
sell_buy.py:
# sell_buy.py
"""
SellBuy 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')
########################### sell buy class
class SellBuy:
"""
__init__(), __str__(),
reset_restart_process(),
sell_buy(), sell_buy_algorithm_0(), sell_buy_algorithm_1, sell_buy_algorithm_2(), sell_buy_algorithm_3(),...
create_sell_order(), create_buy_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/sell_buy/
self.debug.print_log(f"{Bcolors.OKGREEN}{self.gvar.exchange.upper()} SellBuy({self.gvar.althorithm_id},{self.gvar.sellbuy_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"SellBuy({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 sell/buy 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 sell/buy order files...")
### 1) add a fake data into the sell order, sell order sub 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)
### 2) 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)
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 sell_buy(self, algorithm_id: int) -> None:
"""
"""
if algorithm_id <= 3:
eval(f'self.sell_buy_algorithm_{str(algorithm_id)}()') # call 'self.sell_buy_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 sell_buy ")
self.debug.sound_alert(3)
# if algorithm_id == 0:
# self.sell_buy_algorithm_0()
# elif algorithm_id == 1:
# self.sell_buy_algorithm_1()
# elif algorithm_id == 2:
# self.sell_buy_algorithm_2()
# elif algorithm_id == 3:
# self.sell_buy_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 sell_buy ")
# self.debug.sound_alert(3)
return
#######################################
def sell_buy_algorithm_0(self) -> None:
"""
"""
self.gvar.callers_method_name = 'sell_buy(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.shape[0]=} is less than {self.coin.trend_search_count}. skip sell_buy() ")
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.shape[0]=} is less than {self.coin.trend_search_count}. skip sell_buy() ")
self.debug.sound_alert(3)
return
self.debug.trace_write(f".... {self.gvar.callers_method_name} sell_buy_condition({self.gvar.sellbuy_condition}), {mas_buy_df.shape[0]=}, {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 files
### get the last sell order sub info
last_buy_time = sell_sub_df.iloc[-1]['buy_time']
last_sell_time = sell_sub_df.iloc[-1]['sell_time']
last_sell_real_qty = sell_sub_df.iloc[-1]['real_qty']
last_sell_real_price = sell_sub_df.iloc[-1]['real_price']
### get the last buy order (pair of the sell order)
# find the pair of the buy order
find_mask = buy_df['buy_time'] == last_buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
# self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ buy order not found: {last_buy_time:%Y-%m-%d %H:%M:%S=} ")
last_buy_real_qty = 0
else: ### found pair of the buy order
# get buy order real_qty
ix = dfx.index[0]
last_buy_real_qty = dfx.loc[ix, 'real_qty']
# closed sell/buy order ?
if round(last_buy_real_qty, _q_) == round(last_sell_real_qty, _q_):
position = 'sell' # buy order not required => position(SELL)
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}NEW SELL ORDER REQUEST{Bcolors.ENDC} ")
else: # not found buy order or incomplete buy order
return # skip return
# end of if last_buy_real_qty == last_sell_real_qty:
# sell position ?
if position.startswith('sell'):
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SELL{Bcolors.ENDC} position (short position) ")
### SELL position
### check sell condition and create a sell order
# get the latest sell_time & latest sell_price from GMO master dataframe
mas_time = mas_sell_df.iloc[-1]['time'] # gmo time ★
mas_price = mas_sell_df.iloc[-1]['price'] # gmo price ★
while True:
###########################################################
### 1: check duplicate sell order
###########################################################
# mas_time(gmo) is less than or equal to last_sell_time ? => SKIP
if mas_time <= last_sell_time:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(1){Bcolors.ENDC} mas_time(gmo) is less than or equal to last_sell_time: {mas_time=:%Y-%m-%d %H:%M:%S} <= {last_sell_time=:%Y-%m-%d %H:%M:%S} ")
break # exit while true loop
# found sell order ? => SKIP
# find_order(df: pd.DataFrame, col: str, val=any) -> bool:
if self.trade.find_order(sell_df, col='sell_time', val=mas_time):
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(2){Bcolors.ENDC} duplicate sell order: {mas_time=:%Y-%m-%d %H:%M:%S} ")
break # exit while true loop
### no duplicate sell order
###########################################################
### 2: execute sell order (MARKET) & buy order (LIMIT)
###########################################################
qty = self.coin.qty
### execute sell order with qty (MARKET)
# create_sell_order(sell_time: utcnow, qty: float, price: float) -> int:
status = self.create_sell_order(mas_time, qty, mas_price) # ★ price is used for fake mode: execute sell order with market price
if status == 0:
### reload sell order sub file
sell_sub_df = self.csv.get_sell_order_sub() # ★
### get the last sell order sub info
last_sell_time = sell_sub_df.iloc[-1]['sell_time']
### execute buy order (LIMIT)
# create_buy_order( buy_time: utcnow, ms_price: float, sell_time: utcnow) -> int:
status = self.create_buy_order(mas_time, mas_price, last_sell_time) # ★
break # exit while true loop
# end of while True:
return
#######################################
def sell_buy_algorithm_1(self) -> None:
"""
"""
return
########################################
def sell_buy_algorithm_2(self) -> None:
"""
"""
return
#######################################
def sell_buy_algorithm_3(self) -> None:
"""
"""
return
############################################################################################
def create_sell_order(self, sell_time: dt.datetime.utcnow, qty: float, price: float) -> int:
"""
"""
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}, {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 SELL ORDER
################################################
### check duplicate sell order
# OPEN (load the sell order file) : sell_order(BTC_JPY).csv
sell_df = self.csv.get_sell_order()
# find a sell order
find_mask = sell_df['sell_time'] == 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
# reset buy_position_list
self.coin.buy_position_list = []
#############################################################
### 【2】EXECUTE SELL ORDER MARKET (FAK: Fill And Kill)
#############################################################
### execute sell order with market price : FAK (Fill And Kill)
sell_qty = qty # save original sell qty ★
# define sell 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_sell_order_market_wait(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_order_market_wait(sell_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_sell_order_market_wait() ▶ {status=}, {res_df.shape=} ")
####################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_sell_order()
if not self.coin.real_mode:
if self.coin.debug_mode_debug1:
# ★ create two sell order subs for each sell 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 subs
# loop_count == 2: return 1 row => add 1 buy sub
# end of if not self.coin.real_mode:
###################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_sell_order()
# exe_sell_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_sell_order{Bcolors.ENDC}({sell_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_sell_order{Bcolors.ENDC}({sell_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_sell_order{Bcolors.ENDC}({sell_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 SELL 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 sell order child
# define sell order child list
sell_order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response df for exe_sell_order_market_wait(FAK)
for k in range(len(res_df)):
sell_order_id = res_df.loc[k, 'orderId'] # sell 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
sell_order_id_list.append(sell_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_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:
_buy_time_ = sell_time; _buy_order_id_ = 'Dummy'; _qty_ = real_qty; _price_ = real_price
self.csv.write_sell_order_child(sell_time, sell_order_id, _buy_time_, _buy_order_id_, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
first_sell_order_id = sell_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_JPY(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_) # 999.999
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 SELL ORDER SUB (buy_time, sell_time,...) ★ buy_time <= sell_time
###################################################################################################################
### 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:
_buy_time_ = sell_time; _buy_order_id_ = 'Dummy'; _qty_ = sub_sum_real_qty # positionId = 'positionId1;positionId2;positionId3'
self.csv.write_sell_order_sub(sell_time, _buy_time_, first_sell_order_id, _buy_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 sell_qty ?
if round(qty, _q_) == round(header_sum_real_qty, _q_):
# increment sell count
self.gvar.sell_count += 1
self.coin.sell_count += 1
break # exit while true loop
else: # outstanding sell_qty exists
pass # skip (do nothing)
# end of if round(buy_qty, digits) == round(header_sum_real_qty, digits):
### partial exe_sell_order_market_wait() was executed => continue to sell remaining qty
remaining_qty = sell_qty - header_sum_real_qty # calculate new sell qty
sell_qty = remaining_qty # update new sell qty
# continue exe_sell_order_market_wait() ★
# end of while True:
#################################################################################
### 【5】ADD A SELL ORDER HEADER (buy_time, sell_time) ★ buy_time <= sell_time
#################################################################################
### update sell 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(9999.123)
else:
header_mean_real_price = 0
### write a sell order
# 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:
_buy_time_ = sell_time
self.csv.write_sell_order(sell_time, _buy_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_sell_order() ▶ {status=} normal return... ")
return status
################################################################################################################
def create_buy_order(self, buy_time: dt.datetime.utcnow, ms_price: float, sell_time: dt.datetime.utcnow) -> int:
"""
"""
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}, {ms_price=:,.{_p_}f}, {sell_time=:%Y-%m-%d %H:%M:%S}) ")
status = 0
################################################
### 【1】CHECK DUPLICATE BUY ORDER
################################################
### check duplicate buy order
# get the buy order : sbuy_order(BTC_JPY).csv
buy_df = self.csv.get_buy_order()
# find a buy order
find_mask = buy_df['buy_time'] == buy_time # PK(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
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub()
### OPEN (load the sell order child csv file ) : sell_order_child(BTC_JPY).csv
sell_ch_df = self.csv.get_sell_order_child()
######################################################################
### 【2】READ ALL SELL ORDER SUBs (filter by sell_time)
######################################################################
# define sell order sub list
sub_qty_list = []
sub_price_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 filtered by sell_time
for i in range(len(sell_subx_df)): # ★ sell_subx_df ★
### get sell order sub info
sub_sell_time = sell_subx_df.loc[i, 'sell_time'] # non unique
sub_sell_order_id = sell_subx_df.loc[i, 'sell_order_id'] # unique id
sub_qty = sell_subx_df.loc[i, 'qty']
sub_price = sell_subx_df.loc[i, 'price']
sub_real_qty = sell_subx_df.loc[i, 'real_qty']
sub_real_price = sell_subx_df.loc[i, 'real_price']
sub_real_fee = sell_subx_df.loc[i, 'real_fee']
#################################################################################
### 【3】READ ALL SELL ORDER CHILD (filter by sell_time + sell_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 sell order child by sell_time + selll_order_id
# sell order child is empty ?
if sell_ch_df.shape[0] == 0:
sell_chx_df = pd.DataFrame()
else: # filter sell order child
filter_mask = (sell_ch_df['sell_time'] == sell_time) & (sell_ch_df['sell_order_id'] == sub_sell_order_id)
sell_chx_df = sell_ch_df[filter_mask]
if sell_chx_df.shape[0] > 0:
sell_chx_df.reset_index(inplace=True)
# end of if sell_ch_df.shape[0] == 0:
# get all sell order child filtered by sell_time + sell_order_id
for j in range(len(sell_chx_df)): # ★ sell_chx_df ★
### get buy order child info
ch_sell_time = sell_chx_df.loc[j, 'sell_time'] # non unique time
ch_sell_order_id = sell_chx_df.loc[j, 'sell_order_id'] # non unique id (same sell_order_id)
ch_position_id = sell_chx_df.loc[j, 'position_id'] # unique id
ch_qty = sell_chx_df.loc[j, 'qty']
ch_price = sell_chx_df.loc[j, 'price']
ch_real_qty = sell_chx_df.loc[j, 'real_qty']
ch_real_price = sell_chx_df.loc[j, 'real_price']
ch_real_fee = sell_chx_df.loc[j, 'real_fee']
#############################################################################################
### 【4】EXECUTE BUY CLOSE ORDER LIMIT (FAS:Fill And Store) FOR EACH SELL ORDER CHILD
#############################################################################################
buy_qty = ch_real_qty
buy_price = self.trade.get_buy_price(ch_real_price, trace=True)
# market price is less than than buy price
if ms_price < buy_price:
buy_price = ms_price
### execute a buy close order with limit(FAS)
# exe_buy_close_order_limit(position_id: str, qty: float, price: float) ->tuple: # return (status, res_order_id, msg_code, msg_str)
status, res_buy_order_id, msg_code, msg_str = self.api.exe_buy_close_order_limit(ch_position_id, buy_qty, buy_price) # ★
# res_buy_order_id => 'B9999-999999-999999'
# exe_buy_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_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}the available balance exceeded ERR-208: continue...{Bcolors.ENDC} ")
self.debug.sound_alert(2)
continue # continue to next sell order child
else: # misc error
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
self.debug.sound_alert(2)
continue # continue to next sell order child
# end of if status != 0:
### no error for exe_buy_close_order_limit()
################################################################################################################
### 【5】ADD A BUY ORDER SUB
################################################################################################################
### 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:
self.csv.write_buy_order_sub(buy_time, sell_time, res_buy_order_id, ch_sell_order_id, ch_position_id, buy_qty, buy_price)
ch_buy_order_id_list.append(res_buy_order_id)
ch_sell_order_id_list.append(ch_sell_order_id)
ch_position_id_list.append(ch_position_id)
ch_qty_list.append(buy_qty)
ch_price_list.append(buy_price)
# continue to next sell order child
# end of for j in range(len(sell_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 SELL ORDER SUB (buy_order_id, buy_time)
#####################################################################################
buy_order_ids_by_semicolon = '' # 'BuyOrderID1;BuyOrderID2;BuyOrderID3'
for k, buy_order_id_x in enumerate(ch_buy_order_id_list):
if k == 0:
buy_order_ids_by_semicolon += buy_order_id_x
else:
buy_order_ids_by_semicolon += ';' + buy_order_id_x
### update sell order sub (buy_order_id, buy_time)
find_mask = (sell_sub_df['sell_time'] == 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".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}(): {Bcolors.FAIL} sell order sub not found ▶ {sub_sell_order_id=} {Bcolors.ENDC} ")
else: # found sell order sub => update buy_order_id, buy_time
sell_sub_df.loc[find_mask, 'buy_order_id'] = buy_order_ids_by_semicolon # 'BuyOrderID1;BuyOrderID2;BuyOrderID3'
sell_sub_df.loc[find_mask, 'buy_time'] = buy_time
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sell_time=:%Y-%m-%d %H:%M:%S}, {sub_sell_order_id=}, {buy_order_ids_by_semicolon=}, {buy_time=:%Y-%m-%d %H:%M:%S}) ")
# end of if dfx.shape[0] == 0:
# continue to next sell order sub
# end of for i in range(len(sell_subx_df)):
#################################################################################
### 【7】SAVE SELL ORDER SUB TO CSV FILE
#################################################################################
### 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/sell_buy/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
################################################################################
### 【8】ADD A BUY ORDER HEADER
################################################################################
### update buy 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_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, sub_sum_qty, sub_mean_price)
################################################################################
### 【9】UPDATE A SELL ORDER HEADER (buy_time)
################################################################################
# update_sell_order(sell_time: utcnow, col_name_list: list, col_value_list: list) -> object: # PK(sell_time)
self.csv.update_sell_order(sell_time, col_name_list=['buy_time'], col_value_list=[buy_time])
self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_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 : low to high price 100, 110, 120, 130, 140, 150
buy_bk_df = pd.DataFrame(buy_bk) # descending order of 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 sell_close_order
# 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
# incomplet buy order (pending buy 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
# buy_bk_df = pd.DataFrame(buy_bk) # descending order of price
# not empty order books ?
if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:
### get sell_close_order
# 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 buy price from the buy order sub
last_buy_price = buy_sub_df.iloc[-1]['price'] # 120
# get buy position number (sell => buy)
buy_bk_df.sort_values('price', ascending=True, inplace=True) # sort in ascending order of price ★
filter_mask = buy_bk_df['price'] > last_buy_price
dfx = buy_bk_df[filter_mask]
buy_position = dfx.shape[0]
# update buy position count
if buy_position >= 91:
self.coin.position_count_91_999 += 1
elif buy_position >= 71:
self.coin.position_count_71_90 += 1
elif buy_position >= 51:
self.coin.position_count_51_70 += 1
elif buy_position >= 31:
self.coin.position_count_31_50 += 1
elif buy_position >= 11:
self.coin.position_count_11_30 += 1
else:
self.coin.position_count_0_10 += 1
self.coin.buy_position_list.append(buy_position) # append buy_position : coin.buy_position=[10, 8, 5, 3, 1, 0]
self.debug.trace_write(f".... {self.gvar.callers_method_name}{Bcolors.WARNING} {buy_position=}{Bcolors.ENDC} => close condition [{last_buy_price=:,.{_p_}f} >= {bk_sell_price=:,.{_p_}f}] ") # 120 >= 100
reverse_buy_position_list = self.coin.buy_position_list[::-1] # [0, 1, 3, 5, 8, 10]
if len(reverse_buy_position_list) < 21:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_buy_position_list=} ")
else:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_buy_position_list[0:21]=} ")
else: # empty order books
self.coin.buy_position_list = [] # reset buy position list
# end of if not order_completed:
#################################################
### 【1】READ ALL BUY ORDER HEADER
#################################################
### get all buy order header
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # unique time
sell_time = buy_df.loc[i, 'sell_time'] # unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
dfx.reset_index(inplace=True)
buy_order_id = str(dfx.loc[0, 'buy_order_id'])
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id}) has been closed: {buy_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
continue
# end of if buy_order_closed:
### open or incomplete buy order header
##########################################################################################
### 【2】READ ALL BUY ORDER SUB & EXECUTE GET_PRICE() FOR EACH BUY ORDER SUB
##########################################################################################
### for buy order sub : use pandas dataframe groupby()
buy_order_sub_seq = 0
# 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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★
sub_position_id = buy_subx_df.loc[j, 'position_id'] # (unique id)
sub_buy_qty = buy_subx_df.loc[j, 'qty']
sub_buy_price = buy_subx_df.loc[j, 'price']
sub_buy_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_buy_real_price = buy_subx_df.loc[j, 'real_price']
sub_buy_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_buy_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_buy_cancelled = buy_subx_df.loc[j, 'cancelled']
sub_buy_order_open = False
sub_buy_order_closed = False
sub_buy_order_incomplete = False
if sub_buy_real_qty == 0:
sub_buy_order_open = True
elif sub_buy_qty == sub_buy_real_qty:
sub_buy_order_closed = True
else:
sub_buy_order_incomplete = True
# fake buy order sub ? => 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
# cancelled buy order sub ? => SKIP
if sub_buy_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been cancelled ▶ {sub_buy_cancelled=} ")
continue
# stop loss buy order sub ? => SKIP
if sub_buy_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been stopped loss ▶ {sub_buy_stop_loss=} ")
continue
# closed buy order sub ? => SKIP
if sub_buy_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed ▶ {sub_buy_order_closed=} ")
continue
### open or incomplete buy order sub
############################################################
### 【3】UPDATE BUY ORDER SUB (get_price_count+1)
############################################################
### update buy order sub (get_price_count += 1) ★
# DO NOT USE: self.csv.update_buy_order_sub_count(buy_time, buy_order_id, symbol)
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub_count({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub
buy_sub_df.loc[find_mask, 'get_price_count'] += 1 # increment get_price_count by 1
ix = dfx.index[0]
sub_buy_get_price_count = buy_sub_df.loc[ix, 'get_price_count']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub_count({sub_buy_order_id=}, {sub_buy_get_price_count=}) ")
# end of if dfx.shape[0] == 0:
buy_order_sub_seq = j + 1 # 1, 2, 3,....
### get order_status
qty = sub_buy_qty
price = sub_buy_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(sub_buy_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({sub_buy_order_id=}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")
continue # continue to next buy order sub
## get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
# pending exe_buy_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({sub_buy_order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_buy_close_order_limit() request ")
continue # continue to next buy order sub
############################################# DEBUG DEBUG DEBUG
if self.coin.debug_mode_gmo_stop_loss:
order_status = 'EXPIRED'
############################################# DEBUG DEBUG DEBUG
### executed exe_buy_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'
if order_status in 'EXPIRED,CANCELED':
### update buy order, buy 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({sub_buy_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 buy order sub
if order_status == 'EXPIRED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")
self.debug.sound_alert(3)
###########################################
### 1: close buy order sub
###########################################
# update a buy order sub (update real_qty, real_price, stop_loss)
# DO NOT USE: self.csv.update_buy_order_sub(buy_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee, stop_loss)
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
buy_stop_loss = True
buy_sub_df.loc[find_mask, 'real_qty'] = sub_buy_qty
buy_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
buy_sub_df.loc[find_mask, 'real_fee'] = gmo_loss_cut_fee # gmo loss cut fee
buy_sub_df.loc[find_mask, 'stop_loss'] = buy_stop_loss # buy order stop loss
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, {sub_buy_qty=:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {buy_stop_loss=}) ")
# end of if dfx.shape[0] == 0:
#######################################
### 2: close buy order
#######################################
# update buy order (real_qty, real_price, real_fee)
find_mask = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
buy_df.loc[find_mask, 'real_qty'] = buy_qty
buy_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
buy_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_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={buy_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 sell_order_id
find_mask = ord_df['sell_order_id'] == sub_sell_order_id
dfx = ord_df[find_mask]
if dfx.shape[0] > 0:
# update_order_csv_sell(sell_time: utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(sell_time + sell_order_id)
self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])
continue # continue to next buy order sub
# end of if order_status == 'EXPIRED':
# end of if order_status in 'EXPIRED,CANCELED':
### get lowest sell price from sell orderbooks
latest_book_price = bk_sell_price # bk_sell_price = sell_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) => usef for fake mode
_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 buy real qty, price & fee from the GMO : get_price() for exe_buy_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_buy_order_id, sub_buy_qty, sub_buy_price, latest_book_price, _debug1_, _debug2_) # ★ qty, price, close_price are used for fake mode
# BUG status, res_df, msg_code, msg_str = self.api.get_price_try(sub_sell_order_id, sub_buy_qty, sub_buy_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, buy_order_sub_seq) # 1, 2, 3, ...
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({buy_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 buy order sub depending on get_price_count ★
# get buy 'get_price_count'
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# found buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
get_price_count = buy_sub_df.loc[ix, 'get_price_count'] # buy_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_buy_close_order_limit() request ?
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_buy_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_buy_close_order_limit() request: continue to next buy order sub... ")
status = 0 # reset status code
continue # continue to next buy 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_buy_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
continue # continue to next buy order sub
# end of if status == 9:
else: # normal return => executed exe_buy_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 BUY ORDER SUB (real_qty, real_price, real_fee)
####################################################################
# res_df = get_price() response for exe_buy_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 buy 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_buy_order_sub(buy_time, sub_order_id, col_name_list, col_value_list)
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee)
buy_sub_df.loc[find_mask, 'real_qty'] = sub_sum_real_qty
buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price
buy_sub_df.loc[find_mask, 'real_fee'] = sub_sum_real_fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_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 BUY ORDER CHILD (buy_time, buy_order_id, real_qty, real_price, real_fee) + (sell_time, sell_order_id)
##############################################################################################################################
### check exit condition : sub_buy_qty=sub_buy_qty, sub_sum_real_qty=sum(res_df['size'])
########################################### TEMP TEMP TEMP
# temp_buy_qty = round(sub_buy_qty, _q_)
# temp_real_qty = round(sub_sum_real_qty, _q_)
# self.debug.trace_warn(f".... DEBUG: {temp_buy_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
########################################## TEMP TEMP TEMP
# completed exe_buy_close_order_limit() request ?
if round(sub_buy_qty, _q_) == round(sub_sum_real_qty, _q_):
# changed open => closed
if self.coin.real_mode:
self.debug.sound_alert(1)
# increment buy count
self.gvar.buy_count += 1
self.coin.buy_count += 1
# response df for get_price(): exe_buy_close_order_limit()
for k in range(len(res_df)):
buy_order_id = res_df.loc[k, 'orderId'] # buy 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 buy order child ★
# 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:
_qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(sub_buy_time, buy_order_id, sub_sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
else: # outstanding buy_qty exists => incomplete status
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} incomplete buy order sub({sub_buy_order_id=}) ▶ buy_qty={sub_buy_qty:,.{_q_}f}, buy_real_qty={sub_sum_real_qty:,.{_q_}f} ")
# pass # skip (do nothing)
# end of if round(sub_buy_qty, digits) == round(sub_sum_real_qty, digits):
# continue to next buy order sub
# end of for j in range(len(buy_subx_df)):
#################################################################################
### 【6】SAVE BUY/SELL ORDERS 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/sell_buy/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/sell_buy/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【7】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
#################################################################################
### calculate total buy qty / fee and mean price ★ buy_time ★
filter_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[filter_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order sub({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub
header_sum_real_qty = dfx.groupby('buy_time')['real_qty'].sum().values[0]
header_sum_real_fee = dfx.groupby('buy_time')['real_fee'].sum().values[0]
header_mean_real_price = dfx.groupby('buy_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 buy order (real_qty, real_price, real_fee) ★
find_mask = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order
buy_df.loc[find_mask, 'real_qty'] = header_sum_real_qty
buy_df.loc[find_mask, 'real_price'] = header_mean_real_price
buy_df.loc[find_mask, 'real_fee'] = header_sum_real_fee
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_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 buy order... ★
# end of for i in range(len(buy_df)):
#################################################################################
### 【8】SAVE BUY/SELL ORDER HEADER 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/sell_buy/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/sell_buy/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 : low to high price 100, 110, 120, 130, 140, 150
buy_bk_df = pd.DataFrame(buy_bk) # descending order of 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 sell_close_order
# 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 BUY ORDER HEADER
#################################################
### get all buy order header
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # unique time
sell_time = buy_df.loc[i, 'sell_time'] # unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
dfx.reset_index(inplace=True)
buy_order_id = str(dfx.loc[0, 'buy_order_id'])
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id}) has been closed: {buy_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
continue
# end of if buy_order_closed:
### open or incomplete buy order header
##########################################################################################
### 【2】READ ALL BUY ORDER SUB & EXECUTE GET_PRICE() FOR EACH BUY ORDER SUB
##########################################################################################
### for buy order sub : use pandas dataframe groupby()
buy_order_sub_seq = 0
# 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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
buy_order_sub_seq = j + 1 # 1, 2, 3,....
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★
sub_position_id = buy_subx_df.loc[j, 'position_id'] # (unique id)
sub_buy_qty = buy_subx_df.loc[j, 'qty']
sub_buy_price = buy_subx_df.loc[j, 'price']
sub_buy_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_buy_real_price = buy_subx_df.loc[j, 'real_price']
sub_buy_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_buy_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_buy_cancelled = buy_subx_df.loc[j, 'cancelled']
sub_buy_order_open = False
sub_buy_order_closed = False
sub_buy_order_incomplete = False
if sub_buy_real_qty == 0:
sub_buy_order_open = True
elif sub_buy_qty == sub_buy_real_qty:
sub_buy_order_closed = True
else:
sub_buy_order_incomplete = True
# fake buy order sub ? => 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
# cancelled buy order sub ? => SKIP
if sub_buy_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been cancelled ▶ {sub_buy_cancelled=} ")
continue
# stop loss buy order sub ? => SKIP
if sub_buy_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been stopped loss ▶ {sub_buy_stop_loss=} ")
continue
# closed buy order sub ? => SKIP
if sub_buy_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed ▶ {sub_buy_order_closed=} ")
continue
### open or incomplete buy order sub
############################################################
### 【3】GET ORDER STATUS
############################################################
### get order_status
qty = sub_buy_qty
price = sub_buy_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(sub_buy_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({sub_buy_order_id=}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")
continue # continue to next buy order sub
## get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
# pending exe_buy_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({sub_buy_order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_buy_close_order_limit() request ")
continue # continue to next buy order sub
############################################# DEBUG DEBUG DEBUG
if self.coin.debug_mode_gmo_stop_loss:
order_status = 'EXPIRED'
############################################# DEBUG DEBUG DEBUG
### executed exe_buy_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'
if order_status in 'EXPIRED,CANCELED':
### update buy order, buy 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({sub_buy_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 buy order sub
if order_status == 'EXPIRED':
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")
self.debug.sound_alert(3)
###########################################
### 1: close buy order sub
###########################################
# update a buy order sub (update real_qty, real_price, stop_loss)
# DO NOT USE: self.csv.update_buy_order_sub(buy_time, sub_order_id, symbol, col_name_list, col_value_list)
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# not found buy order sub ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee, stop_loss)
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
buy_stop_loss = True
buy_sub_df.loc[find_mask, 'real_qty'] = sub_buy_qty
buy_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
buy_sub_df.loc[find_mask, 'real_fee'] = gmo_loss_cut_fee # gmo loss cut fee
buy_sub_df.loc[find_mask, 'stop_loss'] = buy_stop_loss # buy order stop loss
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, {sub_buy_qty=:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {buy_stop_loss=}) ")
# end of if dfx.shape[0] == 0:
#######################################
### 2: close buy order
#######################################
# update buy order (real_qty, real_price, real_fee)
find_mask = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order
gmo_loss_cut_price = 9_999_999.0
gmo_loss_cut_fee = 999.0
buy_df.loc[find_mask, 'real_qty'] = buy_qty
buy_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price # gmo loss cut rate
buy_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_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={buy_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 sell_order_id
find_mask = ord_df['sell_order_id'] == sub_sell_order_id
dfx = ord_df[find_mask]
if dfx.shape[0] > 0:
# update_order_csv_sell(sell_time: utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(sell_time + sell_order_id)
self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])
continue # continue to next buy order sub
# end of if order_status == 'EXPIRED':
# end of if order_status in 'EXPIRED,CANCELED':
### get lowest sell price from sell orderbooks
latest_book_price = bk_sell_price # bk_sell_price = sell_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) => usef for fake mode
_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 buy real qty, price & fee from the GMO : get_price() for exe_buy_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_buy_order_id, sub_buy_qty, sub_buy_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, buy_order_sub_seq) # 1, 2, 3, ...
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({buy_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 buy order sub depending on get_price_count ★
# get buy 'get_price_count'
find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id) # PK(buy_time + buy_order_id)
dfx = buy_sub_df[find_mask]
# found buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
get_price_count = buy_sub_df.loc[ix, 'get_price_count'] # buy_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_buy_close_order_limit() request ?
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_buy_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_buy_close_order_limit() request: continue to next buy order sub... ")
status = 0 # reset status code
continue # continue to next buy 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_buy_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
continue # continue to next buy order sub
# end of if status == 9:
else: # normal return => executed exe_buy_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 BUY ORDER CHILD (buy_time, buy_order_id, real_qty, real_price, real_fee) + (sell_time, sell_order_id)
##############################################################################################################################
# response df for get_price(): exe_buy_close_order_limit()
for k in range(len(res_df)):
buy_order_id = res_df.loc[k, 'orderId'] # buy 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 buy order child ★
# 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:
_qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(sub_buy_time, buy_order_id, sub_sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)
# end of for k in range(len(res_df)):
# continue to next buy order sub
# end of for j in range(len(buy_subx_df)):
# continue to next buy order... ★
# end of for i in range(len(buy_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 SELL ORDER SUB INFO & APPEND/UPDATE ORDER CSV FILE (sell_time, sell_order_id, sell_real_qty, sell_real_price, sell_real_fee) & (buy_time, buy_order_id)
###############################################################################################################################################################################
### OPEN (load the order csv csv file) : order(BTC_JPY).csv
ord_df = self.csv.get_order_csv()
### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
sell_sub_df = self.csv.get_sell_order_sub()
### import buy order sub file
# get all sell order sub
for i in range(len(sell_sub_df)):
### get sell sub order 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)
sub_buy_order_id = sell_sub_df.loc[i, 'buy_order_id'] # ★ 'B9999-999999-999999;B9999-999999-999999;B9999-999999-999999' (list of buy_order_ids) : sell sub(one) <= buy sub(many)
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_closed = True if sub_sell_qty == sub_sell_real_qty else False
# fake 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
# empty order csv ?
if ord_df.shape[0] == 0:
dfx = pd.DataFrame()
else: # not empty dataframe => find the order csv by sell_order_id
find_mask = ord_df['sell_order_id'] == sub_sell_order_id # S9999-999999-999999
dfx = ord_df[find_mask]
# not found order csv ?
if dfx.shape[0] == 0:
### append order csv ★IMPORTANT★ DO NOT APEND 'Null' Value => buy_order_id = 'B9999-999999-999999'
columns = Gmo_dataframe.ORDER_CSV
_buy_real_qty_=0.0; _buy_real_price_=0.0; _buy_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_sell_stop_loss; _cancelled_=sub_sell_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, _buy_real_qty_, sub_sell_real_qty, _buy_real_price_, sub_sell_real_price, _buy_real_fee_, sub_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 info row)
ord_sell_order_id = sub_sell_order_id
ord_sell_real_qty = sub_sell_real_qty
ord_sell_real_price = sub_sell_real_price
ord_sell_real_fee = sub_sell_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_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=}) ")
else: # found order csv => update order csv
# 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']
# ord_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_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=}) ")
else: # incomplete order csv => update order csv
# update order csv
ord_sell_time = sub_sell_time
ord_sell_order_id = sub_sell_order_id
ord_sell_real_qty = sub_sell_real_qty
ord_sell_real_price = sub_sell_real_price
ord_sell_real_fee = sub_sell_real_fee
ord_df.loc[find_mask, 'sell_time'] = ord_sell_time
ord_df.loc[find_mask, 'sell_order_id'] = ord_sell_order_id
ord_df.loc[find_mask, 'sell_real_qty'] = ord_sell_real_qty
ord_df.loc[find_mask, 'sell_real_price'] = ord_sell_real_price
ord_df.loc[find_mask, 'sell_real_fee'] = ord_sell_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_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:
# end of if dfx.shape[0] == 0:
# continue to next sell order sub
# end of for i in range(len(sell_sub_df)):
#######################################################################################################################################
### 【2】IMPORT BUY ORDER SUB INFO & UPDATE ORDER CSV FILE (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee)
#######################################################################################################################################
### 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
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' (non unique id) : sell sub(many) => buy_sub(one)
sub_sell_order_id = buy_sub_df.loc[i, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
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_sell_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_open = False
sub_buy_order_closed = False
sub_buy_order_incomplete = False
if sub_buy_real_qty == 0:
sub_buy_order_open = True
elif sub_buy_qty == sub_buy_real_qty:
sub_buy_order_closed = True
else:
sub_buy_order_incomplete = True
# fake buy order sub ? => 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 ?
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 is empty: continue... ")
self.debug.sound_alert(3)
continue
# find the order csv by sell_order_id
find_mask = ord_df['sell_order_id'] == sub_sell_order_id
dfx = ord_df[find_mask]
# not found order csv ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} order csv not found: sell_order_id={sub_sell_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_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 ?
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
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=}) ")
else: # incomplete order csv => update order csv
### get a list of buy_order_id from the buy sub orders ★ sell_order_id ★
filter_mask = buy_sub_df['sell_order_id'] == sub_sell_order_id
dfx = buy_sub_df[filter_mask]
buy_order_id_list = dfx.groupby('sell_order_id')['buy_order_id'].apply(list)[0]
buy_order_ids_by_semicolon = ''
for k, order_id in enumerate(buy_order_id_list):
if k == 0:
buy_order_ids_by_semicolon += order_id
else:
buy_order_ids_by_semicolon += ';' + order_id
### calculate total sell qty / fee and mean price ★ sell_order_id ★
buy_sum_real_qty = dfx.groupby('sell_order_id')['real_qty'].sum().values[0]
buy_sum_real_fee = dfx.groupby('sell_order_id')['real_fee'].sum().values[0]
buy_mean_real_price = dfx.groupby('sell_order_id')['real_price'].mean().values[0]
### update buy csv (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee)
ord_buy_real_qty = round(buy_sum_real_qty, _q_) # round(999.12, _q_)
ord_buy_real_price = round(buy_mean_real_price, _p_) # round(999999.123, _p_)
ord_buy_real_fee = round(buy_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, 'buy_time'] = sub_buy_time
ord_df.loc[find_mask, 'buy_order_id'] = buy_order_ids_by_semicolon # 'OrderId1;OrderId2;OrderId3'
ord_df.loc[find_mask, 'buy_real_qty'] = ord_buy_real_qty # round(buy_sum_real_qty, _q_)
ord_df.loc[find_mask, 'buy_real_price'] = ord_buy_real_price # round(buy_mean_real_price, _p_)
ord_df.loc[find_mask, 'buy_real_fee'] = ord_buy_real_fee # round(buy_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_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:
# continue to next buy order sub
# end of for i in range(len(buy_sub_df)):
#######################################################################################################################################
### 【3】SAVE ORDER CSV 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/sell_buy/order(BTC_JPY).csv
ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file
### SAVE (order csv excel file)
# save to the order excel file
excel_file = self.folder_trading_type + f'order({symbol}).xlsx'
# desktop-pc/bot/sell_buy/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 columns
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_by(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.sell_buy_action14}), 15%({self.coin.sell_buy_action13}), 10%({self.coin.sell_buy_action12}) ")
# ### OPEN (load the order csv 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 BUY ORDER HEADER
#################################################
# define buy order header list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
### get all buy order
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # ★ unique time
sell_time = buy_df.loc[i, 'sell_time'] # ★ unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
buy_order_id = dfx.loc[ix, 'buy_order_id']
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
else:
pass #self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({buy_order_id=}) has been closed: {buy_order_closed=} ")
else:
pass #self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue # skip closed order
# end of if buy_order_closed:
#################################################################
### 【2】READ BUY ORDER SUB (open or incomplete status)
#################################################################
### for buy order sub (open or incomplete status)
# define buy order sub list
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # B9999-999999-999999 ★ (non unique id) : buy sub(many) => sell sub(one)
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # S9999-999999-999999 ★ (unique id)
sub_position_id = buy_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = buy_subx_df.loc[j, 'qty']
sub_price = buy_subx_df.loc[j, 'price']
sub_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_real_price = buy_subx_df.loc[j, 'real_price']
sub_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_get_price_count = buy_subx_df.loc[j, 'get_price_count']
sub_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_cancelled = buy_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 buy order sub ? => 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
# buy order sub has been stop loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss: {sub_stop_loss=} ")
continue
# buy order sub has been cancelled ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ")
continue
# closed buy order sub ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed: {sub_order_closed=} ")
continue
##########################################################################################
### 【3】CHECK STOP LOSS FOR EACH BUY ORDER SUB (open or incomplte buy sub order)
##########################################################################################
### open or incomplete buy 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
#####################################################
### 1) check loss cut price : sell => buy
### calculate stop loss amount
stop_loss_amt = 0
latest_buy_price = 0
filter_mask = mas_df['side'] == 'BUY'
dfx = mas_df[filter_mask]
if dfx.shape[0] > 0:
# get latest buy price from the GMO master dataframe
buy_price = dfx.iloc[-1]['price']
latest_buy_price = buy_price
# get last sell qty & price from the sell order header
qty = sell_df.iloc[-1]['qty']
sell_price = sell_df.iloc[-1]['real_price'] # get a real sell 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
### sell_buy : buy position => calculate upper limit for 5%, 10%, 15%, 18%, 20%, 23%, 25%(GMO loss cut rate)
# calculate loss cut price from the sell sub order : loss_cut_price = last_sell_real_price + (last_sell_real_price * 0.20)
last_sell_real_price = sell_sub_df.iloc[-1]['real_price']
loss_cut_price_05 = last_sell_real_price + (last_sell_real_price * 0.05) # 0.25 => 0.05
loss_cut_price_10 = last_sell_real_price + (last_sell_real_price * 0.10) # 0.25 => 0.10
loss_cut_price_15 = last_sell_real_price + (last_sell_real_price * 0.15) # 0.25 => 0.15
loss_cut_price_18 = last_sell_real_price + (last_sell_real_price * 0.18) # 0.25 => 0.18
loss_cut_price_20 = last_sell_real_price + (last_sell_real_price * 0.20) # 0.25 => 0.20 BOT LOSS CUT
loss_cut_price_23 = last_sell_real_price + (last_sell_real_price * 0.23) # 0.25 => 0.23 DO NOT USE THIS
loss_cut_price_25 = last_sell_real_price + (last_sell_real_price * 0.25) # 0.25 => 0.25 GMO LOSS CUT
# gmo master latest buy price is greater than zero ?
if latest_buy_price > 0:
if latest_buy_price >= loss_cut_price_25: # >= 125
self.coin.sell_buy_lc25 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 25%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_25%({loss_cut_price_25:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_23: # >= 120
self.coin.sell_buy_lc23 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 23%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_23%({loss_cut_price_23:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_20: # >= 115
self.coin.sell_buy_lc20 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 20%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_20%({loss_cut_price_20:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_18: # >= 113
self.coin.sell_buy_lc18 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 18%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_18%({loss_cut_price_18:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_15: # >= 112
self.coin.sell_buy_lc15 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 15%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_15%({loss_cut_price_15:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_10: # >= 110
self.coin.sell_buy_lc10 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 10%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_10%({loss_cut_price_10:,.{_p_}f}) ")
elif latest_buy_price >= loss_cut_price_05: # >= 105
self.coin.sell_buy_lc5 += 1
# self.debug.trace_warn(f".... DEBUG: loss cut 5%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_5%({loss_cut_price_05:,.{_p_}f}) ")
### check upper limit based on sell price
# GMO latest buy price is greater than or equal to loss cut price (20%) ? => EXECUTE STOP LOSS
if latest_buy_price >= loss_cut_price_20:
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}STOP LOSS DETECTED REAL(1-5) EXECUTE STOP LOSS {Bcolors.ENDC} loss cut 20%: latest_buy_price({latest_buy_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_buy_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 SELL ORDER SUB
######################################################
### cancel closed sell order
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed sell order sub({sub_sell_order_id=}) ")
# increment buy cancel count
self.gvar.sell_cancel_count += 1 # cancel closed sell order
self.coin.sell_cancel_count += 1 # cancel closed sell order
### set a cancelled flag for sell order sub : PK(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)
sell_sub_df.loc[find_mask, 'cancelled'] = True
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub(sell_order_id={sub_sell_order_id}, cancelled=True) ")
######################################################
### 【6】CANCEL OPEN/INCOMPLETE BUY ORDER SUB
######################################################
### for open or incomplete buy order sub
# cancel open/incomplete buy sub order
status, msg_code, msg_str = self.api.exe_cancel_order(sub_buy_order_id) # ★
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id}): exe_cancel_order() ▶ {status=} ")
sleep(0.5) # sleep 0.5 sec
###########################################################################################
### 【7】REBUY OUTSTANDING BUY ORDER SUB QTY : EXECUTE BUY CLOSE ORDER MARKET (FAK)
###########################################################################################
### rebuy open/incomplete buy order sub with market price (stop loss price)
outstanding_qty = sub_qty - sub_real_qty
new_buy_qty = outstanding_qty
qty = new_buy_qty
if sub_real_price > 0:
new_buy_price = sub_real_price + sub_real_fee
else:
new_buy_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
### rebuy buy sub order outstanding qty
# exe_buy_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_buy_close_order_market_wait(sub_position_id, qty, new_buy_price, mas_latest_close_price) # ★ FAK(Fill and Kill): new_buy_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}REBUY({loop_count}){Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_buy_close_order_market_wait() ▶ {status=} ")
# error ?
if status != 0:
if 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_buy_close_order_market_wait({sub_position_id=}) time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next buy sub orders
else: # misc error
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while tue loop and continue to read next buy sub orders
# end of if status == 8 and msg_code == 'ERR-888':
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
######################################################################################################
### 【8】ADD BUY ORDER CHILD ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★
######################################################################################################
# self.debug.trace_warn(f".... DEBUG: print res.df")
# print('-'*200)
# print(res_df)
# print('-'*200)
# res_df = get_price() response for exe_buy_close_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 REBUY VERSION OF NEW BUY ORDER SUB ★
### append a new record into the buy order child (★ this is the rebuy version of buy order ★)
# define buy 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_buy_close_order_market()
for k in range(len(res_df)):
order_id = res_df.loc[k, 'orderId'] # orderId (same orderId): rebuy 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) # B9999-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 buy order child (rebuy version) ★
# 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:
_qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(buy_time, order_id, sell_time, sub_sell_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(999.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 buy cancel count
self.gvar.buy_cancel_count += 1 # cancel open/incomplete buy order
self.coin.buy_cancel_count += 1 # cancel open/incomplete buy order
break # exit while true loop
else: # outstanding buy_qty exists
pass # skip (do nothing)
# end of if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
### partial exe_buy_close_order_market_wait() = continue to buy remaining qty
remaining_qty = new_buy_qty - new_sub_sum_real_qty
qty = remaining_qty
# continue exe_buy_close_order_market_wait() ★
# end of while True:
#################################################################################
### 【9】UPDATE BUY 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 buy order sub (real_qty, real_price, real_fee, stop_loss) ★
find_mask = (buy_sub_df['buy_time'] == sub_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".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update
buy_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty # add real_qty (rebuy version)
buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price # update real_price (rebuy version)
buy_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee # add real_fee (rebuy version)
buy_sub_df.loc[find_mask, 'stop_loss'] = True
ix = dfx.index[0]
new_sub_sum_real_qty = buy_sub_df.loc[ix, 'real_qty']
new_sub_sum_real_fee = buy_sub_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub(buy_order_id={sub_buy_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=True) ★
# update_order_csv_sell(sell_time: dt.datetime.utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None:
self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])
# reset buy order sub list values
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# continue to next buy order sub ... ★
# end of for j in range(len(buy_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/sell_buy/buy_order_sub(BTC_JPY).csv
buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
### CLOSE (save the sell order sib csv file) : sell_order_sub(BTC_JPY).csv
csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv'
# desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【12】UPDATE BUY ORDER HEADER (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 = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order => update
buy_df.loc[find_mask, 'real_qty'] += header_sum_real_qty # add real_qty (rebuy version)
buy_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (rebuy version)
buy_df.loc[find_mask, 'real_fee'] += header_sum_real_fee # add real_fee (rebuy version)
ix = dfx.index[0]
new_header_sum_real_qty = buy_df.loc[ix, 'real_qty']
new_header_sum_real_fee = buy_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_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 buy order
# end of for i in range(len(buy_df)):
#################################################################################
### 【13】SAVE BUY/SELL ORDER HEADER 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/sell_buy/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/sell_buy/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 BUY ORDER HEADER
#################################################
# define buy order header list
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
### get all buy order
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # ★ unique time
sell_time = buy_df.loc[i, 'sell_time'] # ★ unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
buy_order_id = dfx.loc[ix, 'buy_order_id']
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id=}) has been closed: {buy_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue
# end of if buy_order_closed:
#################################################################
### 【2】READ ALL BUY ORDER SUB (OPEN OR INCOMPLETE STATUS)
#################################################################
### for buy order sub (open or incomplete status)
# define buy order sub list
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : buy sub(many) => sell sub(one)
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_position_id = buy_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = buy_subx_df.loc[j, 'qty']
sub_price = buy_subx_df.loc[j, 'price']
sub_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_real_price = buy_subx_df.loc[j, 'real_price']
sub_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_cancelled = buy_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 buy 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
# buy sub order has been stopped loss ? => SKIP
if sub_stop_loss:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss ▶ {sub_stop_loss=} ")
continue
# cancelled buy sub order ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ")
continue
# closed buy sub order ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been closed: {sub_order_closed=} ")
continue
### open or incomplete buy 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 SELL ORDER SUB : ★ buy order sub (many) => sell order sub (one) ★
##############################################################################################
### for closed sell order sub
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed sell order sub({sub_sell_order_id=}) ")
# increment sell cancel count
self.gvar.sell_cancel_count += 1 # cancel closed sell order
self.coin.sell_cancel_count += 1 # cancel closed sell order
### set a cancelled flag for sell order sub : ★ PK(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)
sell_sub_df.loc[find_mask, 'cancelled'] = True
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, cancelled=True) ")
######################################################
### 【5】CANCEL OPEN/INCOMPLETE BUY ORDER SUB
######################################################
### cancel open or incomplete buy order sub
### cancel buy order sub
status, msg_code, msg_str = self.api.exe_cancel_order(sub_buy_order_id) # ★
self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CANCEL{Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_cancel_order() ▶ {status=} ")
sleep(0.5) # sleep 0.5 sec
##################################################################################################
### 【6】REBUY OUTSTANDING BUY ORDER SUB QTY : EXECUTE BUY CLOSE ORDER MARKET WAIT (FAK)
##################################################################################################
### rebuy open or incomplete buy order sub with market price
outstanding_qty = sub_qty - sub_real_qty # calculate outstanding qty
new_buy_qty = outstanding_qty
qty = new_buy_qty
if sub_real_price > 0:
new_buy_price = sub_real_price + sub_real_fee
else:
new_buy_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_buy_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_buy_close_order_market_wait(sub_position_id, qty, new_buy_price, mas_latest_close_price) # ★ FAK(Fill and Kill): new_buy_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}REBUY({loop_count}){Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_buy_close_order_market_wait() ▶ {status=} ")
# error ?
if status != 0:
if 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_buy_close_order_market_wait({sub_position_id=}) time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next buy sub orders
else: # misc error
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) misc error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")
break # exit while true loop and continue to read next buy sub orders
# end of if status == 8 and msg_code == 'ERR-888':
# end of if status != 0:
### no error : res_df.shape[0] >= 1 (single or multiple rows)
##################################################################################################
### 【7】ADD BUY ORDER CHILD ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★
##################################################################################################
# res_df = response for exe_buy_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 REBUY VERSION OF NEW BUY ORDER SUB ★
### append a new record into the buy order child (★ this is the rebuy version of buy order child ★)
# define buy order child list
order_id_list = []
position_id_list = []
real_qty_list = []
real_price_list = []
real_fee_list = []
# response for exe_buy_close_order_market_wait()
for k in range(len(res_df)):
order_id = res_df.loc[k, 'orderId'] # orderId (same orderId) : rebuy 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) # B9999-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 buy order child (rebuy version) ★
# 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:
_qty_ = real_qty; _price_ = real_price
self.csv.write_buy_order_child(buy_time, order_id, sell_time, sub_sell_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_)
sum_real_fee = round(sum(real_fee_list), 0)
if sum(real_price_list) > 0:
mean_real_price = round(statistics.mean(real_price_list), _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 buy_qty ?
if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
# increment buy cancel count
self.gvar.buy_cancel_count += 1 # cancel open/incomplete buy order
self.coin.buy_cancel_count += 1 # cancel open/incomplete buy order
break # exit while true loop
else: # outstanding buy_qty exists
pass # skip (do nothing)
# end of if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
### partial exe_buy_close_order_market_wait() = continue to buy remaining qty
remaining_qty = new_buy_qty - new_sub_sum_real_qty
qty = remaining_qty
# continue exe_buy_close_order_market_wait() ★
# end of while True:
#################################################################################
### 【8】UPDATE BUY 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 buy order sub (real_qty, real_price, real_fee, cancelled)
find_mask = (buy_sub_df['buy_time'] == sub_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".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL}buy order sub not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order sub => update
buy_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty # add real_qty (rebuy version)
buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price # update real_price (rebuy version)
buy_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee # add real_fee (rebuy version)
buy_sub_df.loc[find_mask, 'cancelled'] = True
ix = dfx.index[0]
new_sub_sum_real_qty = buy_sub_df.loc[ix, 'real_qty']
new_sub_sum_real_fee = buy_sub_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_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 (cancelled=True, order_closed=True) ★
# update_order_csv_sell(sell_time: dt.datetime.utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None:
self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['cancelled'], col_value_list=[True]) # PK(sell_time + sell_order_id)
# reset buy order sub list values
sub_real_qty_list = []
sub_real_price_list = []
sub_real_fee_list = []
# continue to next buy order sub
# end of for j in range(len(buy_subx_df)):
#################################################################################
### 【10】SAVE BUY/SELL ORDERS 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/sell_buy/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/sell_buy/sell_order_sub(BTC_JPY).csv
sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode
#################################################################################
### 【11】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
#################################################################################
if len(header_real_qty_list) > 0:
### update buy 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 = buy_df['buy_time'] == buy_time
dfx = buy_df[find_mask]
# not found buy order ?
if dfx.shape[0] == 0:
self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL}buy order({buy_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")
else: # found buy order => update
buy_df.loc[find_mask, 'real_qty'] += header_sum_real_qty # add real_qty (rebuy version)
buy_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (rebuy version)
buy_df.loc[find_mask, 'real_fee'] += header_sum_real_fee # add real_fee (rebuy version)
ix = dfx.index[0]
new_header_sum_real_qty = buy_df.loc[ix, 'real_qty']
new_header_sum_real_fee = buy_df.loc[ix, 'real_fee']
self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_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 buy header list values
header_real_qty_list = []
header_real_price_list = []
header_real_fee_list = []
# continue to next buy order... ★
# end of for i in range(len(buy_df)):
#################################################################################
### 【12】SAVE BUY/SELL ORDER HEADER TO CSV FILES
#################################################################################
### CLOSE (save the buy order csv file)
csv_file = self.folder_trading_type + f'buy_order({symbol}).csv'
# desktop-pc/bot/sell_buy/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/sell_buy/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 info csv file) : order_by(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 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 BUY ORDER HEADERS
#################################################
### get all buy order headers
for i in range(len(buy_df)):
### get buy order info
buy_time = buy_df.loc[i, 'buy_time'] # ★ unique time
sell_time = buy_df.loc[i, 'sell_time'] # ★ unique time
buy_qty = buy_df.loc[i, 'qty']
buy_price = buy_df.loc[i, 'price']
buy_real_qty = buy_df.loc[i, 'real_qty']
buy_real_price = buy_df.loc[i, 'real_price']
buy_real_fee = buy_df.loc[i, 'real_fee']
buy_order_open = False
buy_order_closed = False
buy_order_incomplete = False
if buy_real_qty == 0:
buy_order_open = True
elif buy_qty == buy_real_qty:
buy_order_closed = True
else:
buy_order_incomplete = True
# closed buy order ? => SKIP
if buy_order_closed:
# find the buy order sub
find_mask = buy_sub_df['buy_time'] == buy_time
dfx = buy_sub_df[find_mask]
# found the buy order sub ?
if dfx.shape[0] > 0:
ix = dfx.index[0]
buy_order_id = dfx.loc[ix, 'buy_order_id']
if buy_order_id.startswith('Fake'):
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id=}) has been closed: {buy_order_closed=} ")
else:
pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ")
# end of if dfx.shape[0] > 0:
continue
# end of if buy_order_closed:
#################################################################
### 【2】READ ALL BUY ORDER SUB (OPEN OR INCOMPLETE STATUS)
#################################################################
### for buy order sub (open or incomplete status)
# 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 for this buy order
for j in range(len(buy_subx_df)): # ★ buy_subx_df ★
### get buy order sub info
sub_buy_time = buy_subx_df.loc[j, 'buy_time'] # non unique time
sub_sell_time = buy_subx_df.loc[j, 'sell_time'] # non unique time
sub_buy_order_id = buy_subx_df.loc[j, 'buy_order_id'] # ★ 'B9999-999999-999999' (non unique id) : buy sub(many) => sell sub(one)
sub_sell_order_id = buy_subx_df.loc[j, 'sell_order_id'] # ★ 'S9999-999999-999999' (unique id)
sub_position_id = buy_subx_df.loc[j, 'position_id'] # ★ (unique id)
sub_qty = buy_subx_df.loc[j, 'qty']
sub_price = buy_subx_df.loc[j, 'price']
sub_real_qty = buy_subx_df.loc[j, 'real_qty']
sub_real_price = buy_subx_df.loc[j, 'real_price']
sub_real_fee = buy_subx_df.loc[j, 'real_fee']
sub_get_price_count = buy_subx_df.loc[j, 'get_price_count']
sub_stop_loss = buy_subx_df.loc[j, 'stop_loss']
sub_cancelled = buy_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 buy order sub ? => 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
# buy 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} buy order sub({sub_buy_order_id=}) has been stopped loss ▶ {sub_stop_loss=} ")
continue
# cancelled buy order sub ? => SKIP
if sub_cancelled:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ")
continue
# closed buy order sub ? => SKIP
if sub_order_closed:
# self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been closed: {sub_order_closed=} ")
continue
### open or incomplete buy order sub
#################################################################
### 【3】CALCULATE LEVERAGE FEE AND UPDATE ORDER CSV FILE
#################################################################
### calculate leverage fee
# get a sell order child
find_mask = (sell_ch_df['sell_time'] == sub_sell_time) & (sell_ch_df['sell_order_id'] == sub_sell_order_id) & (sell_ch_df['position_id'] == sub_position_id)
dfx = sell_ch_df[find_mask]
# not found sell order child ?
if dfx.shape[0] == 0:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found sell order child{Bcolors.ENDC} : {sub_sell_time=:%Y-%m-%d %H:%M:%S}, {sub_sell_order_id=}, {sub_position_id=} ")
else: # found sell order child
ix = dfx.index[0]
ch_sell_real_qty = dfx.loc[ix, 'real_qty']
ch_sell_real_price = dfx.loc[ix, 'real_price']
ch_leverage_fee = math.ceil((ch_sell_real_price * ch_sell_real_qty) * 0.0004) # leverage fee (0.04%)
### update order csv (accumulated_leverage_fee, close_count, close_update_date)
# not empty order csv ?
if ord_df.shape[0] > 0:
find_mask = ord_df['sell_order_id'] == sub_sell_order_id # S9999-999999-999999
dfx = ord_df[find_mask]
# not found order csv ?
if dfx.shape[0] == 0:
self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found order csv{Bcolors.ENDC} : {sub_sell_order_id=} ")
else: # found order csv
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
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
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, 'close_update_date'] = 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_sell(): sell_order_id={sub_sell_order_id}, leverage_fee={new_accumulated_leverage_fee:,.0f}, close_count={new_close_count}, close_update_date={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 buy order sub...
# end of for j in range(len(buy_subx_df)):
# continue to next buy order... ★
# end of for i in range(len(buy_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/sell_buy/order(BTC_JPY).csv
ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file
return