happydasch / btoandav20

Support for Oanda-V20 API in backtrader
Apache License 2.0
130 stars 52 forks source link

Nothing happening #39

Closed codeguru1983 closed 4 years ago

codeguru1983 commented 4 years ago

Hi guys, First of all I would like to thank you for the hard work you've put into this project, it's simply amazing looking to all that it can do...

Unfortunatelly I'm not yet able to use it properly, after a few days spent to create the environment for it and formatting my ubuntu dozens of times :( yes I'm a rookie in python, ubuntu everything but not on trading... I think I'm very close to make it work but can't figure out what's wrong.

First when I launch the test I do it this way: python oandav20test.py --account '100-000-00000000-000' --token 'e8f4f3daabffa01528964db356252481-cefd9e110c1242d3f*************' --data0 EUR_GBP --resample --timeframe Minutes --compression 1 --no-backfill_start --stake 100 --trade

I'm not sure if I do it right, I just followed your forum. Now, it seems that's working but it's not working properly:

So I get all these results:

--------------------------------------------------
Strategy Created
--------------------------------------------------
-- Contract Details:
{'minimumTradeSize': '1.0', 'displayName': u'EUR/GBP', 'name': u'EUR_GBP', 'displayPrecision': 5, 'maximumTrailingStopDistance': '1.0', 'minimumTrailingStopDistance': '0.0005', 'marginRate': '0.0333', 'tradeUnitsPrecision': 0, 'pipLocation': -4, 'maximumOrderUnits': '100000000.0', 'maximumPositionSize': '0.0', 'type': u'CURRENCY'}
Datetime, Open, High, Low, Close, Volume, OpenInterest, SMA
***** DATA NOTIF: LIVE
Data0, 0001, 737406.893056, 2019-12-13T21:26:00.000000, 0.833520, 0.833590, 0.833520, 0.833590,      0, 0, nan
-------------------------------------------------- ORDER BEGIN 2019-12-13 23:27:00.120499
Ref: 1
OrdType: 0
OrdType: Buy
Status: 1
Status: Submitted
Size: 100
Price: 0.82859
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 0
ExecType: Market
CommInfo: None
End of Session: 737407.0
Info: AutoOrderedDict()
Broker: None
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2019-12-13 23:27:00.121684
Ref: 1
OrdType: 0
OrdType: Buy
Status: 2
Status: Accepted
Size: 100
Price: 0.82859
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 0
ExecType: Market
CommInfo: None
End of Session: 737407.0
Info: AutoOrderedDict()
Broker: None
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2019-12-13 23:27:00.122730
Ref: 1
OrdType: 0
OrdType: Buy
Status: 4
Status: Completed
Size: 100
Price: 0.82859
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 0
ExecType: Market
CommInfo: <backtrader.comminfo.CommInfoBase object at 0x7fb102d24a90>
End of Session: 737407.0
Info: AutoOrderedDict()
Broker: None
Alive: False
-------------------------------------------------- ORDER END
-------------------------------------------------- TRADE BEGIN 2019-12-13 23:27:00.123758
ref:1
data:<btoandav20.feeds.oandav20feed.OandaV20Data object at 0x7fb100e74e50>
tradeid:0
size:100
price:0.83357
value:83.357
commission:0.0
pnl:0.0
pnlcomm:0.0
justopened:True
isopen:True
isclosed:False
baropen:2
dtopen:737406.89375
barclose:0
dtclose:0.0
barlen:0
historyon:False
history:[]
status:1
-------------------------------------------------- TRADE END
Data0, 0002, 737406.893750, 2019-12-13T21:27:00.000000, 0.833570, 0.833570, 0.833570, 0.833570,      0, 0, nan
-------------------------------------------------- ORDER BEGIN 2019-12-13 23:28:00.124779
Ref: 2
OrdType: 1
OrdType: Sell
Status: 1
Status: Submitted
Size: -50
Price: 0.83357
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 0
ExecType: Market
CommInfo: None
End of Session: 737407.0
Info: AutoOrderedDict()
Broker: None
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2019-12-13 23:28:00.125943
Ref: 2
OrdType: 1
OrdType: Sell
Status: 2
Status: Accepted
Size: -50
Price: 0.83357
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 0
ExecType: Market
CommInfo: None
End of Session: 737407.0
Info: AutoOrderedDict()
Broker: None
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2019-12-13 23:28:00.127005
Ref: 2
OrdType: 1
OrdType: Sell
Status: 4
Status: Completed
Size: -50
Price: 0.83357
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 0
ExecType: Market
CommInfo: <backtrader.comminfo.CommInfoBase object at 0x7fb102d24a90>
End of Session: 737407.0
Info: AutoOrderedDict()
Broker: None
Alive: False
-------------------------------------------------- ORDER END
Data0, 0003, 737406.894444, 2019-12-13T21:28:00.000000, 0.833540, 0.833750, 0.833540, 0.833750,      0, 0, nan
-------------------------------------------------- ORDER BEGIN 2019-12-13 23:29:00.321794
Ref: 3
OrdType: 1
OrdType: Sell
Status: 1
Status: Submitted
Size: -50
Price: 0.83375
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 0
ExecType: Market
CommInfo: None
End of Session: 737407.0
Info: AutoOrderedDict()
Broker: None
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2019-12-13 23:29:00.326125
Ref: 3
OrdType: 1
OrdType: Sell
Status: 2
Status: Accepted
Size: -50
Price: 0.83375
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 0
ExecType: Market
CommInfo: None
End of Session: 737407.0
Info: AutoOrderedDict()
Broker: None
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2019-12-13 23:29:00.327720
Ref: 3
OrdType: 1
OrdType: Sell
Status: 4
Status: Completed
Size: -50
Price: 0.83375
Price Limit: None
TrailAmount: None
TrailPercent: None
ExecType: 0
ExecType: Market
CommInfo: <backtrader.comminfo.CommInfoBase object at 0x7fb102d24a90>
End of Session: 737407.0
Info: AutoOrderedDict()
Broker: None
Alive: False
-------------------------------------------------- ORDER END
-------------------------------------------------- TRADE BEGIN 2019-12-13 23:29:00.328797
ref:1
data:<btoandav20.feeds.oandav20feed.OandaV20Data object at 0x7fb100e74e50>
tradeid:0
size:0
price:0.83357
value:0.0
commission:0.0
pnl:0.007
pnlcomm:0.007
justopened:False
isopen:False
isclosed:True
baropen:2
dtopen:737406.89375
barclose:4
dtclose:737406.895139
barlen:2
historyon:False
history:[]
status:2
-------------------------------------------------- TRADE END
Data0, 0004, 737406.895139, 2019-12-13T21:29:00.000000, 0.833740, 0.833740, 0.833730, 0.833730,      0, 0, nan
Data0, 0005, 737406.895833, 2019-12-13T21:30:00.000000, 0.833650, 0.833650, 0.833550, 0.833550,      0, 0, 0.833638
Data0, 0006, 737406.896528, 2019-12-13T21:31:00.000000, 0.833600, 0.833630, 0.833540, 0.833550,      0, 0, 0.833630
Data0, 0007, 737406.897222, 2019-12-13T21:32:00.000000, 0.833590, 0.833620, 0.833560, 0.833620,      0, 0, 0.833640
Data0, 0008, 737406.897917, 2019-12-13T21:33:00.000000, 0.833590, 0.833590, 0.833590, 0.833590,      0, 0, 0.833608
Data0, 0009, 737406.898611, 2019-12-13T21:34:00.000000, 0.833620, 0.833620, 0.833440, 0.833440,      0, 0, 0.833550
Data0, 0010, 737406.900000, 2019-12-13T21:36:00.000000, 0.833430, 0.833430, 0.833370, 0.833390,      0, 0, 0.833518
Data0, 0011, 737406.900694, 2019-12-13T21:37:00.000000, 0.833500, 0.833540, 0.833500, 0.833540,      0, 0, 0.833516
Data0, 0012, 737406.901389, 2019-12-13T21:38:00.000000, 0.833520, 0.833520, 0.833520, 0.833520,      0, 0, 0.833496

