Files
v2realbot/v2realbot/strategy/StrategyClassicSL.py
David Brázda be93c17848 retreat commit
2023-10-09 09:15:52 +02:00

293 lines
16 KiB
Python

from v2realbot.strategy.base import Strategy
from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc,price2dec, zoneNY, print, json_serial, safe_get, get_tick, send_to_telegram
from v2realbot.utils.tlog import tlog, tlog_exception
from v2realbot.enums.enums import Mode, Order, Account, RecordType, Followup
#from alpaca.trading.models import TradeUpdate
from v2realbot.common.model import TradeUpdate
from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
from alpaca.trading.enums import TradeEvent, OrderStatus
from v2realbot.indicators.indicators import ema
import json
from datetime import datetime
#from rich import print
from random import randrange
from alpaca.common.exceptions import APIError
import copy
from threading import Event
from uuid import UUID, uuid4
from v2realbot.strategyblocks.indicators.indicators_hub import populate_all_indicators
class StrategyClassicSL(Strategy):
"""
Base override file for Classic Stop-Loss startegy
"""
def __init__(self, name: str, symbol: str, next: callable, init: callable, account: Account, mode: Mode = Mode.PAPER, stratvars: AttributeDict = None, open_rush: int = 30, close_rush: int = 30, pe: Event = None, se: Event = None, runner_id: UUID = None, ilog_save: bool = False) -> None:
super().__init__(name, symbol, next, init, account, mode, stratvars, open_rush, close_rush, pe, se, runner_id, ilog_save)
#zkontroluje zda aktualni profit/loss - nedosahnul limit a pokud ano tak vypne strategii
async def stop_when_max_profit_loss(self):
self.state.ilog(e="CHECK MAX PROFIT")
max_sum_profit_to_quit = safe_get(self.state.vars, "max_sum_profit_to_quit", None)
max_sum_loss_to_quit = safe_get(self.state.vars, "max_sum_loss_to_quit", None)
if max_sum_profit_to_quit is not None:
if float(self.state.profit) >= float(max_sum_profit_to_quit):
self.state.ilog(e=f"QUITTING MAX SUM PROFIT REACHED {max_sum_profit_to_quit=} {self.state.profit=}")
self.state.vars.pending = "max_sum_profit_to_quit"
send_to_telegram(f"QUITTING MAX SUM PROFIT REACHED {max_sum_profit_to_quit=} {self.state.profit=}")
self.se.set()
return True
if max_sum_loss_to_quit is not None:
if float(self.state.profit) < 0 and float(self.state.profit) <= float(max_sum_loss_to_quit):
self.state.ilog(e=f"QUITTING MAX SUM LOSS REACHED {max_sum_loss_to_quit=} {self.state.profit=}")
self.state.vars.pending = "max_sum_loss_to_quit"
send_to_telegram(f"QUITTING MAX SUM LOSS REACHED {max_sum_loss_to_quit=} {self.state.profit=}")
self.se.set()
return True
return False
async def add_followup(self, direction: TradeDirection, size: int, signal_name: str):
trade_to_add = Trade(
id=uuid4(),
last_update=datetime.fromtimestamp(self.state.time).astimezone(zoneNY),
status=TradeStatus.READY,
size=size,
generated_by=signal_name,
direction=direction,
entry_price=None,
stoploss_value = None)
self.state.vars.prescribedTrades.append(trade_to_add)
self.state.vars.requested_followup = None
self.state.ilog(e=f"FOLLOWUP {direction} added to prescr.trades {signal_name=} {size=}", trade=trade_to_add)
async def orderUpdateBuy(self, data: TradeUpdate):
o: Order = data.order
signal_name = None
##nejak to vymyslet, aby se dal poslat cely Trade a serializoval se
self.state.ilog(e="Příchozí BUY notif", msg=o.status, trade=json.loads(json.dumps(data, default=json_serial)))
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
#jde o uzavření short pozice - počítáme PROFIT
if int(self.state.positions) < 0:
#PROFIT pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena
#naklady vypocteme z prumerne ceny, kterou mame v pozicich
bought_amount = data.qty * data.price
#podle prumerne ceny, kolik stalo toto mnozstvi
avg_costs = float(self.state.avgp) * float(data.qty)
if avg_costs == 0:
self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.")
avg_costs = bought_amount
trade_profit = round((avg_costs-bought_amount),2)
self.state.profit += trade_profit
self.state.ilog(e=f"BUY notif - SHORT PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)}", msg=str(data.event), bought_amount=bought_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id))
#zapsat profit do prescr.trades
for trade in self.state.vars.prescribedTrades:
if trade.id == self.state.vars.pending:
trade.last_update = datetime.fromtimestamp(self.state.time).astimezone(zoneNY)
trade.profit += trade_profit
trade.profit_sum = self.state.profit
signal_name = trade.generated_by
if data.event == TradeEvent.FILL:
#zapsat update profitu do tradeList
for tradeData in self.state.tradeList:
if tradeData.execution_id == data.execution_id:
#pridat jako attribut, aby proslo i na LIVE a PAPPER, kde se bere TradeUpdate z Alpaca
setattr(tradeData, "profit", trade_profit)
setattr(tradeData, "profit_sum", self.state.profit)
setattr(tradeData, "signal_name", signal_name)
#self.state.ilog(f"updatnut tradeList o profit", tradeData=json.loads(json.dumps(tradeData, default=json_serial)))
#test na maximalni profit/loss, pokud vypiname pak uz nedelame pripdany reverzal
if await self.stop_when_max_profit_loss() is False:
#pIF REVERSAL REQUIRED - reverse position is added to prescr.Trades with same signal name
#jen při celém FILLU
if data.event == TradeEvent.FILL and self.state.vars.requested_followup is not None:
if self.state.vars.requested_followup == Followup.REVERSE:
await self.add_followup(direction=TradeDirection.LONG, size=o.qty, signal_name=signal_name)
elif self.state.vars.requested_followup == Followup.ADD:
#zatim stejna SIZE
await self.add_followup(direction=TradeDirection.SHORT, size=o.qty, signal_name=signal_name)
else:
#zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano
for trade in self.state.vars.prescribedTrades:
if trade.id == self.state.vars.pending:
signal_name = trade.generated_by
#zapsat update profitu do tradeList
for tradeData in self.state.tradeList:
if tradeData.execution_id == data.execution_id:
setattr(tradeData, "signal_name", signal_name)
self.state.ilog(e="BUY: Jde o LONG nakuú nepocitame profit zatim")
#ic("vstupujeme do orderupdatebuy")
print(data)
#dostavame zde i celkové akutální množství - ukládáme
self.state.positions = data.position_qty
self.state.avgp, self.state.positions = self.state.interface.pos()
if o.status == OrderStatus.FILLED or o.status == OrderStatus.CANCELED:
#davame pryc pending
self.state.vars.pending = None
async def orderUpdateSell(self, data: TradeUpdate):
self.state.ilog(e="Příchozí SELL notif", msg=data.order.status, trade=json.loads(json.dumps(data, default=json_serial)))
#naklady vypocteme z prumerne ceny, kterou mame v pozicich
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
#jde o uzavření long pozice - počítáme PROFIT
if int(self.state.positions) > 0:
#PROFIT pocitame z TradeUpdate.price a TradeUpdate.qty - aktualne provedene mnozstvi a cena
#naklady vypocteme z prumerne ceny, kterou mame v pozicich
sold_amount = data.qty * data.price
#podle prumerne ceny, kolik stalo toto mnozstvi
avg_costs = float(self.state.avgp) * float(data.qty)
if avg_costs == 0:
self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.")
avg_costs = sold_amount
trade_profit = round((sold_amount - avg_costs),2)
self.state.profit += trade_profit
self.state.ilog(e=f"SELL notif - PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)}", msg=str(data.event), sold_amount=sold_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id))
#zapsat profit do prescr.trades
for trade in self.state.vars.prescribedTrades:
if trade.id == self.state.vars.pending:
trade.last_update = datetime.fromtimestamp(self.state.time).astimezone(zoneNY)
trade.profit += trade_profit
trade.profit_sum = self.state.profit
signal_name = trade.generated_by
if data.event == TradeEvent.FILL:
#zapsat update profitu do tradeList
for tradeData in self.state.tradeList:
if tradeData.execution_id == data.execution_id:
#pridat jako attribut, aby proslo i na LIVE a PAPPER, kde se bere TradeUpdate z Alpaca
setattr(tradeData, "profit", trade_profit)
setattr(tradeData, "profit_sum", self.state.profit)
setattr(tradeData, "signal_name", signal_name)
#self.state.ilog(f"updatnut tradeList o profi {str(tradeData)}")
if await self.stop_when_max_profit_loss() is False:
#IF REVERSAL REQUIRED - reverse position is added to prescr.Trades with same signal name
if data.event == TradeEvent.FILL and self.state.vars.requested_followup is not None:
if self.state.vars.requested_followup == Followup.REVERSE:
await self.add_followup(direction=TradeDirection.SHORT, size=data.order.qty, signal_name=signal_name)
elif self.state.vars.requested_followup == Followup.ADD:
#zatim stejna SIZE
await self.add_followup(direction=TradeDirection.LONG, size=data.order.qty, signal_name=signal_name)
else:
#zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano
for trade in self.state.vars.prescribedTrades:
if trade.id == self.state.vars.pending:
signal_name = trade.generated_by
#zapsat update profitu do tradeList
for tradeData in self.state.tradeList:
if tradeData.execution_id == data.execution_id:
setattr(tradeData, "signal_name", signal_name)
self.state.ilog(e="SELL: Jde o SHORT nepocitame profit zatim")
#update pozic, v trade update je i pocet zbylych pozic
old_avgp = self.state.avgp
old_pos = self.state.positions
self.state.positions = int(data.position_qty)
if int(data.position_qty) == 0:
self.state.avgp = 0
self.state.ilog(e="SELL notifikace "+str(data.order.status), msg="update pozic", old_avgp=old_avgp, old_pos=old_pos, avgp=self.state.avgp, pos=self.state.positions, orderid=str(data.order.id))
#self.state.avgp, self.state.positions = self.interface.pos()
if data.event == TradeEvent.FILL or data.event == TradeEvent.CANCELED:
print("Příchozí SELL notifikace - complete FILL nebo CANCEL", data.event)
self.state.vars.pending = None
a,p = self.interface.pos()
#pri chybe api nechavame puvodni hodnoty
if a != -1:
self.state.avgp, self.state.positions = a,p
else: self.state.ilog(e=f"Chyba pri dotažení self.interface.pos() {a}")
#ic(self.state.avgp, self.state.positions)
#this parent method is called by strategy just once before waiting for first data
def strat_init(self):
#ic("strat INI function")
#lets connect method overrides
self.state.buy = self.buy
self.state.sell = self.sell
self.init(self.state)
def call_next(self, item):
#MAIN INDICATORS
populate_all_indicators(item, self.state)
#pro přípravu dat next nevoláme
if self.mode == Mode.PREP:
return
else:
self.next(item, self.state)
#overidden methods
# pouziva se pri vstupu long nebo exitu short
# osetrit uzavreni s vice nez mam
def buy(self, size = None, repeat: bool = False):
print("overriden buy method")
if size is None:
sizer = self.state.vars.chunk
else:
sizer = size
#jde o uzavreni short pozice
if int(self.state.positions) < 0 and (int(self.state.positions) + int(sizer)) > 0:
self.state.ilog(e="buy nelze nakoupit vic nez shortuji", positions=self.state.positions, size=size)
print("buy nelze nakoupit vic nez shortuji")
return -2
if int(self.state.positions) >= self.state.vars.maxpozic:
self.state.ilog(e="buy Maxim mnozstvi naplneno", positions=self.state.positions)
print("max mnostvi naplneno")
return 0
self.state.blockbuy = 1
self.state.vars.lastbuyindex = self.state.bars['index'][-1]
#self.state.ilog(e="send MARKET buy to if", msg="S:"+str(size), ltp=self.state.interface.get_last_price(self.state.symbol))
self.state.ilog(e="send MARKET buy to if", msg="S:"+str(size), ltp=self.state.bars['close'][-1])
return self.state.interface.buy(size=sizer)
#overidden methods
# pouziva se pri vstupu short nebo exitu long
def sell(self, size = None, repeat: bool = False):
print("overriden sell method")
if size is None:
size = abs(int(self.state.positions))
#jde o uzavreni long pozice
if int(self.state.positions) > 0 and (int(self.state.positions) - int(size)) < 0:
self.state.ilog(e="nelze prodat vic nez longuji", positions=self.state.positions, size=size)
print("nelze prodat vic nez longuji")
return -2
#pokud shortuji a mam max pozic
if int(self.state.positions) < 0 and abs(int(self.state.positions)) >= self.state.vars.maxpozic:
self.state.ilog(e="short - Maxim mnozstvi naplneno", positions=self.state.positions, size=size)
print("max mnostvi naplneno")
return 0
#self.state.blocksell = 1
#self.state.vars.lastbuyindex = self.state.bars['index'][-1]
#self.state.ilog(e="send MARKET SELL to if", msg="S:"+str(size), ltp=self.state.interface.get_last_price(self.state.symbol))
self.state.ilog(e="send MARKET SELL to if", msg="S:"+str(size), ltp=self.state.bars['close'][-1])
return self.state.interface.sell(size=size)