
หลังจากเพิ่มเติม stoploss ไปแล้ว เราจะมาเพิ่มรายละเอียดกับการ Backtest กับอีกซักหน่อยหนึง จากบทความที่แล้ว ยังมีความไม่สมจริงอยู่บางอย่างคือ การคิดกำไรเมื่อตอนเราเปิดสัญญา Long หรือ Short
(df["Close"][i]/df["Close"][i-1])-1
การคิดคำนวณแบบนี้มันคงไม่มีปัญหาอะไรถ้าเราจริงๆ ถ้าเรามีสัญญา Long อยู่ หุ้นขึ้นจาก 10 บาท ไป 12 บาทเราก็ควรจะได้กำไร (12/10)-1 = 0.2 หรือคิดเป็นกำไร 20% อยู่แล้วถ้าเรามีหุ้นตัวนั้นอยู่ใน Portfolio ของเรา แต่มันยังมีเคสความไม่สมจริงอยู่ คือในกรณีที่เราเข้าซื้อวันแรก สมมุติว่า เรามีสัญญาณซื้อวันที่ 5มกราคม เราก็จะต้องไปซื้อหุ้นเข้าพอร์ตนะวันที่ 6 หรือวันถัดมานั่นเอง
ถ้าราคาหุ้น
- วันที่ 5 ปิดที่ 10 บาท
- ราคาเปิดของวันที่ 6 เปิดที่ 11 บาท และไปปิดที่ 12 บาท
ในกรณีนี้ การคำนวณโดยใช้ (ราคาปิดเมื่อวาน/ราคาปิดวันนี้)-1 ย่อมไม่ถูกต้อง เพราะราคามันเปิดที่ 11 บาท!!! มันไม่มีทางเลยที่เราจะได้หุ้นนั้นมาในราคา 10 ถ้าเราซื้อตอนราคาเปิดของวันพรุ่งนี้เว้นแต่เราจะเจอแรงเทขายตอนเช้าแล้วราคาตกลงมาที่ 10 จริงๆแล้วเราบังเอิญได้ราคานั้นพอดี แต่จริงๆแล้ว โอกาสที่เราจะได้หุ้นราคาเปิดวันนี้มา ในราคาเดียวกับราคาปิดเมื่อวาน มันไม่ได้มีมากนัก ปัญหานี้มันก็จะเป็นแบบนี้เช่นเดียวกับกรณีที่เรามีเปิดสัญญา Short ครั้งแรก อีกทั้ง
อีกกรณีคือการปิดสัญญาครับ ครึ่งบางครั้งเมื่อเราปิดสัญญาซื่อขาย ถ้าเราเจอ stoploss วันที่ 6 เราต้องไปปิดสัญญาใน วันที่ 7 เราเองอาจจะไม่ได้อยากรอให้จนถึงจบวันแล้วค่อยคิดกำไรขาดทุนก็ได้ เราอาจจะแค่รีบๆออกมาเลยก็ได้
ฉะนั้นใน part ที่ 3 ของชุดบทความนี้ เราจะมาเพิ่มเติมวันเข้าเปิดสัญญา และ ซื้อและขาย ครั้งแรก + ปิดสัญญาในกรณีเจอ stoploss หรือ Counter Trend กันครับ
วันที่เกิด Counter Trend หรือสัญญาณตรงข้ามกับสัญญาณเดิม เราจะต้องคิดกำไรสองแบบ คือ คิดกำไรของการปิดสัญญานั้นๆในวันนั้นก่อน แล้วเราค่อยมาคิดสัญญาการเปิดสัญญาใหม่ครั้งแรก
ก่อนอื่นเราจะไปที่ฟังก์ชั่น signal_creator ของเรากันก่อนเลย
def signal_creator(params):
transaction = ""
df = params["df"].copy()
i = params["i"]
signal = params["signal"]
prev_signal = params["signal"]
if signal != "Buy":
if (df["sma_fast"][i]>=df["sma_slow"][i]) & (df["sma_fast"][i-1] < df["sma_slow"][i-1]) :
if prev_signal == "Sell":
signal = "Sell_to_Buy"
else:
signal = "Buy"
print(df.index[i], signal, "signal created!!!")
transaction = "first"
if signal != "Sell":
if (df["sma_fast"][i]<=df["sma_slow"][i]) & (df["sma_fast"][i-1]>df["sma_slow"][i-1]):
if prev_signal == "Buy":
signal = "Buy_to_Sell"
else:
signal = "Sell"
print(df.index[i], signal, "signal created!!!")
transaction = "first"
if signal == "Buy" and transaction =="":
if df["Close"][i] < df["Close"][i-1] - df["ATR"][i-1]*2:
signal = "Stop_Buy"
print(df.index[i], signal, "signal created!!!")
if signal == "Sell"and transaction =="":
if df["Close"][i] > df["Close"][i-1] + stock["ATR"][i-1]*2:
signal = "Stop_Sell"
print(df.index[i], signal, "signal created!!!")
return signal, transaction
สิ่งที่เพิ่มเข้ามาไม่ได้มีอะไรมา อันแรกคือตัวแปร transaction = “” ให้เป็นค่าตั้งต้น จากนนั้นเมื่อมีสัญญาณใดๆเป็นครั้งแรกเราจะให้ค่าเป็น transaction = “first” เพื่อบอกโปรแกรมว่าเรากำลังมีสัญญาณครั้งแรก แล้วเราไปเพิ่มตรงการ return ของ Function ให้เป็น return signal, transaction
เพิ่มตัวแปร prev_signal = params[“signal”] เพื่อเช็คว่า สัญญาก่อนหน้านั้นเป็นสัญญาอย่างไร เพราะในกรณีที่เรามีสัญญา ซื้อหรือขาย อยู่ใน portfolio ของเราอยู่แล้ว ถ้ามี signal ใหม่ขึ้นมา แล้วมันไม่ตรงกับ signal เดิม เราก็ให้สัญญาณใหม่ขึ้นมา เช่นในกรณีที่ signal เป็น Buy อยู่แล้วเราไปเจอสัญญาณ Counter Trend คือไปเจอสัญญาณให้เราทำการเปิดหน้า Short ขึ้นมา เราจะให้สัญญาณเป็น
if signal != "Sell":
if (df["sma_fast"][i]<=df["sma_slow"][i]) & (df["sma_fast"][i-1]>df["sma_slow"][i-1]):
if prev_signal == "Buy":
signal = "Buy_to_Sell
ตามโค้ดส่วนนี้ ถ้ามีสัญญาณตรงข้ามกับสัญญาณเดิม ถ้าเรามีสัญญาณ Buy อยู่ แล้วมีสัญญษณ Sell มาเราจะเปลี่ยน Signal เป็น “Buy_to_Sell” เพื่อการคิดกำไรอกีรูปแบบหนึงต่อไป เราจะทำเช่นเดียวกันกับอีกฝั่ง
if signal != "Buy":
if (df["sma_fast"][i]>=df["sma_slow"][i]) & (df["sma_fast"][i-1] < df["sma_slow"][i-1]) :
if prev_signal == "Sell":
signal = "Sell_to_Buy"
แล้วก็ตรง stoploss ผมได้เพิ่มเป็นสัญญาณ if signal == “Buy” and transaction ==””: ไว้ด้วยเพราะเหตุผลที่ว่า วันที่เรามีสัญญาณซื้อเป็นครั้งแรกผมจะไม่มาเช็ค Stoploss นั่นเอง
จากนั้นเราไปขั้นตอนที่ main loop ของโปรแกรมของเรา
def strategy(stock):
df = stock.copy()
signal = ""
ret = []
for i in range(0,len(df)):
params ={"df": df, "i": i, "signal": signal}
if signal == "":
ret.append(cal_ret(params, opt="No_Trade"))
if i > 0:
signal, transaction = signal_creator(params)
elif signal == "Buy":
if transaction == "first":
ret.append(cal_ret(params, opt="First_Buy"))
else:
ret.append(cal_ret(params, opt="Buy"))
signal, transaction = signal_creator(params)
elif signal == "Sell":
if transaction == "first":
ret.append(cal_ret(params, opt="First_Sell"))
else:
ret.append(cal_ret(params, opt="Sell"))
signal, transaction = signal_creator(params)
elif signal == "Stop_Buy":
ret.append(cal_ret(params, opt="Stop_Buy"))
signal = ""
elif signal == "Stop_Sell":
ret.append(cal_ret(params, opt="Stop_Sell"))
signal = ""
elif signal == "Buy_to_Sell" :
ret.append(cal_ret(params, opt="Stop_Buy")+cal_ret(params, opt="First_Sell") )
signal = "Sell"
params.update({"signal" : signal})
signal, transaction = signal_creator(params)
elif signal == "Sell_to_Buy" :
ret.append(cal_ret(params,opt="Stop_Sell")+cal_ret(params,opt="First_Buy") )
signal = "Buy"
params.update({"signal" : signal})
signal, transaction = signal_creator(params)
df["strategy"] = np.array(ret)
return df
ที่ main loop ของเราผมเปลี่ยนมันเป็น Function การเรียนหาสัญญาณใน signal_creator เราก็จะเพิ่มตัวแปรมารับค่า transaction เพิ่มมา 1 ตัว เป็นรูปแบบนี้
signal, transaction = signal_creator(params)
และผมได้มีการเรียกหา Function cal_ret สำหรับการซื้อขายแต่ละครั้งผมจะเช็ดด้วยว่าการได้สัญญาณครั้งนั้นเป็นการได้สัญญาณซื้อขายครั้งแรกหรือไม่ ถ้าใช่ผมจะเรียก
elif signal == "Buy":
if transaction == "first":
ret.append(cal_ret(params, opt="First_Buy"))
else:
ret.append(cal_ret(params, opt="Buy"))
signal, transaction = signal_creator(params)
เราจะเช็คว่าถ้าสัญญาณที่ได้มาเป็น trasaction ครั้งแรกหรือไม่ ถ้าใช่ เราก็จะเรียก cal_ret(params, opt=”First_Buy”) เพราะเราจะซื้อมันเป็นครั้งแรกด้วยราคาเปิดของมัน(เพราะคิดกำไรจากราคาปิดเมื่อวานมันไม่ถูกต้อง เมื่อวานเราไม่ได้มีหุ้นนี้ในportfolio) ถ้าไม่ใช่ ก็แปลว่าเรามีหุ้นอยู่ใน portfolio แล้วก็ไปเรียก cal_ret(params, opt=”Buy”) ธรรมดา
จากนั้นไปที่โค้ดที่เพิ่มมาใหม่ในส่วนของ elif signal == “Buy_to_Sell” : และ elif signal == “Sell_to_Buy” : คือสองกรณี
- การปิดสัญญา Buy และเปิดสัญญา Sell
- การปิดสัญญา Sell และเปิดสัญญา Buy
elif signal == "Buy_to_Sell" :
ret.append(cal_ret(params, opt="Stop_Buy")+cal_ret(params, opt="First_Sell") )
signal = "Sell"
params.update({"signal" : signal})
signal, transaction = signal_creator(params)
elif signal == "Sell_to_Buy" :
ret.append(cal_ret(params,opt="Stop_Sell")+cal_ret(params,opt="First_Buy") )
signal = "Buy"
params.update({"signal" : signal})
signal, transaction = signal_creator(params)
ในขั้นตอนนี้เราจะทำสองอย่าง คือปิดสัญญาเดิม และ เปิดสัญญาใหม่ ในกรณีที่เราจะปิดสัญญา Buy ไปเปิดสัญญา Sell ผมจะต้องคิดกำไรสองแบบด้วยกันคือ cal_ret(params, opt=”Stop_Buy”) + cal_ret(params, opt=”First_Sell”) กล่าวเป็นภาษาคนเราก็คือ เมื่อวันที่ผมมีสัญญาณ Sell แล้วผมมีหุ้นอยู่ใน Portfolio ของผม เช้าวันต่อมาผมจะรีบปิดสัญญาญ คือผมจะขายหุ้นออกไปที่ราคาเปิดเลย ด้วยการคิด cal_ret(params, opt=”Stop_Buy”) พอขายออกไปแล้วในวันนั้นมันจะไม่จบแค่นั้น เพราะผมต้องเปิดสัญญา Sell ด้วยเช่นกัน ผมก็จะคิดกำไรทางฝั่ง Sell จากราคาเปิดของมันเช่นกัน จึงเรียกคำสั่ง cal_ret(params, opt=”First_Sell”) มาซึ่งเราจะทำเช่นเดียวกันกับฝั่ง Sell_to_Buy
โอเคครับ มาถึงช่วงนี้แล้วผมว่าเราน่าจะเริ่มมาคิดกำไร calret ของเรากันแล้ว
def cal_ret(params, opt):
df = params["df"]
i = params["i"]
if opt == "No_Trade":
ret = 0
elif opt == "Buy":
ret = (df["Close"][i]/df["Close"][i-1])-1
elif opt == "Sell":
ret = 1-(df["Close"][i]/df["Close"][i-1])
elif opt == "Stop_Buy":
ret = (df["Open"][i]/df["Close"][i-1])-1
elif opt == "Stop_Sell":
ret = 1-(df["Open"][i]/df["Close"][i-1])
elif opt =="First_Buy":
ret = (df["Close"][i]/ df["Open"][i])-1
elif opt == "First_Sell":
ret = 1-(df["Close"][i] /df["Open"][i])
print(df.index[i], opt,"return: ",round(ret*100,2),"%")
return ret
ตรงนี้มันก็จะ Make Sense นะครับว่าทำไมบทความก่อน cal_ret opt == “Sell” และ opt == “Stop_sell” กับ opt == “Buy” และ opt = “Stop_Buy” ทำไมอย่างละสองตัวทั้งๆที่ด้านในเหมือนกัน จริงๆคือผมทำไว้ก่อนละจะเพิ่มรายละเอียดตอนนี้ครับ
elif opt == "Stop_Buy":
ret = (df["Open"][i]/df["Close"][i-1])-1
elif opt == "Stop_Sell":
ret = 1-(df["Open"][i]/df["Close"][i-1]
ส่วนของ Stop_Buy และ Stop_Sell ผมจะตีความว่า ไม่ว่าเราจะได้สัญญาณ Counter trend หรือ Stoploss มา ผมก็จะรีบขายมันออกไปที่ราคาเปิดมันเลย ผมไม่อยากถือไว้จนหมดวันผมจริงคิดกำไรโดยการใช้ (df[“Open”][i]/df[“Close”][i-1])-1 ในฝั่ง Stop_Buy และ 1-(df[“Open”][i]/df[“Close”][i-1] ในฝั่ง Sell
แล้วเราก็จะไม่ได้รีบแค่ฝั่ง Stop แต่เราจะรีบซื้อในฝั่งการซื้อครั้งแรกด้วย
elif opt =="First_Buy":
ret = (df["Close"][i]/ df["Open"][i])-1
elif opt == "First_Sell":
ret = 1-(df["Close"][i] /df["Open"][i])
First_Buy เราใช้ (df[“Close”][i]/ df[“Open”][i])-1 คือ (ราคาปิดวันนี้/ราคาเปิดวันนี้)-1 เพราะวันนี้ว่ากำไรในการเข้าซื้อครั้งแรกของเราเราจะได้กำไรก็ต่อเมื่อ ราคาปิดวันนั้น สูงกว่าราคาเปิดที่เราซื้อมาตอนเช้า
First_Sell ก็เช่นกันเราใช้ 1-(df[“Close”][i] /df[“Open”][i]) คือ 1-(ราคาปิดวันนี้/ราคาเปิดวันนี้) ถ้าเรา Short ในวันนี้เราจะได้กำไรก็ต่อเมื่อ ราปิดวันนี้มันต่ำกว่าราคาเปิดเมื่อเช้า
ใส่รายละเอียดแล้วก็รันมันครับ
stock = strategy(stock)

วัดผลเทียบกับ Buy and Hold
print("pnl",((1+stock["strategy"]).cumprod()[-1]-1)*100)
plt.plot((1+stock["strategy"]).cumprod(), label="Strategy", color = 'green')
plt.plot((1+bnh["rets"].fillna(0)).cumprod(), label="BnH", color = 'black')
plt.title(symbol+" strategy return")
plt.legend()
plt.show()

และก็นี่คือผลงานหลังจากเราปรับนู้นนิดนี่หน่อย ปิดสัญญาให้เร็วขึ้น + เปิดสัญญาคิดกำไรให้สมจริง ดูเหมือนผลลัพธ์จะดีขึ้นมานิดหน่อย อันนี้ไม่ใช่ประเด็นครับ แล้วแต่ดาต้า ถ้าเป็นดาต้าอื่นผลอาจจะแย่ลงก็ได้ แต่ประเด็นคือเรากำลังทำมันให้มีรายละเอียดที่สมจริงเพิ่มขึ้นนั่นเอง part หน้าเราจะเพิ่มรายละเอียดให้กับการเทรดไปอีกด้วยการจัดเก็บ trading log เพื่อเก็สถิติเพิ่มเติมนะครับ