But nothing shows either in MT4, either in the dashboard of Oanda... I'm using a demo account, it has some demo funds in it I think it should be ok? I was looking into the files and I'm not sure if here practice should be True or False... for the demo account...

params = (
        ('token', 'e8f4xxxxx'),
        ('account', '10xxxxxxxxxxxx'),
        **('practice', False),**
        ('account_poll_freq', 10.0),  # account balance refresh timeout
        ('stream_timeout', 10),
        ('poll_timeout', 2),

I'm not sure so if somebody can tell me please what I'm doing wrong to finally get this up and running will be great!

Thank you again for all your work, very good looking system and finally something decent which anyone can use, almost.

codeguru1983 commented 4 years ago

I think now it's working, I can see some transactions. I've used this: python oandav20test.py --account '10xxxxxxxxxxxxxx2' --token 'e8f4f3daabffa015xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx9' --data0 EUR_GBP --resample --timeframe Minutes --compression 1 --no-backfill_start --stake 1000 --trade --broker

however, I still don't think it's ok :) Screenshot from 2019-12-14 00-06-20

codeguru1983 commented 4 years ago

I think I've realised that the market might just closed? :))) I'm not just tired, i'm silly as well...

happydasch commented 4 years ago

Hi, yeah, the market is closed atm, so no trades will go through. If you don't get any auth errors, then the credentials work. You also got data already, so just wait for the market to open.

happydasch commented 4 years ago

btw. You can still use backfilling and backtesting

happydasch commented 4 years ago

@codeguru1983 here are some example strategies I wrote once for testing, maybe this may help you a bit to get into backtrader. You need to change the data source (try using oanda backfill from), but this will work mostly.

example.zip

happydasch commented 4 years ago

@codeguru1983 also your trade sizes are very small so the amount will be near 0 for this trades

codeguru1983 commented 4 years ago

Hi, It seems that I can't find where to change the size of the trade can you give me a hint on that please? Thank you!

On Sat, 14 Dec 2019 at 00:41, happydasch notifications@github.com wrote:

@codeguru1983 https://github.com/codeguru1983 also your trade sizes are very small so the amount will be near 0 for this trades

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ftomassetti/backtrader-oandav20/issues/39?email_source=notifications&email_token=AOBDLEIRMWXAHTQGVNY6PE3QYQFSLA5CNFSM4J2WPLM2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEG3O2LA#issuecomment-565636396, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOBDLENIAP7D5RRVGOAQE7DQYQFSLANCNFSM4J2WPLMQ .

happydasch commented 4 years ago

@codeguru1983 check out some websites about this. for example

https://www.thebalance.com/how-to-determine-proper-position-size-when-forex-trading-1031023

also check out the available sizer:

https://github.com/ftomassetti/backtrader-oandav20/blob/master/btoandav20/sizers/oandav20sizer.py

happydasch commented 4 years ago

for backtrader itself: https://www.backtrader.com/docu/sizers-reference/ https://www.backtrader.com/docu/sizers/sizers/

happydasch commented 4 years ago

you would set a sizer this way

cerebro.addsizer( bt.sizers.PercentSizer, percents=10)

OandaV20Percent - returns position size which matches the percent amount of total cash OandaV20Cash - return position size which matches the cash amount OandaV20RiskPercent - returns position size which matches the total risk in percent of total amount (max stop loss) OandaV20RiskCash - returns position size which matches the total risk in percent of total amount (max stop loss)

codeguru1983 commented 4 years ago

Hi, Thank you for your reply, however the parameters are a bit complicated to be honest.

First of all I can't get rid of that 500 stuff... and then the trailing take profit stop loss I can't find the exact spot where are mentioned... sorry to be a pain, the backtrade docs looks ok, until you start to search in the actual code...

If I run this: python oandav20test.py --account '101-xxxxxxxxx' --token 'e8f4f3dxxxxxxxxxxxxx087d35f69' --data0 EUR_USD --resample --timeframe Minutes --compression 1 --no-backfill_start --stake 1000000 --trade --broker

Indeed is increase the units to 500.000 but I don't get it how it calculates it, from which exact part of the code, then when he will reopen a position he will do it again with 500 :(

Sorry to be a pain, I'm just new in all of this python wonderful story.

Thank you!

On Sun, 15 Dec 2019 at 13:54, happydasch notifications@github.com wrote:

you would set a sizer this way

cerebro.addsizer( bt.sizers.PercentSizer, percents=10)

OandaV20Percent - returns position size which matches the percent amount of total cash OandaV20Cash - return position size which matches the cash amount OandaV20RiskPercent - returns position size which matches the total risk in percent of total amount (max stop loss) OandaV20RiskCash - returns position size which matches the total risk in percent of total amount (max stop loss)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ftomassetti/backtrader-oandav20/issues/39?email_source=notifications&email_token=AOBDLEMZ3ZFMHJPMHQGO5J3QYYLHFA5CNFSM4J2WPLM2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEG4XNLI#issuecomment-565802669, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOBDLEIWXJJWMOCZTEDCKZLQYYLHFANCNFSM4J2WPLMQ .

happydasch commented 4 years ago

https://github.com/ftomassetti/backtrader-oandav20/blob/621ce2c7b1f056246f74301006ec2f5c496f8b62/examples/oandav20test/oandav20test.py#L149

its done here:

if not self.p.sell: self.order = self.sell(size=self.p.stake // 2, exectype=bt.Order.Market, price=self.data0.close[0]) else: self.order = self.buy(size=self.p.stake // 2, exectype=bt.Order.Market, price=self.data0.close[0])

happydasch commented 4 years ago

you would just need to know what your position size has to be, it depends on your strategy or the way you calculate your risk and position size. if you have a fixed size, then just provide the fixed size in code.

codeguru1983 commented 4 years ago

Hi

Finally is working but it doesn't take profits out it close everything on zero even when its extremely profitable it doesnt close... when it refers to +0.20 price ...is not working

-------------------------------------------------- ORDER BEGIN 2019-12-17 21:17:00.150925 Ref: 227 OrdType: 1 OrdType: Sell Status: 2 Status: Accepted Size: -500 Price: 143.588 Price Limit: None TrailAmount: None TrailPercent: None ExecType: 0 ExecType: Market CommInfo: <backtrader.comminfo.CommInfoBase object at 0x7f2685c533d0> End of Session: 737410.9999999999 Info: AutoOrderedDict() Broker: None Alive: True

but I have: else: print('USING BRACKET') price = self.data0.close[0] - 0.05 self.order, , = self.buy_bracket(size=self.p.stake,

exectype=bt.Order.Market, price=price, stopprice=price - 0.10, limitprice=price + 0.10, valid=self.p.valid)

Thank you very much for your help on this, I think is a great too even if it doesn't close by itself :) today with a demo account of $1000 with a leverage of 30:1 it went to $1095.94 which is not bad...

Thanks!

On Mon, 16 Dec 2019, 10:37 happydasch, notifications@github.com wrote:

you would just need to know what your position size has to be, it depends on your strategy or the way you calculate your risk and position size. if you have a fixed size, then just provide the fixed size in code.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ftomassetti/backtrader-oandav20/issues/39?email_source=notifications&email_token=AOBDLEOVNKBWFJ57JU7WAF3QY446PA5CNFSM4J2WPLM2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEG55ZRY#issuecomment-565959879, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOBDLEPYQWF2JAE2EL7OGXLQY446PANCNFSM4J2WPLMQ .

codeguru1983 commented 4 years ago

I'm trading on GBP_JPY with stake 1000, leverage 30:1 and this is the code I have, can't figure out how to make it sell on 80 pips or a profit of say 5%, or after price goes up with 0.005 or sort of thing

if self.datastatus and not self.position and len(self.orderid) < 1:
            if not self.p.usebracket:
                if not self.p.sell:
                    # price = round(self.data0.close[0] * 0.90, 2)
                    price = self.data0.close[0] - 0.005
                    self.order = self.buy(size=self.p.stake,
                                          exectype=self.p.exectype,
                                          price=price,
                                          valid=self.p.valid)
                else:
                    # price = round(self.data0.close[0] * 1.10, 4)
                    price = self.data0.close[0] - 0.05
                    self.order = self.sell(size=self.p.stake,
                                           exectype=self.p.exectype,
                                           price=price,
                                           valid=self.p.valid)

            else:
                print('USING BRACKET')
                price = self.data0.close[0] - 0.05
                self.order, _, _ = self.buy_bracket(size=self.p.stake,
                                                    exectype=bt.Order.Market,
                                                    price=price,
                                                    stopprice=price - 0.10,
                                                    limitprice=price + 0.10,
                                                    valid=self.p.valid)

            self.orderid.append(self.order)
        elif self.position and not self.p.donotcounter:
            if self.order is None:
                if not self.p.sell:
                    self.order = self.sell(size=self.p.stake // 2,
                                           exectype=bt.Order.Market,
                                           price=self.data0.close[0])
                else:
                    self.order = self.buy(size=self.p.stake // 2,
                                          exectype=bt.Order.Market,
                                          price=self.data0.close[0])

            self.orderid.append(self.order)

        elif self.order is not None and self.p.cancel:
            if self.datastatus > self.p.cancel:
                self.cancel(self.order)

thank you very much

happydasch commented 4 years ago

did the bracket order create the take profit order in oanda? or what exactly went wrong with closing of the position?

happydasch commented 4 years ago

for the pips and pip position you can also check data.contractdetails, there following informations should be available:

https://developer.oanda.com/rest-live-v20/primitives-df/ Instrument

# 
# The name of the Instrument
# 
name : (InstrumentName),

# 
# The type of the Instrument
# 
type : (InstrumentType),

# 
# The display name of the Instrument
# 
displayName : (string),

# 
# The location of the “pip” for this instrument. The decimal position of
# the pip in this Instrument’s price can be found at 10 ^ pipLocation (e.g.
# -4 pipLocation results in a decimal pip position of 10 ^ -4 = 0.0001).
# 
pipLocation : (integer),

# 
# The number of decimal places that should be used to display prices for
# this instrument. (e.g. a displayPrecision of 5 would result in a price of
# “1” being displayed as “1.00000”)
# 
displayPrecision : (integer),

# 
# The amount of decimal places that may be provided when specifying the
# number of units traded for this instrument.
# 
tradeUnitsPrecision : (integer),

# 
# The smallest number of units allowed to be traded for this instrument.
# 
minimumTradeSize : (DecimalNumber),

# 
# The maximum trailing stop distance allowed for a trailing stop loss
# created for this instrument. Specified in price units.
# 
maximumTrailingStopDistance : (DecimalNumber),

# 
# The minimum trailing stop distance allowed for a trailing stop loss
# created for this instrument. Specified in price units.
# 
minimumTrailingStopDistance : (DecimalNumber),

# 
# The maximum position size allowed for this instrument. Specified in
# units.
# 
maximumPositionSize : (DecimalNumber),

# 
# The maximum units allowed for an Order placed for this instrument.
# Specified in units.
# 
maximumOrderUnits : (DecimalNumber),

# 
# The margin rate for this instrument.
# 
marginRate : (DecimalNumber),

# 
# The commission structure for this instrument.
# 
commission : (InstrumentCommission),

# 
# The tags associated with this instrument.
# 
tags : (Array[Tag])
happydasch commented 4 years ago

how to make it sell on 80 pips or a profit of say 5%, or after price goes up with 0.005 or sort of thing

for 80 pips, for this you need to know the pip location (see data.contractdetails)

for 5%, you just calculate the 5% (but there are not that high movements, I believe you mean 0.5%) -> Long: price + price 0.005, Short: price - price 0.005

for 0.005, Long: price + 0.005, Short: price - 0.005

codeguru1983 commented 4 years ago

Screenshot_20191217-191030_fxTrade Screenshot_20191217-184620_fxTrade Screenshot_20191217-192329_fxTrade These were opened I had like 40 50 of them, but the closed ones are all with zero

Screenshot_20191217-192403_fxTrade Screenshot_20191217-192339_fxTrade Screenshot_20191217-192353_fxTrade

I'm sure it's just a matter of settings in the code as the default strategy is very good so I don't figure out how to stop trail on loss and earnings.

Thank you very much for all your support again

happydasch commented 4 years ago

you should check the orders created by your code (check the orders tab or output of your code), when a order is created with tp and sl then the sl and tp order would show up in oanda.

Also be careful with your strategies :)

Cheers

codeguru1983 commented 4 years ago

So from strategy and everything point of view it went to up of about 113$ in like 45 minutes or so...I still have them opened now haven't closed them manually, I don't know if I manage to get sorted the code behind if it will do it automaticalky, I haven't closed the terminal session. Screenshot_20191217-215330_fxTrade

happydasch commented 4 years ago

Check your orders:

the order id is this number

Bildschirmfoto 2019-12-17 um 20 54 54

they will match with your terminal output. these were executed, but there should be other orders, which are not et filled. So your issue will mostly be a wrong price for tp and sl. You will find them in the orders tab or in your terminal output.

codeguru1983 commented 4 years ago

This is how it looks like in the console 15766126245742897196368491183258 It's not a rocket science strategy but it's very effective :)

codeguru1983 commented 4 years ago

Will it be from here? Screenshot from 2019-12-17 22-07-04

I mean for example on long you have 143.765000 when you open, let's suppose it goes up to 143.790000, so it's after him increasing the take profit stop, if the price drops to let's say 143.785000 it should sell automatically, so this is what I can't get my head around :) sorry for my poor english

codeguru1983 commented 4 years ago

so I think he needs to stop at let's say -0.1%, and take profit at 0.2%...any idea please?

happydasch commented 4 years ago

stopprice = price - price 0.001 limitprice = price + price 0.002

(I believe, but did not test it out)

codeguru1983 commented 4 years ago

I'll test that, thank you very much!

codeguru1983 commented 4 years ago

id the bracket order create the take profit order in oanda? or what exactly went wrong with closing of the position?

it doesn't close yet, when it opens the order there's no S/L, T/P, T/S, I've tried with the oandav20test.py same problem, so I'm not sure in the end where should I check, on my price settings it sais:

else:
                print('USING BRACKET')
                price = self.data0.close[0] - 0.05
                self.order, _, _ = self.buy_bracket(size=self.p.stake,
                                                    exectype=bt.Order.Market,
                                                    price=price,
                                                    stopprice=price - price * 0.0004,
                                                    limitprice=price + price * 0.0005,
                                                    valid=self.p.valid)
codeguru1983 commented 4 years ago

I have 3 positions one at -0.16% and 2 at +0.13 %... nothing happens anyway, it doesn't setting up in the placing order part

codeguru1983 commented 4 years ago

I think in the end exectype should be different: exectype=bt.Order.StopTrail, trailpercent=0.02,

I've tried to change it everywhere but it's not working based on the original file you provide, to use instead of Market, StopTrail, just gives a bunch of errors...

codeguru1983 commented 4 years ago

This is what I have, It never place S/L, T/P or T/S in Oanda, i've made some research and I could find on backtrader community forum an info that it should look like this:


# For a StopTrail going downwards with 2% distance
self.buy(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)  # last price will be used as reference
# or
self.buy(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.0.02)

# For a StopTrail going upwards with 2% difference
self.sell(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)  # last price will be used as reference
# or
self.sell(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.02)

I'm a bit stucked so if you can please help me by having a look would be great...


#!/usr/bin/env python

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime

import backtrader as bt
from backtrader.utils import flushfile  # win32 quick stdout flushing

import btoandav20

StoreCls = btoandav20.stores.OandaV20Store
DataCls = btoandav20.feeds.OandaV20Data
BrokerCls = btoandav20.brokers.OandaV20Broker

# available timeframes for oanda
TIMEFRAMES = [bt.TimeFrame.Names[bt.TimeFrame.Seconds],
         bt.TimeFrame.Names[bt.TimeFrame.Minutes],
         bt.TimeFrame.Names[bt.TimeFrame.Days],
         bt.TimeFrame.Names[bt.TimeFrame.Weeks],
         bt.TimeFrame.Names[bt.TimeFrame.Months]]

class TestStrategy(bt.Strategy):
    params = dict(
        smaperiod=5,
        trade=False,
        stake=10,
        exectype=bt.Order.Market,
        stopafter=0,
        valid=None,
        cancel=0,
        donotcounter=False,
        sell=False,
        usebracket=False,
    )

    def __init__(self):
        # To control operation entries
        self.orderid = list()
        self.order = None

        self.counttostop = 0
        self.datastatus = 0

        # Create SMA on 2nd data
        self.sma = bt.indicators.MovAv.SMA(self.data, period=self.p.smaperiod)

        print('--------------------------------------------------')
        print('Strategy Created')
        print('--------------------------------------------------')

    def notify_data(self, data, status, *args, **kwargs):
        print('*' * 5, 'DATA NOTIF:', data._getstatusname(status), *args)
        if status == data.LIVE:
            self.counttostop = self.p.stopafter
            self.datastatus = 1

    def notify_store(self, msg, *args, **kwargs):
        print('*' * 5, 'STORE NOTIF:', msg)

    def notify_order(self, order):
        if order.status in [order.Completed, order.Cancelled, order.Rejected]:
            self.order = None

        print('-' * 50, 'ORDER BEGIN', datetime.datetime.now())
        print(order)
        print('-' * 50, 'ORDER END')

    def notify_trade(self, trade):
        print('-' * 50, 'TRADE BEGIN', datetime.datetime.now())
        print(trade)
        print('-' * 50, 'TRADE END')

    def prenext(self):
        self.next(frompre=True)

    def next(self, frompre=False):
        txt = list()
        txt.append('Data0')
        txt.append('%04d' % len(self.data0))
        dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
        txt.append('{:f}'.format(self.data.datetime[0]))
        txt.append('%s' % self.data.datetime.datetime(0).strftime(dtfmt))
        txt.append('{:f}'.format(self.data.open[0]))
        txt.append('{:f}'.format(self.data.high[0]))
        txt.append('{:f}'.format(self.data.low[0]))
        txt.append('{:f}'.format(self.data.close[0]))
        txt.append('{:6d}'.format(int(self.data.volume[0])))
        txt.append('{:d}'.format(int(self.data.openinterest[0])))
        txt.append('{:f}'.format(self.sma[0]))
        print(', '.join(txt))

        if len(self.datas) > 1 and len(self.data1):
            txt = list()
            txt.append('Data1')
            txt.append('%04d' % len(self.data1))
            dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
            txt.append('{}'.format(self.data1.datetime[0]))
            txt.append('%s' % self.data1.datetime.datetime(0).strftime(dtfmt))
            txt.append('{}'.format(self.data1.open[0]))
            txt.append('{}'.format(self.data1.high[0]))
            txt.append('{}'.format(self.data1.low[0]))
            txt.append('{}'.format(self.data1.close[0]))
            txt.append('{}'.format(self.data1.volume[0]))
            txt.append('{}'.format(self.data1.openinterest[0]))
            txt.append('{}'.format(float('NaN')))
            print(', '.join(txt))

        if self.counttostop:  # stop after x live lines
            self.counttostop -= 1
            if not self.counttostop:
                self.env.runstop()
                return

        if not self.p.trade:
            return

        if self.datastatus and not self.position and len(self.orderid) < 1:
            if not self.p.usebracket:
                if not self.p.sell:
                    # price = round(self.data0.close[0] * 0.90, 2)
                    price = self.data0.close[0] - 0.005
                    self.order = self.buy(size=self.p.stake,
                                          exectype=self.p.exectype,
                                          price=price,
                                          valid=self.p.valid)
                else:
                    # price = round(self.data0.close[0] * 1.10, 4)
                    price = self.data0.close[0] - 0.05
                    self.order = self.sell(size=self.p.stake,
                                           exectype=self.p.exectype,
                                           price=price,
                                           valid=self.p.valid)

            else:
                print('USING BRACKET')
                price = self.data0.close[0] - 0.05
                self.order, _, _ = self.buy_bracket(size=self.p.stake,
                                                    exectype=bt.Order.Market,
                                                    valid=self.p.valid)

            self.orderid.append(self.order)
        elif self.position and not self.p.donotcounter:
            if self.order is None:
                if not self.p.sell:
                    self.order = self.sell(size=self.p.stake // 2,
                                           exectype=bt.Order.Market,
                                           price=self.data0.close[0])
                else:
                    self.order = self.buy(size=self.p.stake // 2,
                                          exectype=bt.Order.Market,
                                          price=self.data0.close[0])

            self.orderid.append(self.order)

        elif self.order is not None and self.p.cancel:
            if self.datastatus > self.p.cancel:
                self.cancel(self.order)

        if self.datastatus:
            self.datastatus += 1

    def start(self):
        if self.data0.contractdetails is not None:
            print('-- Contract Details:')
            print(self.data0.contractdetails)

        header = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume',
                  'OpenInterest', 'SMA']
        print(', '.join(header))

        self.done = False

def runstrategy():
    args = parse_args()

    # Create a cerebro
    cerebro = bt.Cerebro()

    storekwargs = dict(
        token=args.token,
        account=args.account,
        practice=not args.live
    )

    if not args.no_store:
        store = StoreCls(**storekwargs)

    if args.broker:
        if args.no_store:
            broker = BrokerCls(**storekwargs)
        else:
            broker = store.getbroker()

        cerebro.setbroker(broker)

    timeframe = bt.TimeFrame.TFrame(args.timeframe)
    # Manage data1 parameters
    tf1 = args.timeframe1
    tf1 = bt.TimeFrame.TFrame(tf1) if tf1 is not None else timeframe
    cp1 = args.compression1
    cp1 = cp1 if cp1 is not None else args.compression

    if args.resample or args.replay:
        datatf = datatf1 = bt.TimeFrame.Ticks
        datacomp = datacomp1 = 1
    else:
        datatf = timeframe
        datacomp = args.compression
        datatf1 = tf1
        datacomp1 = cp1

    fromdate = None
    if args.fromdate:
        dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.fromdate))
        fromdate = datetime.datetime.strptime(args.fromdate, dtformat)

    DataFactory = DataCls if args.no_store else store.getdata

    datakwargs = dict(
        timeframe=datatf, compression=datacomp,
        qcheck=args.qcheck,
        historical=args.historical,
        fromdate=fromdate,
        bidask=args.bidask,
        useask=args.useask,
        backfill_start=not args.no_backfill_start,
        backfill=not args.no_backfill,
        tz=args.timezone
    )

    if args.no_store and not args.broker:   # neither store nor broker
        datakwargs.update(storekwargs)  # pass the store args over the data

    data0 = DataFactory(dataname=args.data0, **datakwargs)

    data1 = None
    if args.data1 is not None:
        if args.data1 != args.data0:
            datakwargs['timeframe'] = datatf1
            datakwargs['compression'] = datacomp1
            data1 = DataFactory(dataname=args.data1, **datakwargs)
        else:
            data1 = data0

    rekwargs = dict(
        timeframe=timeframe, compression=args.compression,
        bar2edge=not args.no_bar2edge,
        adjbartime=not args.no_adjbartime,
        rightedge=not args.no_rightedge,
        takelate=not args.no_takelate,
    )

    if args.replay:
        cerebro.replaydata(data0, **rekwargs)

        if data1 is not None:
            rekwargs['timeframe'] = tf1
            rekwargs['compression'] = cp1
            cerebro.replaydata(data1, **rekwargs)

    elif args.resample:
        cerebro.resampledata(data0, **rekwargs)

        if data1 is not None:
            rekwargs['timeframe'] = tf1
            rekwargs['compression'] = cp1
            cerebro.resampledata(data1, **rekwargs)

    else:
        cerebro.adddata(data0)
        if data1 is not None:
            cerebro.adddata(data1)

    if args.valid is None:
        valid = None
    else:
        valid = datetime.timedelta(seconds=args.valid)
    # Add the strategy
    cerebro.addstrategy(TestStrategy,
                        smaperiod=args.smaperiod,
                        trade=args.trade,
                        exectype=bt.Order.ExecType(args.exectype),
                        stake=args.stake,
                        stopafter=args.stopafter,
                        valid=valid,
                        cancel=args.cancel,
                        donotcounter=args.donotcounter,
                        sell=args.sell,
                        usebracket=args.usebracket)

    # Live data ... avoid long data accumulation by switching to "exactbars"
    cerebro.run(exactbars=args.exactbars)
    if args.exactbars < 1:  # plotting is possible
        if args.plot:
            pkwargs = dict(style='line')
            if args.plot is not True:  # evals to True but is not True
                npkwargs = eval('dict(' + args.plot + ')')  # args were passed
                pkwargs.update(npkwargs)

            cerebro.plot(**pkwargs)

def parse_args(pargs=None):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Test Oanda v20 integration')

    parser.add_argument('--exactbars', default=1, type=int,
                        required=False, action='store',
                        help='exactbars level, use 0/-1/-2 to enable plotting')

    parser.add_argument('--stopafter', default=0, type=int,
                        required=False, action='store',
                        help='Stop after x lines of LIVE data')

    parser.add_argument('--no-store',
                        required=False, action='store_true',
                        help='Do not use the store pattern')

    parser.add_argument('--debug',
                        required=False, action='store_true',
                        help='Display all info received from source')

    parser.add_argument('--token', default=None,
                        required=True, action='store',
                        help='Access token to use')

    parser.add_argument('--account', default=None,
                        required=True, action='store',
                        help='Account identifier to use')

    parser.add_argument('--live', default=None,
                        required=False, action='store',
                        help='Go to live server rather than practice')

    parser.add_argument('--qcheck', default=0.5, type=float,
                        required=False, action='store',
                        help=('Timeout for periodic '
                              'notification/resampling/replaying check'))

    parser.add_argument('--data0', default=None,
                        required=True, action='store',
                        help='data 0 into the system')

    parser.add_argument('--data1', default=None,
                        required=False, action='store',
                        help='data 1 into the system')

    parser.add_argument('--timezone', default=None,
                        required=False, action='store',
                        help='timezone to get time output into (pytz names)')

    parser.add_argument('--bidask', default=None,
                        required=False, action='store_true',
                        help='Use bidask ... if False use midpoint')

    parser.add_argument('--useask', default=None,
                        required=False, action='store_true',
                        help='Use the "ask" of bidask prices/streaming')

    parser.add_argument('--no-backfill_start',
                        required=False, action='store_true',
                        help='Disable backfilling at the start')

    parser.add_argument('--no-backfill',
                        required=False, action='store_true',
                        help='Disable backfilling after a disconnection')

    parser.add_argument('--historical',
                        required=False, action='store_true',
                        help='do only historical download')

    parser.add_argument('--fromdate',
                        required=False, action='store',
                        help=('Starting date for historical download '
                              'with format: YYYY-MM-DD[THH:MM:SS]'))

    parser.add_argument('--smaperiod', default=5, type=int,
                        required=False, action='store',
                        help='Period to apply to the Simple Moving Average')

    pgroup = parser.add_mutually_exclusive_group(required=False)

    pgroup.add_argument('--replay',
                        required=False, action='store_true',
                        help='replay to chosen timeframe')

    pgroup.add_argument('--resample',
                        required=False, action='store_true',
                        help='resample to chosen timeframe')

    parser.add_argument('--timeframe', default=TIMEFRAMES[0],
                        choices=TIMEFRAMES,
                        required=False, action='store',
                        help='TimeFrame for Resample/Replay')

    parser.add_argument('--compression', default=5, type=int,
                        required=False, action='store',
                        help='Compression for Resample/Replay')

    parser.add_argument('--timeframe1', default=None,
                        choices=TIMEFRAMES,
                        required=False, action='store',
                        help='TimeFrame for Resample/Replay - Data1')

    parser.add_argument('--compression1', default=None, type=int,
                        required=False, action='store',
                        help='Compression for Resample/Replay - Data1')

    parser.add_argument('--no-takelate',
                        required=False, action='store_true',
                        help=('resample/replay, do not accept late samples'))

    parser.add_argument('--no-bar2edge',
                        required=False, action='store_true',
                        help='no bar2edge for resample/replay')

    parser.add_argument('--no-adjbartime',
                        required=False, action='store_true',
                        help='no adjbartime for resample/replay')

    parser.add_argument('--no-rightedge',
                        required=False, action='store_true',
                        help='no rightedge for resample/replay')

    parser.add_argument('--broker',
                        required=False, action='store_true',
                        help='Use Oanda as broker')

    parser.add_argument('--trade',
                        required=False, action='store_true',
                        help='Do Sample Buy/Sell operations')

    parser.add_argument('--sell',
                        required=False, action='store_true',
                        help='Start by selling')

    parser.add_argument('--usebracket',
                        required=False, action='store_true',
                        help='Test buy_bracket')

    parser.add_argument('--donotcounter',
                        required=False, action='store_true',
                        help='Do not counter the 1st operation')

    parser.add_argument('--exectype', default=bt.Order.ExecTypes[0],
                        choices=bt.Order.ExecTypes,
                        required=False, action='store',
                        help='Execution to Use when opening position')

    parser.add_argument('--stake', default=10, type=int,
                        required=False, action='store',
                        help='Stake to use in buy operations')

    parser.add_argument('--valid', default=None, type=float,
                        required=False, action='store',
                        help='Seconds to keep the order alive (0 means DAY)')

    parser.add_argument('--cancel', default=0, type=int,
                        required=False, action='store',
                        help=('Cancel a buy order after n bars in operation,'
                              ' to be combined with orders like Limit'))

    # Plot options
    parser.add_argument('--plot', '-p', nargs='?', required=False,
                        metavar='kwargs', const=True,
                        help=('Plot the read data applying any kwargs passed\n'
                              '\n'
                              'For example (escape the quotes if needed):\n'
                              '\n'
                              '  --plot style="candle" (to plot candles)\n'))

    if pargs is not None:
        return parser.parse_args(pargs)

    return parser.parse_args()

if __name__ == '__main__':
    runstrategy()
codeguru1983 commented 4 years ago

For example on GBP_JPY there is an opening price for 143.277, I should have a stop loss at let's say -0.2%, and a trailing stop which will follow the current price in case the strategy is right, with let's say -0.05% new stop loss; they should work in both directions either for sell or buy... I think this makes it a bit more clear what I'm after... thank you again