Skip to content

all ¤

Import everything in one. Import some unnecessary but convenient for everyone

BackTestAccount ¤

BackTestAccount(
    risk: float = 0.02,
    balance: float = 10000,
    commission: float = 0.2,
    margin: float = 1,
    leverage: float = 1,
    **kwargs
)

Bases: Account

Parameters:

  • risk (float, default: 0.02 ) –

    description. Defaults to 0.02.

  • balance (float, default: 10000 ) –

    description. Defaults to 10_000.

  • commission (float, default: 0.2 ) –

    Commission fee is percent of size. Defaults to 0.2.

  • margin (float, default: 1 ) –

    description. Defaults to 1.

  • leverage (float, default: 1 ) –

    description. Defaults to 1.

  • **kwargs (dict, default: {} ) –
Source code in lettrade/exchange/backtest/account.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def __init__(
    self,
    risk: float = 0.02,
    balance: float = 10_000,
    commission: float = 0.2,
    margin: float = 1,
    leverage: float = 1,
    **kwargs,
) -> None:
    """Account for backtest

    Args:
        risk (float, optional): _description_. Defaults to 0.02.
        balance (float, optional): _description_. Defaults to 10_000.
        commission (float, optional): Commission fee is percent of size. Defaults to 0.2.
        margin (float, optional): _description_. Defaults to 1.
        leverage (float, optional): _description_. Defaults to 1.
        **kwargs (dict, optional): Mirror of [lettrade.account.Account()](site:/reference/account/account/#lettrade.account.account.Account).
    """
    super().__init__(
        risk=risk,
        balance=balance,
        margin=margin,
        leverage=leverage,
        **kwargs,
    )
    self._commission = commission

balance property ¤

balance: float

Balance of account

Returns:

  • float ( float ) –

    description

next ¤

next()

Next account

Source code in lettrade/account/account.py
73
74
def next(self):
    """Next account"""

next_next ¤

next_next()

Call after strategy.next()

Source code in lettrade/account/account.py
76
77
78
def next_next(self):
    """Call after strategy.next()"""
    self._equity_snapshot()

risk ¤

risk(side: TradeSide, size: float, **kwargs) -> float

Risk calculation

Source code in lettrade/account/account.py
87
88
89
90
91
def risk(self, side: "TradeSide", size: float, **kwargs) -> float:
    """Risk calculation"""
    if size is None:
        return side * abs(self._risk)
    return side * abs(size)

start ¤

start()

Start account

Source code in lettrade/account/account.py
70
71
def start(self):
    """Start account"""

stop ¤

stop()

Stop account

Source code in lettrade/account/account.py
80
81
82
83
84
85
def stop(self):
    """Stop account"""
    try:
        self._equity_snapshot()
    except LetAccountInsufficientException:
        pass

BackTestCommander ¤

Bases: Commander

BackTest Commander to debug commander notify

init ¤

init(
    bot: LetTradeBot,
    brain: Brain,
    exchange: Exchange,
    strategy: Strategy,
)

Init commander dependencies

Parameters:

Source code in lettrade/commander/commander.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def init(
    self,
    bot: "LetTradeBot",
    brain: "Brain",
    exchange: "Exchange",
    strategy: "Strategy",
):
    """Init commander dependencies

    Args:
        bot (LetTradeBot): LetTradeBot object
        brain (Brain): Brain of bot
        exchange (Exchange): Manage bot trading
        strategy (Strategy): Strategy of bot
    """
    self.bot = bot
    self.brain = brain
    self.exchange = exchange
    self.strategy = strategy

    self._name = self.bot._name

BackTestDataFeed ¤

BackTestDataFeed(
    data: DataFrame,
    name: str,
    timeframe: str | int | Timedelta | None = None,
    meta: dict | None = None,
    since: int | str | Timestamp | None = None,
    to: int | str | Timestamp | None = None,
    **kwargs
)

Bases: DataFeed

BackTest DataFeed

Parameters:

  • data (DataFrame) –

    description

  • name (str) –

    description

  • timeframe (str | int | Timedelta | None, default: None ) –

    description. Defaults to None.

  • meta (dict | None, default: None ) –

    description. Defaults to None.

  • since (int | str | Timestamp | None, default: None ) –

    Drop data before since. Defaults to None.

  • to (int | str | Timestamp | None, default: None ) –

    Drop data after to. Defaults to None.

Source code in lettrade/exchange/backtest/data.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def __init__(
    self,
    data: pd.DataFrame,
    name: str,
    timeframe: str | int | pd.Timedelta | None = None,
    meta: dict | None = None,
    since: int | str | pd.Timestamp | None = None,
    to: int | str | pd.Timestamp | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        data (pd.DataFrame): _description_
        name (str): _description_
        timeframe (str | int | pd.Timedelta | None, optional): _description_. Defaults to None.
        meta (dict | None, optional): _description_. Defaults to None.
        since (int | str | pd.Timestamp | None, optional): Drop data before since. Defaults to None.
        to (int | str | pd.Timestamp | None, optional): Drop data after to. Defaults to None.
    """
    if timeframe is None:
        timeframe = self._find_timeframe(data)
        logger.info("DataFeed %s auto detect timeframe %s", name, timeframe)
    super().__init__(
        data=data,
        name=name,
        timeframe=timeframe,
        meta=meta,
        **kwargs,
    )
    if since is not None or to is not None:
        self.drop(since=since, to=to)

i property ¤

Alias to lettrade.indicator and using in DataFeed by call: DataFeed.i.indicator_name()

is_main property ¤

is_main: bool

Property to check DataFeed is main DataFeed or not

l instance-attribute ¤

LetTrade DataFeed wrapper using to manage index pointer of DataFeed

meta property ¤

meta: dict

Property to get metadata of DataFeed

name property ¤

name: str

Property to get name of DataFeed

now property ¤

now: Timestamp

Property to get current index value of DataFeed

timeframe property ¤

timeframe: TimeFrame

Property to get timeframe of DataFeed

bar ¤

bar(i: int = 0) -> Timestamp

Get current pd.Timestamp value of DataFeed

Parameters:

  • i (int, default: 0 ) –

    Index. Defaults to 0.

Returns:

  • Timestamp

    pd.Timestamp: description

Source code in lettrade/data/data.py
108
109
110
111
112
113
114
115
116
117
def bar(self, i: int = 0) -> pd.Timestamp:
    """Get current pd.Timestamp value of DataFeed

    Args:
        i (int, optional): Index. Defaults to 0.

    Returns:
        pd.Timestamp: _description_
    """
    return self.l.index[i]

copy ¤

copy(deep: bool = False, **kwargs) -> DataFeed

summary

Parameters:

  • deep (bool, default: False ) –

    description. Defaults to False.

Returns:

Source code in lettrade/data/data.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def copy(self, deep: bool = False, **kwargs) -> "DataFeed":
    """_summary_

    Args:
        deep (bool, optional): _description_. Defaults to False.

    Returns:
        DataFeed: _description_
    """
    df = super().copy(deep=deep)
    df = self.__class__(
        data=df,
        name=self.name,
        timeframe=self.timeframe,
        meta=self.meta.copy(),
        **kwargs,
    )
    return df

drop ¤

drop(
    *args,
    since: int | str | Timestamp | None = None,
    to: int | str | Timestamp | None = None,
    **kwargs
) -> None

summary

Parameters:

  • since (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

  • to (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/data/data.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def drop(
    self,
    *args,
    since: int | str | pd.Timestamp | None = None,
    to: int | str | pd.Timestamp | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        since (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.
        to (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if since is None and to is None:
        super().drop(*args, **kwargs)
        return

    condiction = None

    # Since
    if since is not None:
        if isinstance(since, int):
            loc = self.l.index[since]
        elif isinstance(since, str):
            loc = pd.to_datetime(since, utc=True)
        elif isinstance(since, pd.Timestamp):
            loc = since
        else:
            raise RuntimeError(f"DataFeed.drop since {since} is invalid")
        condiction = self.index < loc

    # To
    if to is not None:
        if isinstance(to, int):
            loc = self.l.index[to]
        elif isinstance(to, str):
            loc = pd.to_datetime(to, utc=True)
        elif isinstance(to, pd.Timestamp):
            loc = to
        else:
            raise RuntimeError(f"DataFeed.drop to {to} is invalid")

        if condiction is None:
            condiction = self.index > loc
        else:
            condiction = condiction | (self.index > loc)

    index = self[condiction].index
    super().drop(index=index, inplace=True)
    self.l.reset()

    if __debug__:
        logger.debug("BackTestDataFeed %s dropped %s rows", self.name, len(index))

push ¤

push(
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs
)

Push new rows to DataFeed

Parameters:

  • rows (list[list[int | float]]) –

    list of rows [["timestamp", "open price", "high price"...]]

  • unit (str | None, default: None ) –

    pandas.Timestamp parsing unit. Defaults to None.

  • utc (bool, default: True ) –

    description. Defaults to True.

Source code in lettrade/data/data.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def push(
    self,
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs,
):
    """Push new rows to DataFeed

    Args:
        rows (list[list[int | float]]): list of rows `[["timestamp", "open price", "high price"...]]`
        unit (str | None, optional): pandas.Timestamp parsing unit. Defaults to None.
        utc (bool, optional): _description_. Defaults to True.
    """
    for row in rows:
        dt = pd.to_datetime(row[0], unit=unit, utc=utc, **kwargs)
        self.at[
            dt,
            (
                "open",
                "high",
                "low",
                "close",
                "volume",
            ),
        ] = (
            row[1],  # open
            row[2],  # high
            row[3],  # low
            row[4],  # close
            row[5],  # volume
        )

    if __debug__:
        logger.debug("[%s] Update bar: \n%s", self.name, self.tail(len(rows)))

BackTestDataFeeder ¤

Bases: DataFeeder

BackTest DataFeeder

is_continous property ¤

is_continous

Flag check is realtime continous datafeeder

BackTestExchange ¤

BackTestExchange(**kwargs)

Bases: Exchange

Source code in lettrade/exchange/backtest/exchange.py
14
15
16
17
18
def __init__(self, **kwargs):
    super().__init__(**kwargs)

    if self._config.setdefault("use_execution", False):
        self.executions = None

data instance-attribute ¤

data: DataFeed

main DataFeed

datas instance-attribute ¤

datas: list[DataFeed]

List of all available DataFeed

history_orders instance-attribute ¤

history_orders: dict[str, Order] = dict()

History Order dict by Order.id key

history_positions instance-attribute ¤

history_positions: dict[str, Position] = dict()

History Position dict by Position.id key

orders instance-attribute ¤

orders: dict[str, Order] = dict()

Available Order dict by Order.id key

positions instance-attribute ¤

positions: dict[str, Position] = dict()

Available Position dict by Position.id key

init ¤

init(
    brain: Brain,
    feeder: DataFeeder,
    account: Account,
    commander: Commander,
) -> None

Init Exchange dependencies. Set data/datas from feeder. Set state to ExchangeState.Start.

Parameters:

Source code in lettrade/exchange/exchange.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def init(
    self,
    brain: "Brain",
    feeder: DataFeeder,
    account: Account,
    commander: Commander,
) -> None:
    """Init Exchange dependencies.
    Set data/datas from feeder.
    Set state to `ExchangeState.Start`.

    Args:
        brain (Brain): _description_
        feeder (DataFeeder): _description_
        account (Account): _description_
        commander (Commander): _description_
    """
    self._brain = brain
    self._feeder = feeder
    self._account = account
    self._commander = commander

    self.data = self._feeder.data
    self.datas = self._feeder.datas

    self._account.init(exchange=self)

    self._state = ExchangeState.Start

new_order ¤

new_order(
    size: float,
    type: OrderType = OrderType.Market,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: object | None = None,
    data: DataFeed | None = None,
    **kwargs
) -> OrderResult

Place new order. Then send order events to Brain

Parameters:

  • size (float) –

    description

  • type (OrderType | None, default: Market ) –

    description. Defaults to OrderType.Market.

  • limit (float | None, default: None ) –

    description. Defaults to None.

  • stop (float | None, default: None ) –

    description. Defaults to None.

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • tag (object | None, default: None ) –

    description. Defaults to None.

  • data (DataFeed | None, default: None ) –

    description. Defaults to None.

  • **kwargs (dict | None, default: {} ) –

    Extra-parameters

Returns:

  • OrderResult ( OrderResult ) –

    Result when place new Order

Source code in lettrade/exchange/backtest/exchange.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def new_order(
    self,
    size: float,
    type: OrderType = OrderType.Market,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: object | None = None,
    data: DataFeed | None = None,
    **kwargs,
) -> OrderResult:
    """Place new order.
    Then send order events to `Brain`

    Args:
        size (float): _description_
        type (OrderType | None, optional): _description_. Defaults to OrderType.Market.
        limit (float | None, optional): _description_. Defaults to None.
        stop (float | None, optional): _description_. Defaults to None.
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        tag (object | None, optional): _description_. Defaults to None.
        data (DataFeed | None, optional): _description_. Defaults to None.
        **kwargs (dict | None, optional): Extra-parameters

    Returns:
        OrderResult: Result when place new `Order`
    """
    if not data:
        data = self.data

    order = BackTestOrder(
        id=self._id(),
        exchange=self,
        data=data,
        size=size,
        type=type,
        limit_price=limit,
        stop_price=stop,
        sl_price=sl,
        tp_price=tp,
        tag=tag,
    )
    ok = order.place(at=self.data.bar())

    if type == OrderType.Market:
        # Simulate market order will send event before return order result
        self._simulate_orders()

    return ok

next ¤

next()

Execution when new data feeded

Source code in lettrade/exchange/backtest/exchange.py
24
25
26
27
def next(self):
    """Execution when new data feeded"""
    self._simulate_orders()
    super().next()

next_next ¤

next_next()

Call after strategy.next()

Source code in lettrade/exchange/exchange.py
109
110
111
def next_next(self):
    """Call after strategy.next()"""
    self._account.next_next()

on_executions ¤

on_executions(
    executions: list[Execution],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Execution event from exchange then store and notify Brain

Source code in lettrade/exchange/exchange.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def on_executions(
    self,
    executions: list[Execution],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """
    Receive Execution event from exchange then store and notify Brain
    """
    for execution in executions:
        if not isinstance(execution, Execution):
            raise RuntimeError(f"{execution} is not instance of type Execution")

        if execution.id in self.executions:
            # Merge to keep Execution handler for strategy using
            # when strategy want to store Execution object
            # and object will be automatic update directly
            self.executions[execution.id].merge(execution)
            execution = self.executions[execution.id]
        else:
            self.executions[execution.id] = execution

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_executions(executions)

on_order ¤

on_order(
    order: Order, broadcast: bool | None = True, **kwargs
) -> None

Receive order event from exchange then store and notify Brain

Parameters:

  • order (Order) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Source code in lettrade/exchange/exchange.py
160
161
162
163
164
165
166
167
168
169
170
171
172
def on_order(
    self,
    order: Order,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive order event from exchange then store and notify Brain

    Args:
        order (Order): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.
    """
    self.on_orders(orders=[order], broadcast=broadcast, **kwargs)

on_orders ¤

on_orders(
    orders: list[Order],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive orders event from exchange then store and notify Brain

Parameters:

  • orders (list[Order]) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def on_orders(
    self,
    orders: list[Order],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive orders event from exchange then store and notify Brain

    Args:
        orders (list[Order]): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.

    Raises:
        RuntimeError: _description_
    """
    for order in orders:
        if not isinstance(order, Order):
            raise RuntimeError(f"{order} is not instance of type Order")

        if order.is_closed:
            if order.id in self.history_orders:
                logger.warning("Order closed recall: %s", order)

            self.history_orders[order.id] = order
            if order.id in self.orders:
                del self.orders[order.id]
        else:
            if order.id in self.history_orders:
                raise RuntimeError(f"Order {order.id} closed")

            if order.id in self.orders:
                # Merge to keep Order handler for strategy using
                # when strategy want to store Order object
                # and object will be automatic update directly
                self.orders[order.id].merge(order)
                order = self.orders[order.id]
            else:
                self.orders[order.id] = order

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_orders(orders)

on_position ¤

on_position(
    position: Position,
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Position event from exchange then store and notify Brain

Parameters:

  • position (Position) –

    new comming Position

  • broadcast (bool | None, default: True ) –

    Flag notify to Brain. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def on_position(
    self,
    position: Position,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Position event from exchange then store and notify Brain

    Args:
        position (Position): new comming `Position`
        broadcast (bool | None, optional): Flag notify to Brain. Defaults to True.

    Raises:
        RuntimeError: validate `Position` instance
    """
    self.on_positions(positions=[position], broadcast=broadcast, **kwargs)

on_positions ¤

on_positions(
    positions: list[Position],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Positions event from exchange then store and notify Brain

Parameters:

  • positions (list[Position]) –

    list of new comming Position

  • broadcast (bool | None, default: True ) –

    Flag notify to Brain. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def on_positions(
    self,
    positions: list[Position],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Positions event from exchange then store and notify Brain

    Args:
        positions (list[Position]): list of new comming `Position`
        broadcast (bool | None, optional): Flag notify to Brain. Defaults to True.

    Raises:
        RuntimeError: validate `Position` instance
    """
    for position in positions:
        if not isinstance(position, Position):
            raise RuntimeError(f"{position} is not instance of type Position")

        if position.is_exited:
            if position.id in self.history_positions:
                logger.warning("Position exited recall: %s", position)

            self.history_positions[position.id] = position
            if position.id in self.positions:
                del self.positions[position.id]

            # self._account._on_position_exit(position)
        else:
            if position.id in self.history_positions:
                raise RuntimeError(f"Position {position.id} closed: {position}")
            if position.id in self.positions:
                # Merge to keep Position handler for strategy using
                # when strategy want to store Position object
                # and object will be automatic update directly
                self.positions[position.id].merge(position)
                position = self.positions[position.id]
            else:
                self.positions[position.id] = position
                # self._account._on_position_entry(position)

    self._account.on_positions(positions)

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_positions(positions)

start ¤

start() -> None

Start of Exchange by: - Start account. - Set state to ExchangeState.Run ready for next().

Source code in lettrade/exchange/exchange.py
 97
 98
 99
100
101
102
103
def start(self) -> None:
    """Start of Exchange by:
    - Start account.
    - Set state to `ExchangeState.Run` ready for next().
    """
    self._account.start()
    self._state = ExchangeState.Run

stop ¤

stop() -> None

Stop Exchange

Source code in lettrade/exchange/exchange.py
113
114
115
116
def stop(self) -> None:
    """Stop Exchange"""
    self._state = ExchangeState.Stop
    self._account.stop()

BackTestExecution ¤

BackTestExecution(
    exchange: BackTestExchange, *args, **kwargs
)

Bases: Execution

Execution for backtesting

Source code in lettrade/exchange/backtest/trade.py
28
29
30
31
32
33
34
35
def __init__(self, exchange: "BackTestExchange", *args, **kwargs):
    if exchange.executions is None:
        logger.warning(
            "Execution transaction is disable, enable by flag: show_execution=True"
        )
        return

    super().__init__(*args, exchange=exchange, **kwargs)

is_long property ¤

is_long: bool

True if side is long (size is positive).

Returns:

  • bool ( bool ) –

    True/False

is_short property ¤

is_short: bool

True if side is short (size is negative).

Returns:

  • bool ( bool ) –

    description

side property ¤

side: TradeSide

True if side is short (size is negative).

Returns:

from_order classmethod ¤

from_order(
    order: BackTestOrder,
    price: float,
    at: object,
    size: float | None = None,
) -> BackTestExecution

Method help to build Execution object from Order object

Parameters:

  • price (float) –

    Executed price

  • at (object) –

    Executed bar

  • size (float | None, default: None ) –

    Executed size. Defaults to None.

Returns:

Source code in lettrade/exchange/backtest/trade.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@classmethod
def from_order(
    cls,
    order: "BackTestOrder",
    price: float,
    at: object,
    size: float | None = None,
) -> "BackTestExecution":
    """Method help to build Execution object from Order object

    Args:
        price (float): Executed price
        at (object): Executed bar
        size (float | None, optional): Executed size. Defaults to None.

    Returns:
        BackTestExecution: Execution object
    """
    return cls(
        id=order.id,
        size=size or order.size,
        exchange=order.exchange,
        data=order.data,
        price=price,
        at=at,
        order=order,
    )

merge ¤

merge(other: Execution)

Merge to keep object handler but not overwrite for Strategy using when Strategy want to store object and object will be automatic update directly

Source code in lettrade/exchange/execution.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def merge(self, other: "Execution"):
    """
    Merge to keep object handler but not overwrite for Strategy using when Strategy want to store object and object will be automatic update directly
    """
    if other is self:
        return

    if self.id != other.id:
        raise RuntimeError(f"Merge difference id {self.id} != {other.id} execution")

    self.price = other.price
    self.size = other.size

    if other.at:
        self.at = other.at
    if other.order_id:
        self.order_id = other.order_id
    if other.order:
        self.order = other.order
    if other.position_id:
        self.position_id = other.position_id
    if other.position:
        self.position = other.position

BackTestOrder ¤

BackTestOrder(
    id: str,
    exchange: Exchange,
    data: DataFeed,
    size: float,
    state: OrderState = OrderState.Pending,
    type: OrderType = OrderType.Market,
    limit_price: float | None = None,
    stop_price: float | None = None,
    sl_price: float | None = None,
    tp_price: float | None = None,
    parent: Position | None = None,
    tag: str | None = None,
    placed_at: Timestamp | None = None,
    **kwargs
)

Bases: Order

Order for backtesting

Source code in lettrade/exchange/order.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def __init__(
    self,
    id: str,
    exchange: "Exchange",
    data: "DataFeed",
    size: float,
    state: OrderState = OrderState.Pending,
    type: OrderType = OrderType.Market,
    limit_price: float | None = None,
    stop_price: float | None = None,
    sl_price: float | None = None,
    tp_price: float | None = None,
    parent: "Position | None" = None,
    tag: str | None = None,
    placed_at: pd.Timestamp | None = None,
    **kwargs,
):
    super().__init__(
        id=id,
        exchange=exchange,
        data=data,
        size=size,
        **kwargs,
    )

    self.type: OrderType = type
    self.state: OrderState = state
    self.limit_price: float | None = limit_price
    self.stop_price: float | None = stop_price
    self.sl_price: float | None = sl_price
    self.tp_price: float | None = tp_price
    self.parent: "Position | None" = parent
    self.tag: str | None = tag
    self.placed_at: pd.Timestamp | None = placed_at
    self.filled_at: pd.Timestamp | None = None
    self.filled_price: float | None = None

is_closed property ¤

is_closed: bool

Flag to check Order closed

Returns:

  • bool ( bool ) –

    True if state in [OrderState.Filled, OrderState.Canceled]

is_long property ¤

is_long: bool

True if side is long (size is positive).

Returns:

  • bool ( bool ) –

    True/False

is_opening property ¤

is_opening: bool

Flag to check Order still alive

Returns:

  • bool ( bool ) –

    True if state in [OrderState.Pending, OrderState.Placed, OrderState.Partial]

is_short property ¤

is_short: bool

True if side is short (size is negative).

Returns:

  • bool ( bool ) –

    description

is_sl_order property ¤

is_sl_order: bool

Order is stop-loss order of a Position

Returns:

  • bool ( bool ) –

    description

is_tp_order property ¤

is_tp_order: bool

Order is take-profit order of a Position

Returns:

  • bool ( bool ) –

    description

limit property ¤

limit: float | None

Getter of limit_price

Returns:

  • float | None

    float | None: float or None

place_price property ¤

place_price: float | None

Getter of place_price

Returns:

  • float | None

    float | None: float or None

side property ¤

side: TradeSide

True if side is short (size is negative).

Returns:

sl property ¤

sl: float | None

Getter of sl_price

Returns:

  • float | None

    float | None: float or None

stop property ¤

stop: float | None

Getter of stop_price

Returns:

  • float | None

    float | None: float or None

tp property ¤

tp: float | None

Getter of tp_price

Returns:

  • float | None

    float | None: float or None

cancel ¤

cancel(
    caller: Order | Position | None = None, **kwargs
) -> OrderResult

Cancel the Order and notify Exchange

Source code in lettrade/exchange/backtest/trade.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def cancel(
    self,
    caller: Order | Position | None = None,
    **kwargs,
) -> "OrderResult":
    """Cancel the Order and notify Exchange"""
    if self.state != OrderState.Placed:
        raise RuntimeError(f"Order {self.id} state {self.state} is not Placed")

    if self.parent:
        if self is self.parent.sl_order:
            self.parent.sl_order = None
        elif self is self.parent.tp_order:
            self.parent.tp_order = None

    return super().cancel()

fill ¤

fill(
    price: float, at: object, **kwargs
) -> BackTestExecution

Execution order and notify for Exchange

Parameters:

  • price (float) –

    Executed price

  • at (object) –

    Executed bar

Raises:

Returns:

Source code in lettrade/exchange/backtest/trade.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def fill(self, price: float, at: object, **kwargs) -> BackTestExecution:
    """Execution order and notify for Exchange

    Args:
        price (float): Executed price
        at (object): Executed bar

    Raises:
        RuntimeError: _description_

    Returns:
        BackTestExecution: Execution object
    """
    if self.state != OrderState.Placed:
        raise RuntimeError(f"Execution a {self.state} order")

    # Order
    ok = super().fill(price=price, at=at)

    # Execution is enable
    if self.exchange.executions is not None:
        execution = BackTestExecution.from_order(order=self, price=price, at=at)
        execution._on_execution()

    # Position hit SL/TP
    if self.parent:
        self.parent.exit(price=price, at=at, caller=self)
    else:
        # Position: Place and create new position
        position = BackTestPosition.from_order(order=self)

        position.entry(price=price, at=at)

    return ok

merge ¤

merge(other: Order)

Update current Order variables by other Order

Parameters:

  • other (Order) –

    Merge source Order

Raises:

Source code in lettrade/exchange/order.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def merge(self, other: "Order"):
    """Update current `Order` variables by other `Order`

    Args:
        other (Order): Merge source `Order`

    Raises:
        RuntimeError: Validate same id
    """
    if other is self:
        return

    if self.id != other.id:
        raise RuntimeError(f"Merge difference id {self.id} != {other.id} order")

    self.size = other.size
    self.sl_price = other.sl_price
    self.tp_price = other.tp_price

    if other.limit_price:
        self.limit_price = other.limit_price
    if other.stop_price:
        self.stop_price = other.stop_price
    if other.placed_at:
        self.placed_at = other.placed_at

    if other.filled_price:
        self.filled_price = other.filled_price
    if other.filled_at:
        self.filled_at = other.filled_at

    if other.parent:
        self.parent = other.parent

place ¤

place(
    at: Timestamp, raw: object | None = None
) -> OrderResult

Place Order Set status to OrderState.Placed. Send event to Exchange

Raises:

Returns:

Source code in lettrade/exchange/order.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def place(
    self,
    at: pd.Timestamp,
    raw: object | None = None,
) -> "OrderResult":
    """Place `Order`
    Set `status` to `OrderState.Placed`.
    Send event to `Exchange`

    Raises:
        RuntimeError: _description_

    Returns:
        OrderResult: result of `Order`
    """
    self.validate()

    if self.state != OrderState.Pending:
        raise RuntimeError(f"Order {self.id} state {self.state} is not Pending")

    self.state = OrderState.Placed
    self.placed_at = at

    logger.info("Placing new order: %s", self)

    self.exchange.on_order(self)
    return OrderResultOk(order=self, raw=raw)

update ¤

update(
    limit_price: float | None = None,
    stop_price: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    raw: object | None = None,
) -> OrderResult

Update Order

Parameters:

  • limit_price (float, default: None ) –

    description. Defaults to None.

  • stop_price (float, default: None ) –

    description. Defaults to None.

  • sl (float, default: None ) –

    description. Defaults to None.

  • tp (float, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/exchange/order.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def update(
    self,
    limit_price: float | None = None,
    stop_price: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    raw: object | None = None,
) -> "OrderResult":
    """Update Order

    Args:
        limit_price (float, optional): _description_. Defaults to None.
        stop_price (float, optional): _description_. Defaults to None.
        sl (float, optional): _description_. Defaults to None.
        tp (float, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if self.is_closed:
        raise RuntimeError(f"Update a closed order {self}")

    # TODO: validate parameters
    if limit_price is not None:
        self.limit_price = limit_price
    if stop_price is not None:
        self.stop_price = stop_price

    if sl is not None:
        self.sl_price = sl
    if tp is not None:
        self.tp_price = tp

    self.exchange.on_order(self)
    return OrderResultOk(order=self, raw=raw)

BackTestPosition ¤

BackTestPosition(
    id: str,
    exchange: Exchange,
    data: DataFeed,
    size: float,
    parent: Order,
    state: PositionState = PositionState.Open,
    entry_price: float | None = None,
    entry_fee: float = 0.0,
    entry_at: Timestamp | None = None,
    sl_order: Order | None = None,
    tp_order: Order | None = None,
    tag: str | None = None,
    **kwargs
)

Bases: Position

Position for backtesting

Source code in lettrade/exchange/position.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def __init__(
    self,
    id: str,
    exchange: "Exchange",
    data: "DataFeed",
    size: float,
    parent: "Order",
    state: PositionState = PositionState.Open,
    entry_price: float | None = None,
    entry_fee: float = 0.0,
    entry_at: pd.Timestamp | None = None,
    sl_order: Order | None = None,
    tp_order: Order | None = None,
    tag: str | None = None,
    **kwargs,
):
    super().__init__(
        id=id,
        exchange=exchange,
        data=data,
        size=size,
        **kwargs,
    )
    self._account: "Account" = self.exchange._account

    self.state: PositionState = state
    self.parent: "Order" = parent
    self.tag: str | None = tag

    self.entry_price: float | None = entry_price
    self.entry_fee: float = entry_fee
    self.entry_at: pd.Timestamp | None = entry_at

    self.exit_price: float | None = None
    self.exit_fee: float = 0.0
    self.exit_at: pd.Timestamp | None = None
    self.exit_pl: float | None = None

    self.sl_order: Order | None = sl_order
    self.tp_order: Order | None = tp_order

fee property ¤

fee: float

Fee/Estimate Fee for trade

Returns:

  • float ( float ) –

    Fee

is_exited property ¤

is_exited: bool

Flag to check Position state.

Returns:

  • bool ( bool ) –

    True if the trade exited

is_long property ¤

is_long: bool

True if side is long (size is positive).

Returns:

  • bool ( bool ) –

    True/False

is_opening property ¤

is_opening: bool

Flag to check Position state.

Returns:

  • bool ( bool ) –

    True if the trade opening

is_short property ¤

is_short: bool

True if side is short (size is negative).

Returns:

  • bool ( bool ) –

    description

pl property ¤

pl: float

Estimate Profit or Loss of Position

Returns:

  • float ( float ) –

    PnL

side property ¤

side: TradeSide

True if side is short (size is negative).

Returns:

exit ¤

exit(
    price: float | None = None,
    at: Timestamp | None = None,
    caller: Order | Position | None = None,
    **kwargs
) -> PositionResult

Exit Position

Parameters:

  • price (float, default: None ) –

    Exit price

  • at (object, default: None ) –

    Exit bar

  • caller (Order | Position, default: None ) –

    Skip caller to prevent infinite recursion loop. Defaults to None.

Source code in lettrade/exchange/backtest/trade.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
def exit(
    self,
    price: float | None = None,
    at: pd.Timestamp | None = None,
    caller: Order | Position | None = None,
    **kwargs,
) -> PositionResult:
    """Exit Position

    Args:
        price (float): Exit price
        at (object): Exit bar
        caller (Order | Position, optional): Skip caller to prevent infinite recursion loop. Defaults to None.
    """
    if self.state == PositionState.Exit:
        if caller is None:
            # Call by user
            raise RuntimeError(f"Call exited position {self}")
        return

    if caller is None:
        # Call by user
        if price is not None:
            raise RuntimeError(f"Price set {price} is not available")
        if at is not None:
            raise RuntimeError(f"At set {at} is not available")

        price = self.data.l.open[0]
        at = self.data.l.index[0]
    else:
        # Call by SL/TP order
        if price is None or at is None:
            raise RuntimeError(f"Caller {caller} with price is None or at is None")

    # PnL
    pl = self._account.pl(
        size=self.size,
        entry_price=self.entry_price,
        exit_price=price,
    )

    # Fee
    fee = self._account.fee(size=self.size)

    # State
    ok = super().exit(price=price, at=at, pl=pl, fee=fee, **kwargs)

    # Caller is position close by tp/sl order
    if caller is None:
        if self.sl_order is not None:
            self.sl_order.cancel(caller=self)
        if self.tp_order is not None:
            self.tp_order.cancel(caller=self)
    else:
        if self.sl_order and self.sl_order is not caller:
            self.sl_order.cancel(caller=caller)
        if self.tp_order and self.tp_order is not caller:
            self.tp_order.cancel(caller=caller)

    return ok

from_order classmethod ¤

from_order(
    order: BackTestOrder,
    size: float | None = None,
    state: PositionState = PositionState.Open,
    **kwargs
) -> BackTestPosition

Build Position object from Order object

Parameters:

  • size (float, default: None ) –

    Size of Position object. Defaults to None.

  • state (PositionState, default: Open ) –

    State of Position object. Defaults to PositionState.Open.

Returns:

Source code in lettrade/exchange/backtest/trade.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
@classmethod
def from_order(
    cls,
    order: "BackTestOrder",
    size: float | None = None,
    state: PositionState = PositionState.Open,
    **kwargs,
) -> "BackTestPosition":
    """Build Position object from Order object

    Args:
        size (float, optional): Size of Position object. Defaults to None.
        state (PositionState, optional): State of Position object. Defaults to PositionState.Open.

    Returns:
        BackTestPosition: Position object
    """
    position = cls(
        id=order.id,
        size=size or order.size,
        exchange=order.exchange,
        data=order.data,
        state=state,
        parent=order,
    )
    if order.sl_price:
        position._new_sl_order(stop_price=order.sl_price)
    if order.tp_price:
        position._new_tp_order(limit_price=order.tp_price)
    order.parent = position
    return position

merge ¤

merge(other: Position) -> bool

Merge position from another position has same id

Parameters:

Raises:

Source code in lettrade/exchange/position.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def merge(self, other: "Position") -> bool:
    """Merge position from another position has same id

    Args:
        other (Position): _description_

    Raises:
        RuntimeError: _description_
    """
    if other is self:
        return False

    if self.id != other.id:
        raise RuntimeError(f"Merge difference id {self.id} != {other.id} order")

    self.size = other.size

    if other.sl_order is not None:
        self.sl_order = other.sl_order
        self.sl_order.parent = self
    elif self.sl_order is not None:
        self.sl_order.cancel()
        self.sl_order = None

    if other.tp_order is not None:
        self.tp_order = other.tp_order
        self.tp_order.parent = self
    elif self.tp_order is not None:
        self.tp_order.cancel()
        self.tp_order = None

    if other.entry_at:
        self.entry_at = other.entry_at
    if other.entry_price:
        self.entry_price = other.entry_price

    if other.exit_at:
        self.exit_at = other.exit_at
    if other.exit_price:
        self.exit_price = other.exit_price
    if other.exit_fee:
        self.exit_fee = other.exit_fee
    if other.exit_pl:
        self.exit_pl = other.exit_pl

    if other.parent:
        self.parent = other.parent

    return True

BotPlotter ¤

BotPlotter(bot: LetTradeBot)

Bases: Plotter

Class help to plot bot result

Source code in lettrade/plot/bot.py
34
35
36
37
38
39
40
41
def __init__(self, bot: "LetTradeBot") -> None:
    self.bot = bot
    self.feeder = bot.feeder
    self.exchange = bot.exchange
    self.account = bot.account
    self.strategy = bot.strategy

    self.datas = self.feeder.datas

data property writable ¤

data: DataFeed

Get plotting main datafeed

Returns:

datas instance-attribute ¤

datas: list[DataFeed] = datas

All plotting datafeeds

jump ¤

jump(
    since: int | str | Timestamp | None = None,
    order_id: str | None = None,
    position_id: str | None = None,
    range: int = 300,
    name: str | None = None,
)

Jump to place on datefeed

Parameters:

  • since (int | str | Timestamp | None, default: None ) –

    Jump to index/datetime. Defaults to None.

  • order_id (str | None, default: None ) –

    Jump to order id. Defaults to None.

  • position_id (str | None, default: None ) –

    Jump to position id. Defaults to None.

  • range (int, default: 300 ) –

    number of candle plot. Defaults to 300.

  • name (str | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/plot/bot.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def jump(
    self,
    since: int | str | pd.Timestamp | None = None,
    order_id: str | None = None,
    position_id: str | None = None,
    range: int = 300,
    name: str | None = None,
):
    """Jump to place on datefeed

    Args:
        since (int | str | pd.Timestamp | None, optional): Jump to index/datetime. Defaults to None.
        order_id (str | None, optional): Jump to order id. Defaults to None.
        position_id (str | None, optional): Jump to position id. Defaults to None.
        range (int, optional): number of candle plot. Defaults to 300.
        name (str | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if self._datas_stored is None:
        self._datas_stored = self.datas.copy()

    if since is None:
        if order_id is not None:  # Jump to order id
            if not isinstance(order_id, str):
                order_id = str(order_id)

            if order_id in self.exchange.orders:
                order = self.exchange.orders[order_id]
            elif order_id in self.exchange.history_orders:
                order = self.exchange.history_orders[order_id]
            else:
                raise RuntimeError(f"Order id {order_id} not found")

            loc = self._data_stored.l.index.get_loc(order.placed_at)
            since = loc - int(range / 2)

        elif position_id is not None:  # Jump to position id
            if not isinstance(position_id, str):
                position_id = str(position_id)

            if position_id in self.exchange.positions:
                position = self.exchange.positions[position_id]
            elif position_id in self.exchange.history_positions:
                position = self.exchange.history_positions[position_id]
            else:
                raise RuntimeError(f"Position id {position_id} not found")

            loc = self._data_stored.l.index.get_loc(position.entry_at)
            since = loc - int(range / 2)
        else:  # Reset
            self.jump_reset()
            return

    elif isinstance(since, str):  # Parse string to pd.Timestamp, then since=index
        since = pd.to_datetime(since, utc=True)
        since = self._data_stored.l.index.get_loc(since)
    elif isinstance(since, pd.Timestamp):  # Get index of Timestamp
        since = self._data_stored.l.index.get_loc(since)

    if name is None:
        name = self._data_stored.name

    # Since min at pointer_start
    if since < self._data_stored.l.pointer_start:
        since = self._data_stored.l.pointer_start
    # Since max at pointer_stop
    if since > self._data_stored.l.pointer_stop - range:
        since = self._data_stored.l.pointer_stop - range

    # Jump
    jump_start_dt = None
    jump_stop_dt = None
    for i, data in enumerate(self._datas_stored):
        if i == 0:
            self.datas[i] = data.__class__(
                data=data.l[since : since + range],
                name=data.name,
                timeframe=data.timeframe,
            )
            jump_start_dt = self.data.index[0]
            jump_stop_dt = self.data.index[-1]
        else:
            self.datas[i] = data.__class__(
                data=data.loc[
                    (data.index >= jump_start_dt) & (data.index <= jump_stop_dt)
                ],
                name=data.name,
                timeframe=data.timeframe,
            )

        if hasattr(data, DATAFRAME_PLOTTERS_NAME):
            object.__setattr__(
                self.datas[i],
                DATAFRAME_PLOTTERS_NAME,
                getattr(data, DATAFRAME_PLOTTERS_NAME),
            )

    # Reload data
    self.load()

jump_reset ¤

jump_reset() -> bool

Reset jump datafeeds back to bot datafeeds

Source code in lettrade/plot/bot.py
162
163
164
165
166
167
168
def jump_reset(self) -> bool:
    """Reset jump datafeeds back to bot datafeeds"""
    if not self._datas_stored or self.data is self._data_stored:
        return False

    self.datas = self._datas_stored.copy()
    return True

load abstractmethod ¤

load()

Load plot config from Strategy.plot() and setup candlestick/equity

Source code in lettrade/plot/plot.py
 9
10
11
@abstractmethod
def load(self):
    """Load plot config from `Strategy.plot()` and setup candlestick/equity"""

plot abstractmethod ¤

plot(**kwargs)

Plot equity, orders, and positions then show

Source code in lettrade/plot/plot.py
13
14
15
@abstractmethod
def plot(self, **kwargs):
    """Plot `equity`, `orders`, and `positions` then show"""

stop abstractmethod ¤

stop()

stop plotter

Source code in lettrade/plot/plot.py
17
18
19
@abstractmethod
def stop(self):
    """stop plotter"""

BotStatistic ¤

BotStatistic(
    feeder: DataFeeder,
    exchange: Exchange,
    strategy: Strategy,
)

Compute strategy result

Source code in lettrade/stats/stats.py
21
22
23
24
25
26
27
28
29
30
def __init__(
    self,
    feeder: DataFeeder,
    exchange: Exchange,
    strategy: Strategy,
) -> None:
    self.feeder: DataFeeder = feeder
    self.exchange: Exchange = exchange
    self.strategy: Strategy = strategy
    self.account: Account = strategy.account

compute ¤

compute()

Calculate strategy report

Source code in lettrade/stats/stats.py
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def compute(self):
    """Calculate strategy report"""
    data = self.feeder.data

    ### Stats
    self.result = result = pd.Series(dtype=object)

    result.loc["strategy"] = str(self.strategy.__class__)
    result.loc["start"] = data.index[0]
    result.loc["end"] = data.index[-1]
    result.loc["duration"] = result.end - result.start

    ### Equity
    equities_index = pd.DatetimeIndex(self.account._equities.keys())
    equities = pd.Series(self.account._equities.values(), index=equities_index)
    dd = 1 - equities / np.maximum.accumulate(equities)
    dd_dur, dd_peaks = _compute_drawdown_duration_peaks(
        pd.Series(dd, index=equities_index)
    )

    result.loc["start_balance"] = round(equities.iloc[0], 2)
    result.loc["equity"] = round(equities.iloc[-1], 2)
    result.loc["equity_peak"] = round(equities.max(), 2)

    pl = equities.iloc[-1] - equities.iloc[0]
    result.loc["pl"] = round(pl, 2)
    result.loc["pl_percent"] = round(pl / equities.iloc[0] * 100, 2)

    c = data.close.values
    result.loc["buy_hold_pl_percent"] = round(
        (c[-1] - c[0]) / c[0] * 100, 2
    )  # long-only return

    max_dd = -np.nan_to_num(dd.max())
    result.loc["max_drawdown_percent"] = round(max_dd * 100, 2)
    result.loc["avg_drawdown_percent"] = round(-dd_peaks.mean() * 100, 2)
    result.loc["max_drawdown_duration"] = _round_timedelta(
        dd_dur.max(), data.timeframe
    )
    result.loc["avg_drawdown_duration"] = _round_timedelta(
        dd_dur.mean(), data.timeframe
    )

    # Separator
    result.loc[""] = ""

    ### Positions
    positions = list(self.exchange.history_positions.values()) + list(
        self.exchange.positions.values()
    )
    positions_columns = (
        "size",
        "entry_at",
        "exit_at",
        "entry_price",
        "exit_price",
        "pl",
        "fee",
    )
    positions_df = pd.DataFrame(columns=positions_columns)
    for position in positions:
        positions_df.at[position.id, positions_columns] = (
            position.size,
            position.entry_at,
            position.exit_at,
            position.entry_price,
            position.exit_price,
            position.pl,
            position.fee,
        )
    positions_df["duration"] = positions_df["entry_at"] - positions_df["exit_at"]
    positions_total = len(positions)
    pl = positions_df["pl"]

    result.loc["positions"] = positions_total

    win_rate = np.nan if not positions_total else (pl > 0).mean()
    result.loc["win_rate"] = round(win_rate, 2)
    result.loc["fee"] = round(positions_df.fee.sum(), 2)
    result.loc["best_trade_percent"] = round(pl.max(), 2)
    result.loc["worst_trade_percent"] = round(pl.min(), 2)
    result.loc["sqn"] = round(
        np.sqrt(positions_total) * pl.mean() / (pl.std() or np.nan),
        2,
    )
    result.loc["kelly_criterion"] = win_rate - (1 - win_rate) / (
        pl[pl > 0].mean() / -pl[pl < 0].mean()
    )
    result.loc["profit_factor"] = pl[pl > 0].sum() / (
        abs(pl[pl < 0].sum()) or np.nan
    )

    return self.result

show ¤

show()

Show statistic report

Source code in lettrade/stats/stats.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def show(self):
    """
    Show statistic report
    """
    if "Start" not in self.result:
        logger.warning("call compute() before show()")
        self.compute()

    # Show result inside docs session
    if __debug__:
        from lettrade.utils.docs import is_docs_session

        if is_docs_session():
            print(str(self))
            return

    logger.info(
        "\n============= Statistic result =============\n%s\n",
        str(self),
    )

CCXTAPI ¤

CCXTAPI(
    exchange: int,
    key: str,
    secret: str,
    currency: str = "USDT",
    ccxt: CCXTAPIExchange | None = None,
    **kwargs
)

Bases: LiveAPI

CCXT API

Parameters:

  • exchange (int) –

    description

  • key (str) –

    description

  • secret (str) –

    description

  • ccxt (CCXTAPIExchange | None, default: None ) –

    description. Defaults to None.

Source code in lettrade/exchange/ccxt/api.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def __init__(
    self,
    exchange: int,
    key: str,
    secret: str,
    currency: str = "USDT",
    ccxt: CCXTAPIExchange | None = None,
    **kwargs,
):
    """_summary_

    Args:
        exchange (int): _description_
        key (str): _description_
        secret (str): _description_
        ccxt (CCXTAPIExchange | None, optional): _description_. Defaults to None.
    """
    if ccxt is None:
        ccxt = CCXTAPIExchange(exchange=exchange, key=key, secret=secret, **kwargs)
    self._ccxt = ccxt
    self._currency = ccxt

account ¤

account() -> dict
Source code in lettrade/exchange/ccxt/api.py
264
265
266
267
268
269
270
271
272
273
274
def account(self) -> dict:
    """"""
    raw = self._ccxt.fetch_my_balance()
    currency = raw[self._currency]
    return Box(
        balance=currency["free"],
        equity=currency["total"],
        margin=1,
        leverage=1,
        raw=raw,
    )

execution_get ¤

execution_get(id: str, **kwargs) -> dict
Source code in lettrade/exchange/ccxt/api.py
323
324
def execution_get(self, id: str, **kwargs) -> dict:
    """"""

executions_get ¤

executions_get(
    position_id: str | None = None,
    search: str | None = None,
    **kwargs
) -> list[dict]
Source code in lettrade/exchange/ccxt/api.py
315
316
317
318
319
320
321
def executions_get(
    self,
    position_id: str | None = None,
    search: str | None = None,
    **kwargs,
) -> list[dict]:
    """"""

executions_total ¤

executions_total(
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> int
Source code in lettrade/exchange/ccxt/api.py
307
308
309
310
311
312
313
def executions_total(
    self,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> int:
    """"""

init ¤

init(**kwargs)
Source code in lettrade/exchange/live/api.py
21
22
def init(self, **kwargs):
    """"""

order_close ¤

order_close(order: CCXTOrder)
Source code in lettrade/exchange/ccxt/api.py
297
298
def order_close(self, order: "CCXTOrder"):
    """"""

order_open ¤

order_open(order: CCXTOrder, **kwargs)
Source code in lettrade/exchange/ccxt/api.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
def order_open(self, order: "CCXTOrder", **kwargs):
    """"""
    try:
        result = self._ccxt.create_my_order(
            symbol=order.data.symbol,
            type=order.type.lower(),
            side=order.side.lower(),
            amount=abs(order.size),
            price=order.place_price,
            **kwargs,
        )

        print("order_open", order, result)
        return result
    except ccxt.InvalidOrder as e:
        raise LetLiveOrderInvalidException(e.args[0]) from e

orders_get ¤

orders_get(**kwargs)
Source code in lettrade/exchange/ccxt/api.py
303
304
def orders_get(self, **kwargs):
    """"""

orders_history_get ¤

orders_history_get(
    id: str | None = None,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> list[dict]
Source code in lettrade/exchange/live/api.py
100
101
102
103
104
105
106
107
def orders_history_get(
    self,
    id: str | None = None,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> list[dict]:
    """"""

orders_total ¤

orders_total()
Source code in lettrade/exchange/ccxt/api.py
300
301
def orders_total(self):
    """"""

position_close ¤

position_close(position: CCXTPosition, **kwargs) -> dict
Source code in lettrade/exchange/ccxt/api.py
347
348
def position_close(self, position: "CCXTPosition", **kwargs) -> dict:
    """"""

position_update ¤

position_update(
    position: CCXTPosition,
    sl: float | None = None,
    tp: float | None = None,
    **kwargs
) -> dict
Source code in lettrade/exchange/ccxt/api.py
338
339
340
341
342
343
344
345
def position_update(
    self,
    position: "CCXTPosition",
    sl: float | None = None,
    tp: float | None = None,
    **kwargs,
) -> dict:
    """"""

positions_get ¤

positions_get(
    id: str = None, symbol: str = None, **kwargs
) -> list[dict]
Source code in lettrade/exchange/ccxt/api.py
335
336
def positions_get(self, id: str = None, symbol: str = None, **kwargs) -> list[dict]:
    """"""

positions_total ¤

positions_total(
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> int
Source code in lettrade/exchange/ccxt/api.py
327
328
329
330
331
332
333
def positions_total(
    self,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> int:
    """"""

stop ¤

stop()
Source code in lettrade/exchange/ccxt/api.py
232
233
def stop(self):
    """"""

CCXTAccount ¤

CCXTAccount(api: LiveAPI, currency: str = 'USDT', **kwargs)

Bases: LiveAccount

Account for CCXT

Source code in lettrade/exchange/ccxt/ccxt.py
40
41
42
def __init__(self, api: LiveAPI, currency: str = "USDT", **kwargs) -> None:
    super().__init__(api, **kwargs)
    self._currency = currency

balance property ¤

balance: float

Balance of account

Returns:

  • float ( float ) –

    description

account_refresh ¤

account_refresh()

Refresh account balance

Source code in lettrade/exchange/live/account.py
53
54
55
56
57
58
59
60
61
def account_refresh(self):
    """Refresh account balance"""
    self._account = self._api.account()

    if __debug__:
        logger.debug("Account: %s", str(self._account))

    self._balance = self._account.balance
    self._equity = self._account.equity

next ¤

next()

Next account

Source code in lettrade/account/account.py
73
74
def next(self):
    """Next account"""

next_next ¤

next_next()

Call after strategy.next()

Source code in lettrade/account/account.py
76
77
78
def next_next(self):
    """Call after strategy.next()"""
    self._equity_snapshot()

risk ¤

risk(side: TradeSide, size: float, **kwargs) -> float

Risk calculation

Source code in lettrade/account/account.py
87
88
89
90
91
def risk(self, side: "TradeSide", size: float, **kwargs) -> float:
    """Risk calculation"""
    if size is None:
        return side * abs(self._risk)
    return side * abs(size)

start ¤

start()

Start live account by load account info from API

Source code in lettrade/exchange/live/account.py
34
35
36
37
38
def start(self):
    """Start live account by load account info from API"""
    self.account_refresh()
    self._margin = self._account.margin
    self._leverage = self._account.leverage

stop ¤

stop()

Stop account

Source code in lettrade/account/account.py
80
81
82
83
84
85
def stop(self):
    """Stop account"""
    try:
        self._equity_snapshot()
    except LetAccountInsufficientException:
        pass

CCXTDataFeed ¤

CCXTDataFeed(
    symbol: str,
    timeframe: str | int | Timedelta,
    name: str | None = None,
    columns: list[str] | None = None,
    api: LiveAPI | None = None,
    api_kwargs: dict | None = None,
    **kwargs
)

Bases: LiveDataFeed

DataFeed for CCXT

Parameters:

  • symbol (str) –

    Symbol of DataFeed

  • timeframe (str | int | Timedelta) –

    TimeFrame of DataFeed

  • name (str | None, default: None ) –

    Name of DataFeed, auto generate {symbol}_{timeframe} if none. Defaults to None.

  • columns (list[str] | None, default: None ) –

    List of DataFeed columns. Defaults to None.

  • api (LiveAPI | None, default: None ) –

    Live trading API. Defaults to None.

Source code in lettrade/exchange/live/data.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def __init__(
    self,
    symbol: str,
    timeframe: str | int | pd.Timedelta,
    name: str | None = None,
    columns: list[str] | None = None,
    api: LiveAPI | None = None,
    api_kwargs: dict | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        symbol (str): Symbol of DataFeed
        timeframe (str | int | pd.Timedelta): TimeFrame of DataFeed
        name (str | None, optional): Name of DataFeed, auto generate `{symbol}_{timeframe}` if none. Defaults to None.
        columns (list[str] | None, optional): List of DataFeed columns. Defaults to None.
        api (LiveAPI | None, optional): Live trading API. Defaults to None.
    """
    super().__init__(
        name=name or f"{symbol}_{timeframe}",
        timeframe=timeframe,
        columns=columns or ["open", "high", "low", "close", "volume"],
        **kwargs,
    )

    self.meta.update(symbol=symbol, base_columns=self.columns.copy())

    if api is not None:
        self._api = api
    elif api_kwargs is not None:
        self._api = self._api_cls(**api_kwargs)
    else:
        raise RuntimeError("Parameter: api or api_kwargs cannot missing")

i property ¤

Alias to lettrade.indicator and using in DataFeed by call: DataFeed.i.indicator_name()

is_main property ¤

is_main: bool

Property to check DataFeed is main DataFeed or not

l instance-attribute ¤

LetTrade DataFeed wrapper using to manage index pointer of DataFeed

meta property ¤

meta: dict

Property to get metadata of DataFeed

name property ¤

name: str

Property to get name of DataFeed

now property ¤

now: Timestamp

Property to get current index value of DataFeed

symbol property ¤

symbol: str

Property to get symbol of DataFeed

timeframe property ¤

timeframe: TimeFrame

Property to get timeframe of DataFeed

bar ¤

bar(i: int = 0) -> Timestamp

Get current pd.Timestamp value of DataFeed

Parameters:

  • i (int, default: 0 ) –

    Index. Defaults to 0.

Returns:

  • Timestamp

    pd.Timestamp: description

Source code in lettrade/data/data.py
108
109
110
111
112
113
114
115
116
117
def bar(self, i: int = 0) -> pd.Timestamp:
    """Get current pd.Timestamp value of DataFeed

    Args:
        i (int, optional): Index. Defaults to 0.

    Returns:
        pd.Timestamp: _description_
    """
    return self.l.index[i]

bars ¤

bars(
    since: int | str | Timestamp, to: int | str | Timestamp
) -> list

Get bars from LiveAPI

Parameters:

  • since (int | str | Timestamp) –

    description

  • to (int | str | Timestamp) –

    description

Returns:

  • list ( list ) –

    list of bar

Source code in lettrade/exchange/live/data.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def bars(
    self,
    since: int | str | pd.Timestamp,
    to: int | str | pd.Timestamp,
) -> list:
    """Get bars from LiveAPI

    Args:
        since (int | str | pd.Timestamp): _description_
        to (int | str | pd.Timestamp): _description_

    Returns:
        list: list of bar
    """
    return self._api.bars(
        symbol=self.symbol,
        timeframe=self.timeframe.string,
        since=since,
        to=to,
    )

bars_load ¤

bars_load(
    since: int | str | Timestamp, to: int | str | Timestamp
) -> bool

Get bar from API and push to DataFeed

Parameters:

  • since (int | str | Timestamp) –

    description

  • to (int | str | Timestamp) –

    description

Returns:

  • bool ( bool ) –

    True if has data, False if no data

Source code in lettrade/exchange/live/data.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def bars_load(
    self,
    since: int | str | pd.Timestamp,
    to: int | str | pd.Timestamp,
) -> bool:
    """Get bar from API and push to DataFeed

    Args:
        since (int | str | pd.Timestamp): _description_
        to (int | str | pd.Timestamp): _description_

    Returns:
        bool: True if has data, False if no data
    """
    bars = self.bars(since=since, to=to)

    if bars is None or len(bars) == 0:
        logger.warning("No bars data for %s", self.name)
        return False

    self.push(bars, unit=self._bar_datetime_unit)
    return True

drop ¤

drop(
    *args,
    since: int | str | Timestamp | None = None,
    to: int | str | Timestamp | None = None,
    **kwargs
) -> None

summary

Parameters:

  • since (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

  • to (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/data/data.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def drop(
    self,
    *args,
    since: int | str | pd.Timestamp | None = None,
    to: int | str | pd.Timestamp | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        since (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.
        to (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if since is None and to is None:
        super().drop(*args, **kwargs)
        return

    condiction = None

    # Since
    if since is not None:
        if isinstance(since, int):
            loc = self.l.index[since]
        elif isinstance(since, str):
            loc = pd.to_datetime(since, utc=True)
        elif isinstance(since, pd.Timestamp):
            loc = since
        else:
            raise RuntimeError(f"DataFeed.drop since {since} is invalid")
        condiction = self.index < loc

    # To
    if to is not None:
        if isinstance(to, int):
            loc = self.l.index[to]
        elif isinstance(to, str):
            loc = pd.to_datetime(to, utc=True)
        elif isinstance(to, pd.Timestamp):
            loc = to
        else:
            raise RuntimeError(f"DataFeed.drop to {to} is invalid")

        if condiction is None:
            condiction = self.index > loc
        else:
            condiction = condiction | (self.index > loc)

    index = self[condiction].index
    super().drop(index=index, inplace=True)
    self.l.reset()

    if __debug__:
        logger.debug("BackTestDataFeed %s dropped %s rows", self.name, len(index))

dump_csv ¤

dump_csv(
    path: str | None = None,
    since: int | str | datetime | None = 0,
    to: int | str | datetime | None = 1000,
    **kwargs
)

summary

Parameters:

  • path (str | None, default: None ) –

    description. Defaults to None.

  • since (int | str | datetime | None, default: 0 ) –

    description. Defaults to 0.

  • to (int | str | datetime | None, default: 1000 ) –

    description. Defaults to 1_000.

Source code in lettrade/exchange/live/data.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def dump_csv(
    self,
    path: str | None = None,
    since: int | str | datetime | None = 0,
    to: int | str | datetime | None = 1_000,
    **kwargs,
):
    """_summary_

    Args:
        path (str | None, optional): _description_. Defaults to None.
        since (int  |  str  |  datetime | None, optional): _description_. Defaults to 0.
        to (int  |  str  |  datetime | None, optional): _description_. Defaults to 1_000.
    """
    if self.empty:
        if isinstance(since, str):
            since = pd.to_datetime(since).to_pydatetime()
        if isinstance(to, str):
            to = pd.to_datetime(to).to_pydatetime()

        self.bars_load(since=since, to=to)

    if path is None:
        path = f"data/{self.name}-{since}_{to}.csv"

    from lettrade.data.extra.csv import csv_export

    csv_export(dataframe=self, path=path, **kwargs)

next ¤

next(size=1, tick=0) -> bool

Drop extra columns and load next DataFeed

Parameters:

  • size (int, default: 1 ) –

    description. Defaults to 1.

  • tick (int, default: 0 ) –

    description. Defaults to 0.

Returns:

  • bool ( bool ) –

    description

Source code in lettrade/exchange/live/data.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def next(self, size=1, tick=0) -> bool:
    """Drop extra columns and load next DataFeed

    Args:
        size (int, optional): _description_. Defaults to 1.
        tick (int, optional): _description_. Defaults to 0.

    Returns:
        bool: _description_
    """
    # Drop existed extra columns to skip reusing calculated data
    self.drop(columns=self.columns.difference(self._base_columns), inplace=True)

    self.bars_load(since=0, to=size + 1)
    self.l.go_stop()
    return True

push ¤

push(
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs
)

Push new rows to DataFeed

Parameters:

  • rows (list[list[int | float]]) –

    list of rows [["timestamp", "open price", "high price"...]]

  • unit (str | None, default: None ) –

    pandas.Timestamp parsing unit. Defaults to None.

  • utc (bool, default: True ) –

    description. Defaults to True.

Source code in lettrade/data/data.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def push(
    self,
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs,
):
    """Push new rows to DataFeed

    Args:
        rows (list[list[int | float]]): list of rows `[["timestamp", "open price", "high price"...]]`
        unit (str | None, optional): pandas.Timestamp parsing unit. Defaults to None.
        utc (bool, optional): _description_. Defaults to True.
    """
    for row in rows:
        dt = pd.to_datetime(row[0], unit=unit, utc=utc, **kwargs)
        self.at[
            dt,
            (
                "open",
                "high",
                "low",
                "close",
                "volume",
            ),
        ] = (
            row[1],  # open
            row[2],  # high
            row[3],  # low
            row[4],  # close
            row[5],  # volume
        )

    if __debug__:
        logger.debug("[%s] Update bar: \n%s", self.name, self.tail(len(rows)))

symbol_info ¤

symbol_info()

Get symbol information from API

Source code in lettrade/exchange/live/data.py
73
74
75
def symbol_info(self):
    """Get symbol information from API"""
    return self._api.market(symbol=self.symbol)

CCXTDataFeeder ¤

CCXTDataFeeder(
    api: LiveAPI | None = None,
    tick: bool = 5,
    start_size: int = 500,
    api_kwargs: dict | None = None,
    **kwargs
)

Bases: LiveDataFeeder

DataFeeder for CCXT

tick == 0: wait until new bar change value
tick > 0: sleep tick time (in seconds) then update
Source code in lettrade/exchange/live/feeder.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def __init__(
    self,
    api: LiveAPI | None = None,
    tick: bool = 5,
    start_size: int = 500,
    api_kwargs: dict | None = None,
    **kwargs,
) -> None:
    """
    tick:
        tick < 0: no tick, just get completed bar
        tick == 0: wait until new bar change value
        tick > 0: sleep tick time (in seconds) then update
    """
    super().__init__()

    # API init
    if api is None:
        if api_kwargs is None:
            raise RuntimeError("api or api_kwargs cannot missing")
        api = self._api_cls(**api_kwargs)

    self._api = api
    self._tick = tick
    self._start_size = start_size
    self._config = kwargs

    if isinstance(self._tick, int):
        self._wait_timeframe = TimeFrame(f"{self._tick}s")
    else:
        self._wait_timeframe = None

is_continous property ¤

is_continous

Flag check is realtime continous datafeeder

CCXTExchange ¤

CCXTExchange(api: LiveAPI, *args, **kwargs)

Bases: LiveExchange

MetaTrade 5 exchange module for lettrade

Parameters:

  • api (LiveAPI) –

    API connect to rpyc MeTrader 5 Terminal server through module mt5linux

  • *args (list, default: () ) –

    Exchange list parameters

  • **kwargs (dict, default: {} ) –

    Exchange dict parameters

Source code in lettrade/exchange/live/exchange.py
21
22
23
24
25
26
27
28
29
30
def __init__(self, api: LiveAPI, *args, **kwargs):
    """_summary_

    Args:
        api (LiveAPI): API connect to rpyc MeTrader 5 Terminal server through module `mt5linux`
        *args (list): `Exchange` list parameters
        **kwargs (dict): `Exchange` dict parameters
    """
    super().__init__(*args, **kwargs)
    self._api = api

data instance-attribute ¤

data: DataFeed

main DataFeed

datas instance-attribute ¤

datas: list[DataFeed]

List of all available DataFeed

executions instance-attribute ¤

executions: dict[str, Execution] = dict()

Execution dict by Execution.id key

history_orders instance-attribute ¤

history_orders: dict[str, Order] = dict()

History Order dict by Order.id key

history_positions instance-attribute ¤

history_positions: dict[str, Position] = dict()

History Position dict by Position.id key

orders instance-attribute ¤

orders: dict[str, Order] = dict()

Available Order dict by Order.id key

positions instance-attribute ¤

positions: dict[str, Position] = dict()

Available Position dict by Position.id key

init ¤

init(
    brain: Brain,
    feeder: DataFeeder,
    account: Account,
    commander: Commander,
) -> None

Init Exchange dependencies. Set data/datas from feeder. Set state to ExchangeState.Start.

Parameters:

Source code in lettrade/exchange/exchange.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def init(
    self,
    brain: "Brain",
    feeder: DataFeeder,
    account: Account,
    commander: Commander,
) -> None:
    """Init Exchange dependencies.
    Set data/datas from feeder.
    Set state to `ExchangeState.Start`.

    Args:
        brain (Brain): _description_
        feeder (DataFeeder): _description_
        account (Account): _description_
        commander (Commander): _description_
    """
    self._brain = brain
    self._feeder = feeder
    self._account = account
    self._commander = commander

    self.data = self._feeder.data
    self.datas = self._feeder.datas

    self._account.init(exchange=self)

    self._state = ExchangeState.Start

new_order ¤

new_order(
    size: float,
    type: OrderType | None = OrderType.Market,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    data: DataFeed | None = None,
    **kwargs
) -> OrderResult

Place new order to server

Parameters:

  • size (float) –

    description

  • type (OrderType | None, default: Market ) –

    description. Defaults to OrderType.Market.

  • limit (float | None, default: None ) –

    description. Defaults to None.

  • stop (float | None, default: None ) –

    description. Defaults to None.

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • tag (str | None, default: None ) –

    description. Defaults to None.

  • data (DataFeed | None, default: None ) –

    description. Defaults to None.

Returns:

Source code in lettrade/exchange/live/exchange.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def new_order(
    self,
    size: float,
    type: OrderType | None = OrderType.Market,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    data: DataFeed | None = None,
    **kwargs,
) -> OrderResult:
    """Place new order to server

    Args:
        size (float): _description_
        type (OrderType | None, optional): _description_. Defaults to OrderType.Market.
        limit (float | None, optional): _description_. Defaults to None.
        stop (float | None, optional): _description_. Defaults to None.
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        tag (str | None, optional): _description_. Defaults to None.
        data (DataFeed | None, optional): _description_. Defaults to None.

    Returns:
        OrderResult: _description_
    """
    if not data:
        data = self.data

    if type == OrderType.Market:
        limit = 0
        stop = 0

    order = self._order_cls(
        id=-1,
        exchange=self,
        data=data,
        size=size,
        type=type,
        limit_price=limit,
        stop_price=stop,
        sl_price=sl,
        tp_price=tp,
        tag=tag,
        **kwargs,
    )
    ok = order.place()

    # if __debug__:
    #     logger.info("New order %s at %s", order, self.data.now)

    return ok

next_next ¤

next_next()

Call after strategy.next()

Source code in lettrade/exchange/exchange.py
109
110
111
def next_next(self):
    """Call after strategy.next()"""
    self._account.next_next()

on_execution ¤

on_execution(
    execution: Execution,
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Execution event from exchange then store and notify Brain

Parameters:

  • execution (Execution) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Source code in lettrade/exchange/exchange.py
118
119
120
121
122
123
124
125
126
127
128
129
130
def on_execution(
    self,
    execution: Execution,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Execution event from exchange then store and notify Brain

    Args:
        execution (Execution): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.
    """
    self.on_executions(executions=[execution], broadcast=broadcast, **kwargs)

on_executions ¤

on_executions(
    executions: list[Execution],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Execution event from exchange then store and notify Brain

Source code in lettrade/exchange/exchange.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def on_executions(
    self,
    executions: list[Execution],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """
    Receive Execution event from exchange then store and notify Brain
    """
    for execution in executions:
        if not isinstance(execution, Execution):
            raise RuntimeError(f"{execution} is not instance of type Execution")

        if execution.id in self.executions:
            # Merge to keep Execution handler for strategy using
            # when strategy want to store Execution object
            # and object will be automatic update directly
            self.executions[execution.id].merge(execution)
            execution = self.executions[execution.id]
        else:
            self.executions[execution.id] = execution

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_executions(executions)

on_order ¤

on_order(
    order: Order, broadcast: bool | None = True, **kwargs
) -> None

Receive order event from exchange then store and notify Brain

Parameters:

  • order (Order) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Source code in lettrade/exchange/exchange.py
160
161
162
163
164
165
166
167
168
169
170
171
172
def on_order(
    self,
    order: Order,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive order event from exchange then store and notify Brain

    Args:
        order (Order): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.
    """
    self.on_orders(orders=[order], broadcast=broadcast, **kwargs)

on_orders ¤

on_orders(
    orders: list[Order],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive orders event from exchange then store and notify Brain

Parameters:

  • orders (list[Order]) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def on_orders(
    self,
    orders: list[Order],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive orders event from exchange then store and notify Brain

    Args:
        orders (list[Order]): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.

    Raises:
        RuntimeError: _description_
    """
    for order in orders:
        if not isinstance(order, Order):
            raise RuntimeError(f"{order} is not instance of type Order")

        if order.is_closed:
            if order.id in self.history_orders:
                logger.warning("Order closed recall: %s", order)

            self.history_orders[order.id] = order
            if order.id in self.orders:
                del self.orders[order.id]
        else:
            if order.id in self.history_orders:
                raise RuntimeError(f"Order {order.id} closed")

            if order.id in self.orders:
                # Merge to keep Order handler for strategy using
                # when strategy want to store Order object
                # and object will be automatic update directly
                self.orders[order.id].merge(order)
                order = self.orders[order.id]
            else:
                self.orders[order.id] = order

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_orders(orders)

on_position ¤

on_position(
    position: Position,
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Position event from exchange then store and notify Brain

Parameters:

  • position (Position) –

    new comming Position

  • broadcast (bool | None, default: True ) –

    Flag notify to Brain. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def on_position(
    self,
    position: Position,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Position event from exchange then store and notify Brain

    Args:
        position (Position): new comming `Position`
        broadcast (bool | None, optional): Flag notify to Brain. Defaults to True.

    Raises:
        RuntimeError: validate `Position` instance
    """
    self.on_positions(positions=[position], broadcast=broadcast, **kwargs)

on_positions ¤

on_positions(
    positions: list[Position],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Positions event from exchange then store and notify Brain

Parameters:

  • positions (list[Position]) –

    list of new comming Position

  • broadcast (bool | None, default: True ) –

    Flag notify to Brain. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def on_positions(
    self,
    positions: list[Position],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Positions event from exchange then store and notify Brain

    Args:
        positions (list[Position]): list of new comming `Position`
        broadcast (bool | None, optional): Flag notify to Brain. Defaults to True.

    Raises:
        RuntimeError: validate `Position` instance
    """
    for position in positions:
        if not isinstance(position, Position):
            raise RuntimeError(f"{position} is not instance of type Position")

        if position.is_exited:
            if position.id in self.history_positions:
                logger.warning("Position exited recall: %s", position)

            self.history_positions[position.id] = position
            if position.id in self.positions:
                del self.positions[position.id]

            # self._account._on_position_exit(position)
        else:
            if position.id in self.history_positions:
                raise RuntimeError(f"Position {position.id} closed: {position}")
            if position.id in self.positions:
                # Merge to keep Position handler for strategy using
                # when strategy want to store Position object
                # and object will be automatic update directly
                self.positions[position.id].merge(position)
                position = self.positions[position.id]
            else:
                self.positions[position.id] = position
                # self._account._on_position_entry(position)

    self._account.on_positions(positions)

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_positions(positions)

start ¤

start() -> None

Start Live exchange by: Sync positions from server

Source code in lettrade/exchange/live/exchange.py
32
33
34
35
def start(self) -> None:
    """Start Live exchange by: Sync positions from server"""
    self._api.start(exchange=self)
    return super().start()

stop ¤

stop() -> None

Stop Exchange

Source code in lettrade/exchange/exchange.py
113
114
115
116
def stop(self) -> None:
    """Stop Exchange"""
    self._state = ExchangeState.Stop
    self._account.stop()

CCXTExecution ¤

CCXTExecution(
    id: str,
    exchange: LiveExchange,
    data: LiveDataFeed,
    size: float,
    price: float,
    at: float,
    order_id: str | None = None,
    order: Order | None = None,
    position_id: str | None = None,
    position: Position | None = None,
    api: LiveAPI | None = None,
    raw: object | None = None,
    **kwargs
)

Bases: LiveExecution

Execution for CCXT

Source code in lettrade/exchange/live/trade.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def __init__(
    self,
    id: str,
    exchange: "LiveExchange",
    data: "LiveDataFeed",
    size: float,
    price: float,
    at: float,
    order_id: str | None = None,
    order: "Order | None" = None,
    position_id: str | None = None,
    position: "Position | None" = None,
    # tag: str | None = None,
    api: LiveAPI | None = None,
    raw: object | None = None,
    **kwargs,
):
    super().__init__(
        id=id,
        exchange=exchange,
        data=data,
        size=size,
        price=price,
        at=at,
        order_id=order_id,
        order=order,
        position_id=position_id,
        position=position,
        api=api,
        raw=raw,
        **kwargs,
    )

is_long property ¤

is_long: bool

True if side is long (size is positive).

Returns:

  • bool ( bool ) –

    True/False

is_short property ¤

is_short: bool

True if side is short (size is negative).

Returns:

  • bool ( bool ) –

    description

side property ¤

side: TradeSide

True if side is short (size is negative).

Returns:

from_raw classmethod ¤

from_raw(
    raw, exchange: LiveExchange, api: CCXTAPI = None
) -> CCXTExecution

Building new CCXTExecution from live api raw object

Parameters:

  • raw (_type_) –

    description

  • exchange (LiveExchange) –

    description

Returns:

Source code in lettrade/exchange/ccxt/trade.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@classmethod
def from_raw(
    cls,
    raw,
    exchange: "LiveExchange",
    api: CCXTAPI = None,
) -> "CCXTExecution":
    """Building new CCXTExecution from live api raw object

    Args:
        raw (_type_): _description_
        exchange (LiveExchange): _description_

    Returns:
        CCXTExecution: _description_
    """

    return cls(
        exchange=exchange,
        id=raw.ticket,
        # TODO: Fix by get data from symbol
        data=exchange.data,
        # TODO: size and type from raw.type
        size=raw.volume,
        price=raw.price,
        # TODO: set bar time
        at=None,
        order_id=raw.order,
        position_id=raw.position_id,
        tag=raw.comment,
        api=api,
        raw=raw,
    )

merge ¤

merge(other: Execution)

Merge to keep object handler but not overwrite for Strategy using when Strategy want to store object and object will be automatic update directly

Source code in lettrade/exchange/execution.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def merge(self, other: "Execution"):
    """
    Merge to keep object handler but not overwrite for Strategy using when Strategy want to store object and object will be automatic update directly
    """
    if other is self:
        return

    if self.id != other.id:
        raise RuntimeError(f"Merge difference id {self.id} != {other.id} execution")

    self.price = other.price
    self.size = other.size

    if other.at:
        self.at = other.at
    if other.order_id:
        self.order_id = other.order_id
    if other.order:
        self.order = other.order
    if other.position_id:
        self.position_id = other.position_id
    if other.position:
        self.position = other.position

CCXTOrder ¤

CCXTOrder(is_real: bool = True, **kwargs)

Bases: LiveOrder

Order for CCXT

Source code in lettrade/exchange/ccxt/trade.py
73
74
75
76
77
def __init__(self, is_real: bool = True, **kwargs):
    super().__init__(**kwargs)

    self.is_real: bool = is_real
    """Flag to check `Order` is real, cannot duplicate id, cannot recall from history"""

is_closed property ¤

is_closed: bool

Flag to check Order closed

Returns:

  • bool ( bool ) –

    True if state in [OrderState.Filled, OrderState.Canceled]

is_long property ¤

is_long: bool

True if side is long (size is positive).

Returns:

  • bool ( bool ) –

    True/False

is_opening property ¤

is_opening: bool

Flag to check Order still alive

Returns:

  • bool ( bool ) –

    True if state in [OrderState.Pending, OrderState.Placed, OrderState.Partial]

is_real instance-attribute ¤

is_real: bool = is_real

Flag to check Order is real, cannot duplicate id, cannot recall from history

is_short property ¤

is_short: bool

True if side is short (size is negative).

Returns:

  • bool ( bool ) –

    description

is_sl_order property ¤

is_sl_order: bool

Order is stop-loss order of a Position

Returns:

  • bool ( bool ) –

    description

is_tp_order property ¤

is_tp_order: bool

Order is take-profit order of a Position

Returns:

  • bool ( bool ) –

    description

limit property ¤

limit: float | None

Getter of limit_price

Returns:

  • float | None

    float | None: float or None

place_price property ¤

place_price: float | None

Getter of place_price

Returns:

  • float | None

    float | None: float or None

side property ¤

side: TradeSide

True if side is short (size is negative).

Returns:

sl property ¤

sl: float | None

Getter of sl_price

Returns:

  • float | None

    float | None: float or None

stop property ¤

stop: float | None

Getter of stop_price

Returns:

  • float | None

    float | None: float or None

tp property ¤

tp: float | None

Getter of tp_price

Returns:

  • float | None

    float | None: float or None

cancel ¤

cancel(**kwargs) -> OrderResult

Cancel order

Returns:

Source code in lettrade/exchange/ccxt/trade.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def cancel(self, **kwargs) -> OrderResult:
    """Cancel order

    Returns:
        OrderResult: _description_
    """
    if not self.parent:
        # Abandon order
        result = self._api.order_close(order=self, **kwargs)
    else:
        # Virtual SL/TP order of trade
        result = None

    return super(LiveOrder, self).cancel(raw=result)

fill ¤

fill(
    price: float, at: Timestamp, raw: object | None = None
) -> OrderResult

Fill Order. Set status to OrderState.Executed. Send event to Exchange

Parameters:

  • price (float) –

    Executed price

  • at (Timestamp) –

    Executed bar

Raises:

Returns:

Source code in lettrade/exchange/order.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
def fill(
    self,
    price: float,
    at: pd.Timestamp,
    raw: object | None = None,
) -> "OrderResult":
    """Fill `Order`.
    Set `status` to `OrderState.Executed`.
    Send event to `Exchange`

    Args:
        price (float): Executed price
        at (pd.Timestamp): Executed bar

    Raises:
        RuntimeError: _description_

    Returns:
        OrderResult: result of `Order`
    """
    if self.state != OrderState.Placed:
        raise RuntimeError(f"Order {self.id} state {self.state} is not Placed")

    self.filled_at = at
    self.filled_price = price
    self.state = OrderState.Filled
    self.exchange.on_order(self)
    return OrderResultOk(order=self, raw=raw)

from_position classmethod ¤

from_position(
    position: CCXTPosition,
    sl: float | None = None,
    tp: float | None = None,
) -> CCXTOrder

summary

Parameters:

  • position (CCXTPosition) –

    description

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

Raises:

Returns:

Source code in lettrade/exchange/ccxt/trade.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
@classmethod
def from_position(
    cls,
    position: "CCXTPosition",
    sl: float | None = None,
    tp: float | None = None,
) -> "CCXTOrder":
    """_summary_

    Args:
        position (CCXTPosition): _description_
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_

    Returns:
        CCXTOrder: _description_
    """
    if not sl and not tp:
        raise RuntimeError("not sl and not tp")
    return cls(
        id=f"{position.id}-{'sl' if sl else 'tp'}",
        exchange=position.exchange,
        data=position.data,
        state=OrderState.Placed,
        type=OrderType.Stop if sl else OrderType.Limit,
        size=-position.size,
        limit_price=tp,
        stop_price=sl,
        parent=position,
        placed_at=position.entry_at,
        is_real=False,
    )

from_raw classmethod ¤

from_raw(
    raw: Any,
    exchange: LiveExchange,
    api: CCXTAPI | None = None,
) -> CCXTOrder | None

summary

Parameters:

  • raw (Any) –

    description.

  • exchange (LiveExchange) –

    description.

  • api (CCXTAPI | None, default: None ) –

    description. Defaults to None.

Raises:

Returns:

  • CCXTOrder ( CCXTOrder | None ) –

    description

Source code in lettrade/exchange/ccxt/trade.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
@classmethod
def from_raw(
    cls,
    raw: Any,
    exchange: "LiveExchange",
    api: CCXTAPI | None = None,
) -> "CCXTOrder | None":
    """_summary_

    Args:
        raw (Any): _description_.
        exchange (LiveExchange): _description_.
        api (CCXTAPI | None, optional): _description_. Defaults to None.

    Raises:
        NotImplementedError: _description_

    Returns:
        CCXTOrder: _description_
    """
    # DataFeed
    data = None
    for d in exchange.datas:
        if d.symbol == raw.symbol:
            data = d
            break
    if data is None:
        logger.warning("Raw order %s is not handling %s", raw.symbol, raw)
        return

    # Prices & Side & Type
    limit_price = None
    stop_price = None
    match raw.type:
        case MT5.ORDER_TYPE_BUY:
            side = TradeSide.Buy
            type = OrderType.Market
        case MT5.ORDER_TYPE_SELL:
            side = TradeSide.Sell
            type = OrderType.Market
        case MT5.ORDER_TYPE_BUY_LIMIT:
            side = TradeSide.Buy
            type = OrderType.Limit
            limit_price = raw.price_open
        case MT5.ORDER_TYPE_SELL_LIMIT:
            side = TradeSide.Sell
            type = OrderType.Limit
            limit_price = raw.price_open
        case MT5.ORDER_TYPE_BUY_STOP:
            side = TradeSide.Buy
            type = OrderType.Stop
            stop_price = raw.price_open
        case MT5.ORDER_TYPE_SELL_STOP:
            side = TradeSide.Sell
            type = OrderType.Stop
            stop_price = raw.price_open
        # case MT5.ORDER_TYPE_BUY_STOP_LIMIT:
        #     side = TradeSide.Buy
        #     type = OrderType.StopLimit
        #     # TODO
        #     limit_price = raw.price_open
        #     stop_price = raw.price_open
        # case MT5.ORDER_TYPE_SELL_STOP_LIMIT:
        #     side = TradeSide.Sell
        #     type = OrderType.StopLimit
        #     # TODO
        #     limit_price = raw.price_open
        #     stop_price = raw.price_open
        # case MT5.ORDER_TYPE_CLOSE_BY:
        case _:
            raise NotImplementedError(
                f"Order type {raw.type} is not implement",
                raw,
            )
    # State
    match raw.state:
        case MT5.ORDER_STATE_STARTED:
            state = OrderState.Pending
        case MT5.ORDER_STATE_PLACED:
            state = OrderState.Placed
        case MT5.ORDER_STATE_CANCELED:
            state = OrderState.Canceled
        case MT5.ORDER_STATE_PARTIAL:
            state = OrderState.Partial
        case MT5.ORDER_STATE_FILLED:
            state = OrderState.Filled
        case MT5.ORDER_STATE_REJECTED:
            state = OrderState.Canceled
        case MT5.ORDER_STATE_EXPIRED:
            state = OrderState.Canceled
        case MT5.ORDER_STATE_REQUEST_ADD:
            state = OrderState.Placed
        case MT5.ORDER_STATE_REQUEST_MODIFY:
            state = OrderState.Placed
        case MT5.ORDER_STATE_REQUEST_CANCEL:
            state = OrderState.Canceled
        case _:
            raise NotImplementedError(
                f"Raw order state {raw.state} is not implement"
            )

    order = cls(
        exchange=exchange,
        id=raw.ticket,
        state=state,
        data=data,
        size=side * (raw.volume_current or raw.volume_initial),
        type=type,
        limit_price=limit_price,
        stop_price=stop_price,
        sl_price=raw.sl or None,
        tp_price=raw.tp or None,
        tag=raw.comment,
        placed_at=pd.to_datetime(raw.time_setup_msc, unit="ms", utc=True),
        api=api,
        raw=raw,
    )

    if hasattr(raw, "time_done_msc"):
        order.filled_price = raw.price_current
        order.filled_at = pd.to_datetime(raw.time_done_msc, unit="ms", utc=True)

    return order

merge ¤

merge(other: Order)

Update current Order variables by other Order

Parameters:

  • other (Order) –

    Merge source Order

Raises:

Source code in lettrade/exchange/order.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def merge(self, other: "Order"):
    """Update current `Order` variables by other `Order`

    Args:
        other (Order): Merge source `Order`

    Raises:
        RuntimeError: Validate same id
    """
    if other is self:
        return

    if self.id != other.id:
        raise RuntimeError(f"Merge difference id {self.id} != {other.id} order")

    self.size = other.size
    self.sl_price = other.sl_price
    self.tp_price = other.tp_price

    if other.limit_price:
        self.limit_price = other.limit_price
    if other.stop_price:
        self.stop_price = other.stop_price
    if other.placed_at:
        self.placed_at = other.placed_at

    if other.filled_price:
        self.filled_price = other.filled_price
    if other.filled_at:
        self.filled_at = other.filled_at

    if other.parent:
        self.parent = other.parent

place ¤

place() -> OrderResult

summary

Raises:

Returns:

Source code in lettrade/exchange/ccxt/trade.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def place(self) -> OrderResult:
    """_summary_

    Raises:
        RuntimeError: _description_

    Returns:
        OrderResult: _description_
    """
    if self.state != OrderState.Pending:
        raise RuntimeError(f"Order {self.id} state {self.state} is not Pending")

    try:
        result = self._api.order_open(self)

        self.raw = result
        self.id = result.order

        # TODO: get current order time
        return super(LiveOrder, self).place(at=self.data.l.index[0], raw=result)
    except LetLiveOrderInvalidException as e:
        error = OrderResultError(
            error=e.message,
            order=self,
            raw=e.raw,
        )
        logger.error("Place order %s", str(error))
        self.exchange.on_notify(error=error)
        return error

update ¤

update(
    limit_price: float | None = None,
    stop_price: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    caller: float | None = None,
    **kwargs
) -> OrderResult

summary

Parameters:

  • limit_price (float | None, default: None ) –

    description. Defaults to None.

  • stop_price (float | None, default: None ) –

    description. Defaults to None.

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • caller (float | None, default: None ) –

    description. Defaults to None.

Raises:

Returns:

Source code in lettrade/exchange/ccxt/trade.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def update(
    self,
    limit_price: float | None = None,
    stop_price: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    caller: float | None = None,
    **kwargs,
) -> OrderResult:
    """_summary_

    Args:
        limit_price (float | None, optional): _description_. Defaults to None.
        stop_price (float | None, optional): _description_. Defaults to None.
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        caller (float | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_

    Returns:
        OrderResult: _description_
    """
    if caller is self:
        raise RuntimeError(f"Order recusive update {self}")

    if self.parent is None:
        result = self._api.order_update(
            order=self,
            limit_price=limit_price,
            stop_price=stop_price,
            sl=sl,
            tp=tp,
            **kwargs,
        )
        return super(LiveOrder, self).update(
            limit_price=result.limit_price,
            stop_price=result.stop_price,
            sl=result.sl,
            tp=result.tp,
        )
    else:
        # SL/TP Order just a virtual order
        if caller is not self.parent:
            if self.is_sl_order:
                self.parent.update(sl=stop_price, caller=self)
            elif self.is_tp_order:
                self.parent.update(tp=limit_price, caller=self)
            else:
                raise RuntimeError(f"Abandon order {self}")

        return super(LiveOrder, self).update(
            limit_price=limit_price,
            stop_price=stop_price,
        )

CCXTPosition ¤

CCXTPosition(
    id: str,
    exchange: LiveExchange,
    data: LiveDataFeed,
    size: float,
    parent: Order,
    tag: str | None = None,
    state: PositionState = PositionState.Open,
    entry_price: float | None = None,
    entry_fee: float = 0.0,
    entry_at: int | None = None,
    sl_order: Order | None = None,
    tp_order: Order | None = None,
    api: LiveAPI | None = None,
    raw: object | None = None,
    **kwargs
)

Bases: LivePosition

Position for CCXT

Source code in lettrade/exchange/live/trade.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
def __init__(
    self,
    id: str,
    exchange: "LiveExchange",
    data: "LiveDataFeed",
    size: float,
    parent: Order,
    tag: str | None = None,
    state: PositionState = PositionState.Open,
    entry_price: float | None = None,
    entry_fee: float = 0.0,
    entry_at: int | None = None,
    sl_order: Order | None = None,
    tp_order: Order | None = None,
    api: LiveAPI | None = None,
    raw: object | None = None,
    **kwargs,
):
    super().__init__(
        id=id,
        exchange=exchange,
        data=data,
        size=size,
        parent=parent,
        tag=tag,
        state=state,
        entry_price=entry_price,
        entry_fee=entry_fee,
        entry_at=entry_at,
        sl_order=sl_order,
        tp_order=tp_order,
        api=api,
        raw=raw,
        **kwargs,
    )

fee property ¤

fee: float

Fee/Estimate Fee for trade

Returns:

  • float ( float ) –

    Fee

is_exited property ¤

is_exited: bool

Flag to check Position state.

Returns:

  • bool ( bool ) –

    True if the trade exited

is_long property ¤

is_long: bool

True if side is long (size is positive).

Returns:

  • bool ( bool ) –

    True/False

is_opening property ¤

is_opening: bool

Flag to check Position state.

Returns:

  • bool ( bool ) –

    True if the trade opening

is_short property ¤

is_short: bool

True if side is short (size is negative).

Returns:

  • bool ( bool ) –

    description

pl property ¤

pl: float

Estimate Profit or Loss of Position

Returns:

  • float ( float ) –

    PnL

side property ¤

side: TradeSide

True if side is short (size is negative).

Returns:

exit ¤

exit() -> PositionResult

summary

Returns:

Source code in lettrade/exchange/ccxt/trade.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
def exit(self) -> PositionResult:
    """_summary_

    Returns:
        PositionResult: _description_
    """
    result = self._api.position_close(position=self)
    if result.code != 0:
        logger.error("Update position %s", str(result))
        error = PositionResultError(
            error=result.error,
            position=self,
            raw=result,
        )
        self.exchange.on_notify(error=error)
        return error

    execution_raw = self._api.execution_get(id=result.execution_id)

    # TODO: execution object and event
    result.execution_raw = execution_raw

    return super(LivePosition, self).exit(
        price=result.price,
        at=pd.to_datetime(execution_raw.time_msc, unit="ms", utc=True),
        pl=execution_raw.profit,
        fee=execution_raw.fee + execution_raw.swap + execution_raw.commission,
        raw=result,
    )

from_raw classmethod ¤

from_raw(
    raw,
    exchange: LiveExchange,
    state: PositionState = PositionState.Open,
    data: LiveDataFeed = None,
    api: CCXTAPI = None,
) -> CCXTPosition

summary

Parameters:

  • raw (_type_) –

    description

  • exchange (LiveExchange) –

    description

  • data (LiveDataFeed, default: None ) –

    description. Defaults to None.

  • api (CCXTAPI, default: None ) –

    description. Defaults to None.

Raises:

Returns:

Source code in lettrade/exchange/ccxt/trade.py
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
@classmethod
def from_raw(
    cls,
    raw,
    exchange: "LiveExchange",
    state: PositionState = PositionState.Open,
    data: "LiveDataFeed" = None,
    api: CCXTAPI = None,
) -> "CCXTPosition":
    """_summary_

    Args:
        raw (_type_): _description_
        exchange (LiveExchange): _description_
        data (LiveDataFeed, optional): _description_. Defaults to None.
        api (CCXTAPI, optional): _description_. Defaults to None.

    Raises:
        NotImplementedError: _description_

    Returns:
        CCXTPosition: _description_
    """
    # DataFeed
    if data is None:
        for d in exchange.datas:
            if d.symbol == raw.symbol:
                data = d
                break
        if data is None:
            logger.warning("Raw position %s is not handling %s", raw.symbol, raw)
            return

    # Side
    match raw.type:
        case MT5.POSITION_TYPE_BUY:
            side = TradeSide.Buy
        case MT5.POSITION_TYPE_SELL:
            side = TradeSide.Sell
        case _:
            raise NotImplementedError(
                f"Position type {raw.type} is not implement",
                raw,
            )

    position = cls(
        exchange=exchange,
        id=raw.ticket,
        data=data,
        state=state,
        size=side * raw.volume,
        entry_price=raw.price_open,
        entry_fee=raw.swap,
        entry_at=pd.to_datetime(raw.time_msc, unit="ms", utc=True),
        parent=None,
        tag=raw.comment,
        api=api,
        raw=raw,
    )

    # SL
    if raw.sl > 0.0:
        position.sl_order = exchange._order_cls.from_position(
            position=position, sl=raw.sl
        )
        exchange.on_order(position.sl_order)

    # TP
    if raw.tp > 0.0:
        position.tp_order = exchange._order_cls.from_position(
            position=position, tp=raw.tp
        )
        exchange.on_order(position.tp_order)

    return position

merge ¤

merge(other: LivePosition) -> bool

Merge LivePosition from another

Parameters:

Returns:

  • bool ( bool ) –

    description

Source code in lettrade/exchange/live/trade.py
288
289
290
291
292
293
294
295
296
297
298
299
300
def merge(self, other: "LivePosition") -> bool:
    """Merge LivePosition from another

    Args:
        other (LivePosition): _description_

    Returns:
        bool: _description_
    """
    if not super().merge(other):
        return False
    self.raw = other.raw
    return True

update ¤

update(
    sl: float | None = None,
    tp: float | None = None,
    caller: float | None = None,
    **kwargs
) -> PositionResult

summary

Parameters:

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • caller (float | None, default: None ) –

    description. Defaults to None.

Raises:

Returns:

Source code in lettrade/exchange/ccxt/trade.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
def update(
    self,
    sl: float | None = None,
    tp: float | None = None,
    caller: float | None = None,
    **kwargs,
) -> PositionResult:
    """_summary_

    Args:
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        caller (float | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_

    Returns:
        PositionResult: _description_
    """
    if not sl and not tp:
        raise RuntimeError("Update sl=None and tp=None")
    if caller is self:
        raise RuntimeError(f"Position recusive update {self}")

    result = self._api.position_update(position=self, sl=sl, tp=tp)
    if result.code != 0:
        logger.error("Update position %s", str(result))
        error = PositionResultError(
            error=result.error,
            position=self,
            raw=result,
        )
        self.exchange.on_notify(error=error)
        return error

    if sl is not None:
        if self.sl_order:
            if caller is not self.sl_order:
                self.sl_order.update(stop_price=sl, caller=self)
        else:
            self.sl_order = self.exchange._order_cls.from_position(
                position=self, sl=sl
            )

    if tp is not None:
        if self.tp_order:
            if caller is not self.tp_order:
                self.tp_order.update(limit_price=tp, caller=self)
        else:
            self.tp_order = self.exchange._order_cls.from_position(
                position=self, tp=tp
            )

    return super(LivePosition, self).update(raw=result)

CSVBackTestDataFeed ¤

CSVBackTestDataFeed(
    path: str | None = None,
    csv: dict | None = None,
    name: str | None = None,
    timeframe: str | int | Timedelta | None = None,
    meta: dict | None = None,
    data: DataFeed | None = None,
    **kwargs: dict
)

Bases: BackTestDataFeed

Implement help to load DataFeed from csv file

Parameters:

  • path (str | None, default: None ) –

    Path to csv file. Defaults to None.

  • csv (dict | None, default: None ) –

    Reflect of pandas.read_csv() parameters. Defaults to None.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • timeframe (str | int | Timedelta | None, default: None ) –

    description. Defaults to None.

  • meta (dict | None, default: None ) –

    description. Defaults to None.

  • data (DataFeed | None, default: None ) –

    description. Defaults to None.

  • **kwargs (dict, default: {} ) –

    DataFeed dict parameters

Source code in lettrade/exchange/backtest/data.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def __init__(
    self,
    path: str | None = None,
    csv: dict | None = None,
    name: str | None = None,
    timeframe: str | int | pd.Timedelta | None = None,
    meta: dict | None = None,
    data: DataFeed | None = None,
    **kwargs: dict,
) -> None:
    """_summary_

    Args:
        path (str | None, optional): Path to csv file. Defaults to None.
        csv (dict | None, optional): Reflect of `pandas.read_csv()` parameters. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        timeframe (str | int | pd.Timedelta | None, optional): _description_. Defaults to None.
        meta (dict | None, optional): _description_. Defaults to None.
        data (DataFeed | None, optional): _description_. Defaults to None.
        **kwargs (dict): [DataFeed](../../data/data.md#lettrade.data.data.DataFeed) dict parameters
    """
    if name is None:
        name = _path_to_name(path)

    if data is None:
        csv_params = dict(
            index_col=0,
            parse_dates=["datetime"],
            delimiter=",",
            header=0,
        )
        if csv is not None:
            csv_params.update(**csv)

        data = pd.read_csv(path, **csv_params)

        if not isinstance(data.index, pd.DatetimeIndex):
            data.index = data.index.astype("datetime64[ns, UTC]")

    super().__init__(
        data=data,
        name=name,
        timeframe=timeframe,
        meta=meta,
        **kwargs,
    )

i property ¤

Alias to lettrade.indicator and using in DataFeed by call: DataFeed.i.indicator_name()

is_main property ¤

is_main: bool

Property to check DataFeed is main DataFeed or not

l instance-attribute ¤

LetTrade DataFeed wrapper using to manage index pointer of DataFeed

meta property ¤

meta: dict

Property to get metadata of DataFeed

name property ¤

name: str

Property to get name of DataFeed

now property ¤

now: Timestamp

Property to get current index value of DataFeed

timeframe property ¤

timeframe: TimeFrame

Property to get timeframe of DataFeed

bar ¤

bar(i: int = 0) -> Timestamp

Get current pd.Timestamp value of DataFeed

Parameters:

  • i (int, default: 0 ) –

    Index. Defaults to 0.

Returns:

  • Timestamp

    pd.Timestamp: description

Source code in lettrade/data/data.py
108
109
110
111
112
113
114
115
116
117
def bar(self, i: int = 0) -> pd.Timestamp:
    """Get current pd.Timestamp value of DataFeed

    Args:
        i (int, optional): Index. Defaults to 0.

    Returns:
        pd.Timestamp: _description_
    """
    return self.l.index[i]

copy ¤

copy(deep: bool = False, **kwargs) -> DataFeed

summary

Parameters:

  • deep (bool, default: False ) –

    description. Defaults to False.

Returns:

Source code in lettrade/data/data.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def copy(self, deep: bool = False, **kwargs) -> "DataFeed":
    """_summary_

    Args:
        deep (bool, optional): _description_. Defaults to False.

    Returns:
        DataFeed: _description_
    """
    df = super().copy(deep=deep)
    df = self.__class__(
        data=df,
        name=self.name,
        timeframe=self.timeframe,
        meta=self.meta.copy(),
        **kwargs,
    )
    return df

drop ¤

drop(
    *args,
    since: int | str | Timestamp | None = None,
    to: int | str | Timestamp | None = None,
    **kwargs
) -> None

summary

Parameters:

  • since (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

  • to (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/data/data.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def drop(
    self,
    *args,
    since: int | str | pd.Timestamp | None = None,
    to: int | str | pd.Timestamp | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        since (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.
        to (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if since is None and to is None:
        super().drop(*args, **kwargs)
        return

    condiction = None

    # Since
    if since is not None:
        if isinstance(since, int):
            loc = self.l.index[since]
        elif isinstance(since, str):
            loc = pd.to_datetime(since, utc=True)
        elif isinstance(since, pd.Timestamp):
            loc = since
        else:
            raise RuntimeError(f"DataFeed.drop since {since} is invalid")
        condiction = self.index < loc

    # To
    if to is not None:
        if isinstance(to, int):
            loc = self.l.index[to]
        elif isinstance(to, str):
            loc = pd.to_datetime(to, utc=True)
        elif isinstance(to, pd.Timestamp):
            loc = to
        else:
            raise RuntimeError(f"DataFeed.drop to {to} is invalid")

        if condiction is None:
            condiction = self.index > loc
        else:
            condiction = condiction | (self.index > loc)

    index = self[condiction].index
    super().drop(index=index, inplace=True)
    self.l.reset()

    if __debug__:
        logger.debug("BackTestDataFeed %s dropped %s rows", self.name, len(index))

push ¤

push(
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs
)

Push new rows to DataFeed

Parameters:

  • rows (list[list[int | float]]) –

    list of rows [["timestamp", "open price", "high price"...]]

  • unit (str | None, default: None ) –

    pandas.Timestamp parsing unit. Defaults to None.

  • utc (bool, default: True ) –

    description. Defaults to True.

Source code in lettrade/data/data.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def push(
    self,
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs,
):
    """Push new rows to DataFeed

    Args:
        rows (list[list[int | float]]): list of rows `[["timestamp", "open price", "high price"...]]`
        unit (str | None, optional): pandas.Timestamp parsing unit. Defaults to None.
        utc (bool, optional): _description_. Defaults to True.
    """
    for row in rows:
        dt = pd.to_datetime(row[0], unit=unit, utc=utc, **kwargs)
        self.at[
            dt,
            (
                "open",
                "high",
                "low",
                "close",
                "volume",
            ),
        ] = (
            row[1],  # open
            row[2],  # high
            row[3],  # low
            row[4],  # close
            row[5],  # volume
        )

    if __debug__:
        logger.debug("[%s] Update bar: \n%s", self.name, self.tail(len(rows)))

Commander ¤

Bases: ABC

Abstract class for strategy commander. Help to manage and report strategy real-time

init ¤

init(
    bot: LetTradeBot,
    brain: Brain,
    exchange: Exchange,
    strategy: Strategy,
)

Init commander dependencies

Parameters:

Source code in lettrade/commander/commander.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def init(
    self,
    bot: "LetTradeBot",
    brain: "Brain",
    exchange: "Exchange",
    strategy: "Strategy",
):
    """Init commander dependencies

    Args:
        bot (LetTradeBot): LetTradeBot object
        brain (Brain): Brain of bot
        exchange (Exchange): Manage bot trading
        strategy (Strategy): Strategy of bot
    """
    self.bot = bot
    self.brain = brain
    self.exchange = exchange
    self.strategy = strategy

    self._name = self.bot._name

DataFeed ¤

DataFeed(
    *args,
    name: str,
    timeframe: TimeFrame,
    meta: dict | None = None,
    **kwargs
)

Bases: DataFrame

Data for Strategy. A implement of pandas.DataFrame

Parameters:

  • name (str) –

    description

  • timeframe (TimeFrame) –

    description

  • meta (dict | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/data/data.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def __init__(
    self,
    *args,
    name: str,
    timeframe: TimeFrame,
    meta: dict | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        name (str): _description_
        timeframe (TimeFrame): _description_
        meta (dict | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    # Validate
    if not _data_name_pattern.match(name):
        raise RuntimeError(
            f"Bot name {name} is not valid format {_data_name_pattern}"
        )

    # Init
    super().__init__(*args, **kwargs)
    self._init_index()

    # Metadata
    if not meta:
        meta = dict()
    meta["name"] = name
    meta["timeframe"] = TimeFrame(timeframe)
    self.attrs = {"lt_meta": meta}

    # LetWrapper
    object.__setattr__(self, "l", LetDataFeedWrapper(self))

i property ¤

Alias to lettrade.indicator and using in DataFeed by call: DataFeed.i.indicator_name()

is_main property ¤

is_main: bool

Property to check DataFeed is main DataFeed or not

l instance-attribute ¤

LetTrade DataFeed wrapper using to manage index pointer of DataFeed

meta property ¤

meta: dict

Property to get metadata of DataFeed

name property ¤

name: str

Property to get name of DataFeed

now property ¤

now: Timestamp

Property to get current index value of DataFeed

timeframe property ¤

timeframe: TimeFrame

Property to get timeframe of DataFeed

bar ¤

bar(i: int = 0) -> Timestamp

Get current pd.Timestamp value of DataFeed

Parameters:

  • i (int, default: 0 ) –

    Index. Defaults to 0.

Returns:

  • Timestamp

    pd.Timestamp: description

Source code in lettrade/data/data.py
108
109
110
111
112
113
114
115
116
117
def bar(self, i: int = 0) -> pd.Timestamp:
    """Get current pd.Timestamp value of DataFeed

    Args:
        i (int, optional): Index. Defaults to 0.

    Returns:
        pd.Timestamp: _description_
    """
    return self.l.index[i]

copy ¤

copy(deep: bool = False, **kwargs) -> DataFeed

summary

Parameters:

  • deep (bool, default: False ) –

    description. Defaults to False.

Returns:

Source code in lettrade/data/data.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def copy(self, deep: bool = False, **kwargs) -> "DataFeed":
    """_summary_

    Args:
        deep (bool, optional): _description_. Defaults to False.

    Returns:
        DataFeed: _description_
    """
    df = super().copy(deep=deep)
    df = self.__class__(
        data=df,
        name=self.name,
        timeframe=self.timeframe,
        meta=self.meta.copy(),
        **kwargs,
    )
    return df

drop ¤

drop(
    *args,
    since: int | str | Timestamp | None = None,
    to: int | str | Timestamp | None = None,
    **kwargs
) -> None

summary

Parameters:

  • since (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

  • to (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/data/data.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def drop(
    self,
    *args,
    since: int | str | pd.Timestamp | None = None,
    to: int | str | pd.Timestamp | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        since (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.
        to (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if since is None and to is None:
        super().drop(*args, **kwargs)
        return

    condiction = None

    # Since
    if since is not None:
        if isinstance(since, int):
            loc = self.l.index[since]
        elif isinstance(since, str):
            loc = pd.to_datetime(since, utc=True)
        elif isinstance(since, pd.Timestamp):
            loc = since
        else:
            raise RuntimeError(f"DataFeed.drop since {since} is invalid")
        condiction = self.index < loc

    # To
    if to is not None:
        if isinstance(to, int):
            loc = self.l.index[to]
        elif isinstance(to, str):
            loc = pd.to_datetime(to, utc=True)
        elif isinstance(to, pd.Timestamp):
            loc = to
        else:
            raise RuntimeError(f"DataFeed.drop to {to} is invalid")

        if condiction is None:
            condiction = self.index > loc
        else:
            condiction = condiction | (self.index > loc)

    index = self[condiction].index
    super().drop(index=index, inplace=True)
    self.l.reset()

    if __debug__:
        logger.debug("BackTestDataFeed %s dropped %s rows", self.name, len(index))

next ¤

next(size=1)

Load next data

Parameters:

  • size (int, default: 1 ) –

    description. Defaults to 1.

Source code in lettrade/data/data.py
100
101
102
103
104
105
106
def next(self, size=1):
    """Load next data

    Args:
        size (int, optional): _description_. Defaults to 1.
    """
    self.l.next(size)

push ¤

push(
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs
)

Push new rows to DataFeed

Parameters:

  • rows (list[list[int | float]]) –

    list of rows [["timestamp", "open price", "high price"...]]

  • unit (str | None, default: None ) –

    pandas.Timestamp parsing unit. Defaults to None.

  • utc (bool, default: True ) –

    description. Defaults to True.

Source code in lettrade/data/data.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def push(
    self,
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs,
):
    """Push new rows to DataFeed

    Args:
        rows (list[list[int | float]]): list of rows `[["timestamp", "open price", "high price"...]]`
        unit (str | None, optional): pandas.Timestamp parsing unit. Defaults to None.
        utc (bool, optional): _description_. Defaults to True.
    """
    for row in rows:
        dt = pd.to_datetime(row[0], unit=unit, utc=utc, **kwargs)
        self.at[
            dt,
            (
                "open",
                "high",
                "low",
                "close",
                "volume",
            ),
        ] = (
            row[1],  # open
            row[2],  # high
            row[3],  # low
            row[4],  # close
            row[5],  # volume
        )

    if __debug__:
        logger.debug("[%s] Update bar: \n%s", self.name, self.tail(len(rows)))

ForexBackTestAccount ¤

ForexBackTestAccount(
    risk: float = 0.02,
    balance: float = 10000,
    commission: float = 0.2,
    margin: float = 1,
    leverage: float = 1,
    **kwargs
)

Bases: BackTestAccount

Forex backtest account helps to handle lot size

Parameters:

  • risk (float, default: 0.02 ) –

    description. Defaults to 0.02.

  • balance (float, default: 10000 ) –

    description. Defaults to 10_000.

  • commission (float, default: 0.2 ) –

    Commission fee is percent of size. Defaults to 0.2.

  • margin (float, default: 1 ) –

    description. Defaults to 1.

  • leverage (float, default: 1 ) –

    description. Defaults to 1.

  • **kwargs (dict, default: {} ) –
Source code in lettrade/exchange/backtest/account.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def __init__(
    self,
    risk: float = 0.02,
    balance: float = 10_000,
    commission: float = 0.2,
    margin: float = 1,
    leverage: float = 1,
    **kwargs,
) -> None:
    """Account for backtest

    Args:
        risk (float, optional): _description_. Defaults to 0.02.
        balance (float, optional): _description_. Defaults to 10_000.
        commission (float, optional): Commission fee is percent of size. Defaults to 0.2.
        margin (float, optional): _description_. Defaults to 1.
        leverage (float, optional): _description_. Defaults to 1.
        **kwargs (dict, optional): Mirror of [lettrade.account.Account()](site:/reference/account/account/#lettrade.account.account.Account).
    """
    super().__init__(
        risk=risk,
        balance=balance,
        margin=margin,
        leverage=leverage,
        **kwargs,
    )
    self._commission = commission

balance property ¤

balance: float

Balance of account

Returns:

  • float ( float ) –

    description

next ¤

next()

Next account

Source code in lettrade/account/account.py
73
74
def next(self):
    """Next account"""

next_next ¤

next_next()

Call after strategy.next()

Source code in lettrade/account/account.py
76
77
78
def next_next(self):
    """Call after strategy.next()"""
    self._equity_snapshot()

risk ¤

risk(side: TradeSide, size: float, **kwargs) -> float

Risk calculation

Source code in lettrade/account/account.py
87
88
89
90
91
def risk(self, side: "TradeSide", size: float, **kwargs) -> float:
    """Risk calculation"""
    if size is None:
        return side * abs(self._risk)
    return side * abs(size)

start ¤

start()

Start account

Source code in lettrade/account/account.py
70
71
def start(self):
    """Start account"""

stop ¤

stop()

Stop account

Source code in lettrade/account/account.py
80
81
82
83
84
85
def stop(self):
    """Stop account"""
    try:
        self._equity_snapshot()
    except LetAccountInsufficientException:
        pass

IndicatorPlotter ¤

IndicatorPlotter(
    dataframe: DataFrame,
    plotter: Callable,
    filter: Callable | Series | None = None,
    push: bool = True,
    **kwargs
)

Add indicator plotter to DataFrame

Source code in lettrade/indicator/plot.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def __init__(
    self,
    dataframe: pd.DataFrame,
    plotter: Callable,
    filter: Callable | pd.Series | None = None,
    push: bool = True,
    **kwargs,
) -> None:
    # self.dataframe: pd.DataFrame = dataframe
    self.plotter: Callable = plotter
    self.filter: Callable | pd.Series | None = filter
    self.kwargs = kwargs

    if push:
        indicator_push_plotter(dataframe=dataframe, ip=self)

LetTrade ¤

LetTrade(
    datas: DataFeed | list[DataFeed] | str | list[str],
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    name: str | None = None,
    bot: type[LetTradeBot] = LetTradeBot,
    **kwargs
)

Building new bot object and handle multiprocessing

Parameters:

Source code in lettrade/lettrade.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def __init__(
    self,
    datas: DataFeed | list[DataFeed] | str | list[str],
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    name: str | None = None,
    bot: type[LetTradeBot] = LetTradeBot,
    **kwargs,
) -> None:
    """_summary_

    Args:
        datas (DataFeed | list[DataFeed] | str | list[str]): _description_
        strategy (type[Strategy]): _description_
        feeder (type[DataFeeder]): _description_
        exchange (type[Exchange]): _description_
        account (type[Account]): _description_
        commander (type[Commander] | None, optional): _description_. Defaults to None.
        stats (type[BotStatistic], optional): _description_. Defaults to BotStatistic.
        plotter (type[Plotter] | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        bot (type[LetTradeBot], optional): _description_. Defaults to LetTradeBot.
    """
    self._kwargs = kwargs
    self._kwargs["strategy_cls"] = strategy
    self._kwargs["feeder_cls"] = feeder
    self._kwargs["exchange_cls"] = exchange
    self._kwargs["account_cls"] = account
    self._kwargs["commander_cls"] = commander
    self._kwargs["stats_cls"] = stats
    self._kwargs["plotter_cls"] = plotter
    self._kwargs["bot_cls"] = bot
    self._kwargs["name"] = name

    self._kwargs["datas"] = self._init_datafeeds(datas)

plot ¤

plot(*args, jump: dict | None = None, **kwargs)

Plot strategy/optimize result

BotPlotter

Miror of BotPlotter.plot(). Plotly implement PlotlyBotPlotter.plot().

Args: jump (dict | None, optional): Miror of BotPlotter.jump()

Example: Jump to position_id

lt.plot(
    jump=dict(position_id=1, range=300),
    layout=dict(height=2000),
)

OptimizePlotter

Miror of OptimizePlotter.plot(). Plotly implement PlotlyOptimizePlotter.plot().

Example:

lt.plot(layout=dict(height=2000))

Source code in lettrade/lettrade.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def plot(self, *args, jump: dict | None = None, **kwargs):
    """Plot strategy/optimize result

    BotPlotter:
        Miror of [BotPlotter.plot()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.plot).
        Plotly implement [PlotlyBotPlotter.plot()](site:/reference/plot/plotly/plotly/#lettrade.plot.plotly.plotly.PlotlyBotPlotter.plot).

        Args:
            `jump` (dict | None, optional): Miror of [BotPlotter.jump()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.jump)

        Example:
            Jump to position_id
                ```python
                lt.plot(
                    jump=dict(position_id=1, range=300),
                    layout=dict(height=2000),
                )
                ```

    OptimizePlotter:
        Miror of [OptimizePlotter.plot()](site:/reference/plot/optimize/#lettrade.plot.optimize.OptimizePlotter.plot).
        Plotly implement [PlotlyOptimizePlotter.plot()](site:/reference/exchange/backtest/plotly/optimize/#lettrade.exchange.backtest.plotly.optimize.PlotlyOptimizePlotter.plot).

        Example:
                ```python
                lt.plot(layout=dict(height=2000))
                ```
    """
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, jump=jump, **kwargs)

run ¤

run(worker: int | None = None, **kwargs)

Run LetTrade in single or multiple processing

Parameters:

  • worker (int | None, default: None ) –

    Number of processing. Defaults to None.

Source code in lettrade/lettrade.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def run(self, worker: int | None = None, **kwargs):
    """Run LetTrade in single or multiple processing

    Args:
        worker (int | None, optional): Number of processing. Defaults to None.
    """
    if worker or isinstance(self.data, list):
        if not isinstance(self.data, list):
            # self.data = self.datas
            self.datas = [self.data]

        worker = self._max_workers(worker)

        self._multiprocess()

        datas_source = self._kwargs.pop("datas")
        with ProcessPoolExecutor(max_workers=worker) as executor:
            futures = [
                executor.submit(
                    self._bot_cls.run_bot,
                    datas=datas,
                    id=i,
                    result="str",
                    **self._kwargs,
                )
                for i, datas in enumerate(datas_source)
            ]
            for future in futures:
                result = future.result()
                print(result)
    else:
        self._bot = self._bot_cls.run_bot(
            bot=self._bot,
            result="bot",
            **self._kwargs,
        )
        print(str(self._bot.stats))

start ¤

start(force: bool = False)

Start LetTrade by init bot object and loading datafeeds

Parameters:

  • force (bool, default: False ) –

    description. Defaults to False.

Source code in lettrade/lettrade.py
108
109
110
111
112
113
114
115
116
117
def start(self, force: bool = False):
    """Start LetTrade by init bot object and loading datafeeds

    Args:
        force (bool, optional): _description_. Defaults to False.
    """
    if force and self._bot is not None:
        self._bot = None

    self._bot = self._bot_cls.start_bot(bot=self._bot, **self._kwargs)

stop ¤

stop()

Stop LetTrade

Source code in lettrade/lettrade.py
183
184
185
186
187
188
def stop(self):
    """Stop LetTrade"""
    if self._bot is not None:
        return self._bot.stop()
    if self.stats:
        self.stats.stop()

LetTradeBackTest ¤

LetTradeBackTest(
    datas: DataFeed | list[DataFeed] | str | list[str],
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    name: str | None = None,
    bot: type[LetTradeBot] = LetTradeBot,
    **kwargs
)

Bases: LetTrade

Parameters:

Source code in lettrade/lettrade.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def __init__(
    self,
    datas: DataFeed | list[DataFeed] | str | list[str],
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    name: str | None = None,
    bot: type[LetTradeBot] = LetTradeBot,
    **kwargs,
) -> None:
    """_summary_

    Args:
        datas (DataFeed | list[DataFeed] | str | list[str]): _description_
        strategy (type[Strategy]): _description_
        feeder (type[DataFeeder]): _description_
        exchange (type[Exchange]): _description_
        account (type[Account]): _description_
        commander (type[Commander] | None, optional): _description_. Defaults to None.
        stats (type[BotStatistic], optional): _description_. Defaults to BotStatistic.
        plotter (type[Plotter] | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        bot (type[LetTradeBot], optional): _description_. Defaults to LetTradeBot.
    """
    self._kwargs = kwargs
    self._kwargs["strategy_cls"] = strategy
    self._kwargs["feeder_cls"] = feeder
    self._kwargs["exchange_cls"] = exchange
    self._kwargs["account_cls"] = account
    self._kwargs["commander_cls"] = commander
    self._kwargs["stats_cls"] = stats
    self._kwargs["plotter_cls"] = plotter
    self._kwargs["bot_cls"] = bot
    self._kwargs["name"] = name

    self._kwargs["datas"] = self._init_datafeeds(datas)

optimize ¤

optimize(
    multiprocessing: Literal["auto", "fork"] = "auto",
    workers: int | None = None,
    process_bar: bool = True,
    cache: str = "data/optimize",
    **kwargs
)

Backtest optimization

Parameters:

  • multiprocessing (str | None, default: 'auto' ) –

    description. Defaults to "auto".

Source code in lettrade/exchange/backtest/backtest.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def optimize(
    self,
    multiprocessing: Literal["auto", "fork"] = "auto",
    workers: int | None = None,
    process_bar: bool = True,
    cache: str = "data/optimize",
    **kwargs,
):
    """Backtest optimization

    Args:
        multiprocessing (str | None, optional): _description_. Defaults to "auto".
    """
    if self.data.l.pointer != 0:
        # TODO: Can drop unnecessary columns by snapshort data.columns from init time
        raise RuntimeError(
            "Optimize datas is not clean, don't run() backtest before optimize()"
        )

    # optimizes = list(product(*(zip(repeat(k), v) for k, v in kwargs.items())))
    optimizes = list(
        dict(zip(kwargs.keys(), values)) for values in product(*kwargs.values())
    )

    self._optimize_init(cache=cache, total=len(optimizes), process_bar=process_bar)

    # Run optimize in multiprocessing
    self._optimizes_multiproccess(
        optimizes=optimizes,
        multiprocessing=multiprocessing,
        workers=workers,
    )

    self.optimize_done()

optimize_cache ¤

optimize_cache(cache: str = 'data/optimize')

Load optimize results from cache

Parameters:

  • cache (str, default: 'data/optimize' ) –

    Cache directory. Defaults to "data/optimize".

Source code in lettrade/exchange/backtest/backtest.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def optimize_cache(self, cache: str = "data/optimize"):
    """Load optimize results from cache

    Args:
        cache (str, optional): Cache directory. Defaults to "data/optimize".
    """
    import json

    self._optimize_init(cache=cache, total=0, process_bar=False)
    cache_dir = self._kwargs["cache"]
    queue = self._kwargs["queue"]

    logger.info("Load caches from: %s", cache)

    for cache_file in os.listdir(cache_dir):
        if cache_file == "info.json":
            continue

        try:
            cache_path = f"{cache_dir}/{cache_file}"
            data = json.load(open(cache_path, encoding="utf-8"))

            queue.put(
                dict(
                    index=cache_file,
                    optimize=data["optimize"],
                    result=data["result"],
                )
            )
        except Exception as e:
            logger.warning("Loading cache %s error %s", cache_path, e)

    logger.info("Loaded %s caches", len(self._stats.results))

optimize_done ¤

optimize_done()

Clean and close optimize handlers

Source code in lettrade/exchange/backtest/backtest.py
434
435
436
def optimize_done(self):
    """Clean and close optimize handlers"""
    self._stats.done()

optimize_model ¤

optimize_model(
    params_parser: Callable[
        [Any], list[set[str, Any]]
    ] = None,
    result_parser: Callable[[Series], float] = None,
    total: int = 0,
    cache: str = "data/optimize",
    process_bar: bool = False,
    dumper: (
        Callable[[dict, LetTradeBackTest], None] | None
    ) = None,
) -> Callable[[Any], Any]

Optimize function help to integrated with external optimize trainer

Parameters:

  • params_parser (Callable[[Any], list[set[str, Any]]], default: None ) –

    Parse external parameters to bot parameters dict. Defaults to None.

  • result_parser (Callable[[Series], float], default: None ) –

    Parse bot result to external score. Defaults to None.

  • total (int, default: 0 ) –

    Total number of optimize if possible. Defaults to 0.

  • cache (str, default: 'data/optimize' ) –

    Cache directory to store optimize result. Defaults to "data/optimize".

  • process_bar (bool, default: False ) –

    Enable/Disable process bar. Defaults to False.

Raises:

Returns:

  • Callable[[Any], Any]

    Callable[[Any], Any]: Optimize model function

Source code in lettrade/exchange/backtest/backtest.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def optimize_model(
    self,
    params_parser: Callable[[Any], list[set[str, Any]]] = None,
    result_parser: Callable[[pd.Series], float] = None,
    total: int = 0,
    cache: str = "data/optimize",
    process_bar: bool = False,
    dumper: Callable[[dict, "LetTradeBackTest"], None] | None = None,
) -> Callable[[Any], Any]:
    """Optimize function help to integrated with external optimize trainer

    Args:
        params_parser (Callable[[Any], list[set[str, Any]]], optional): Parse external parameters to bot parameters dict. Defaults to None.
        result_parser (Callable[[pd.Series], float], optional): Parse bot result to external score. Defaults to None.
        total (int, optional): Total number of optimize if possible. Defaults to 0.
        cache (str, optional): Cache directory to store optimize result. Defaults to "data/optimize".
        process_bar (bool, optional): Enable/Disable process bar. Defaults to False.

    Raises:
        RuntimeError: _description_

    Returns:
        Callable[[Any], Any]: Optimize model function
    """
    if self.data.l.pointer != 0:
        raise RuntimeError(
            "Optimize datas is not clean, don't run() backtest before optimize()"
        )

    self._optimize_init(cache=cache, total=total, process_bar=process_bar)

    # Optimize parameters
    optimizer_kwargs = dict(
        main_pid=os.getpid(),
        params_parser=params_parser,
        result_parser=result_parser,
        kwargs=self._kwargs,
    )

    if dumper is not None:
        dumper(optimizer_kwargs, self)
    else:
        self.__class__._optimize_model_kwargs(optimizer_kwargs)

    return self.__class__._optimize_model

plot ¤

plot(*args, jump: dict | None = None, **kwargs)

Plot strategy/optimize result

BotPlotter

Miror of BotPlotter.plot(). Plotly implement PlotlyBotPlotter.plot().

Args: jump (dict | None, optional): Miror of BotPlotter.jump()

Example: Jump to position_id

lt.plot(
    jump=dict(position_id=1, range=300),
    layout=dict(height=2000),
)

OptimizePlotter

Miror of OptimizePlotter.plot(). Plotly implement PlotlyOptimizePlotter.plot().

Example:

lt.plot(layout=dict(height=2000))

Source code in lettrade/lettrade.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def plot(self, *args, jump: dict | None = None, **kwargs):
    """Plot strategy/optimize result

    BotPlotter:
        Miror of [BotPlotter.plot()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.plot).
        Plotly implement [PlotlyBotPlotter.plot()](site:/reference/plot/plotly/plotly/#lettrade.plot.plotly.plotly.PlotlyBotPlotter.plot).

        Args:
            `jump` (dict | None, optional): Miror of [BotPlotter.jump()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.jump)

        Example:
            Jump to position_id
                ```python
                lt.plot(
                    jump=dict(position_id=1, range=300),
                    layout=dict(height=2000),
                )
                ```

    OptimizePlotter:
        Miror of [OptimizePlotter.plot()](site:/reference/plot/optimize/#lettrade.plot.optimize.OptimizePlotter.plot).
        Plotly implement [PlotlyOptimizePlotter.plot()](site:/reference/exchange/backtest/plotly/optimize/#lettrade.exchange.backtest.plotly.optimize.PlotlyOptimizePlotter.plot).

        Example:
                ```python
                lt.plot(layout=dict(height=2000))
                ```
    """
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, jump=jump, **kwargs)

stop ¤

stop()

Stop LetTrade

Source code in lettrade/lettrade.py
183
184
185
186
187
188
def stop(self):
    """Stop LetTrade"""
    if self._bot is not None:
        return self._bot.stop()
    if self.stats:
        self.stats.stop()

LetTradeBackTestBot ¤

LetTradeBackTestBot(
    datas: DataFeed | list[DataFeed] | list[str] | str,
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    plotter: type[Plotter] | None = None,
    stats: type[BotStatistic] | None = None,
    name: str | None = None,
    **kwargs
)

Bases: LetTradeBot

LetTradeBot for backtest

Parameters:

Source code in lettrade/bot.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def __init__(
    self,
    datas: DataFeed | list[DataFeed] | list[str] | str,
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    plotter: type[Plotter] | None = None,
    stats: type[BotStatistic] | None = None,
    name: str | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        datas (DataFeed | list[DataFeed] | list[str] | str): _description_
        strategy (type[Strategy]): _description_
        feeder (type[DataFeeder]): _description_
        exchange (type[Exchange]): _description_
        account (type[Account]): _description_
        commander (type[Commander] | None, optional): _description_. Defaults to None.
        plotter (type[Plotter] | None, optional): _description_. Defaults to None.
        stats (type[BotStatistic] | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
    """
    logger.info("New bot: %s", name)

    self._strategy_cls = strategy
    self._feeder_cls = feeder
    self._exchange_cls = exchange
    self._account_cls = account
    self._commander_cls = commander
    self._plotter_cls = plotter
    self._stats_cls = stats

    self._name = name
    self._kwargs = kwargs

    # DataFeeds
    self.datas = datas
    self.data = self.datas[0]

account instance-attribute ¤

account: Account

Trading account handler

brain instance-attribute ¤

brain: Brain

Brain of bot

commander class-attribute instance-attribute ¤

commander: Commander | None = None

Control the bot

data instance-attribute ¤

data: DataFeed = datas[0]

Main DataFeed for bot

datas instance-attribute ¤

datas: list[DataFeed] = datas

DataFeed list for bot

exchange instance-attribute ¤

exchange: Exchange

Trading exchange and events

feeder instance-attribute ¤

feeder: DataFeeder

DataFeeder help to handle datas

plotter class-attribute instance-attribute ¤

plotter: Plotter | None = None

Plot graphic results

strategy instance-attribute ¤

strategy: Strategy

Strategy

new_bot classmethod ¤

new_bot(
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs
) -> LetTradeBot

Create new bot object

Parameters:

Returns:

Source code in lettrade/bot.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
@classmethod
def new_bot(
    cls,
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs,
) -> "LetTradeBot":
    """Create new bot object

    Args:
        datas (list[DataFeed]): _description_
        strategy_cls (type[Strategy]): _description_
        feeder_cls (type[DataFeeder]): _description_
        exchange_cls (type[Exchange]): _description_
        account_cls (type[Account]): _description_
        commander_cls (type[Commander]): _description_
        plotter_cls (type[Plotter]): _description_
        stats_cls (type[BotStatistic]): _description_
        name (str | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    bot = cls(
        strategy=strategy_cls,
        datas=datas,
        feeder=feeder_cls,
        exchange=exchange_cls,
        account=account_cls,
        commander=commander_cls,
        plotter=plotter_cls,
        stats=stats_cls,
        name=name,
        **kwargs,
    )
    return bot

plot ¤

plot(*args, **kwargs)

Plot bot result

Source code in lettrade/bot.py
186
187
188
189
190
191
192
193
194
def plot(self, *args, **kwargs):
    """Plot bot result"""
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, **kwargs)

run ¤

run()

Run bot

Source code in lettrade/bot.py
173
174
175
176
177
178
179
180
181
182
183
184
def run(self):
    """Run bot"""
    if self.commander:
        self.commander.start()

    self.brain.run()

    if self.commander:
        self.commander.stop()

    if self._stats_cls:
        self.stats.compute()

run_bot classmethod ¤

run_bot(
    bot: LetTradeBot | None = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs
) -> LetTradeBot | BotStatistic | str | None

Run bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

  • datas (list[DataFeed] | None, default: None ) –

    description. Defaults to None.

  • id (int | None, default: None ) –

    description. Defaults to None.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • result (Literal['str', 'stats', 'bot', None], default: 'str' ) –

    description. Defaults to "str".

Returns:

  • LetTradeBot | BotStatistic | str | None

    LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing. The cost higher from str, stats object, bot object

Source code in lettrade/bot.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
@classmethod
def run_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs,
) -> "LetTradeBot | BotStatistic | str | None":
    """Run bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.
        datas (list[DataFeed] | None, optional): _description_. Defaults to None.
        id (int | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        result (Literal["str", "stats", "bot", None], optional): _description_. Defaults to "str".

    Returns:
        LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing.
            The cost higher from `str`, `stats` object, `bot` object
    """
    # Set name for current processing
    if name is None:
        d = datas[0] if datas else bot.data
        name = f"{id}-{os.getpid()}-{d.name}"

    if bot is None:
        bot = cls.start_bot(
            datas=datas,
            name=name,
            **kwargs,
        )

    # bot
    bot.run(**kwargs.get("run_kwargs", {}))

    # Return type
    if result == "stats":
        return bot.stats
    if result == "str":
        return str(bot.stats)

    return bot

start ¤

start()

Start bot

Source code in lettrade/bot.py
163
164
165
166
167
168
169
170
171
def start(self):
    """Start bot"""
    if not hasattr(self, "brain"):
        self.init()

    self.brain.start()

    if __debug__:
        logger.debug("Bot %s started with %d datas", self._name, len(self.datas))

start_bot classmethod ¤

start_bot(
    bot: LetTradeBot | None = None, **kwargs
) -> LetTradeBot

Init and start bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

Returns:

Source code in lettrade/bot.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
@classmethod
def start_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    **kwargs,
) -> "LetTradeBot":
    """Init and start bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    if bot is None:
        bot = cls.new_bot(**kwargs)
    bot.init(**kwargs.get("init_kwargs", {}))
    bot.start()
    return bot

stop ¤

stop()

Stop bot

Source code in lettrade/bot.py
196
197
198
199
200
def stop(self):
    """Stop bot"""
    self.brain.stop()
    if self.plotter is not None:
        self.plotter.stop()

LetTradeBot ¤

LetTradeBot(
    datas: DataFeed | list[DataFeed] | list[str] | str,
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    plotter: type[Plotter] | None = None,
    stats: type[BotStatistic] | None = None,
    name: str | None = None,
    **kwargs
)

Bot object helps to manage actions of bot

Parameters:

Source code in lettrade/bot.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def __init__(
    self,
    datas: DataFeed | list[DataFeed] | list[str] | str,
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    plotter: type[Plotter] | None = None,
    stats: type[BotStatistic] | None = None,
    name: str | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        datas (DataFeed | list[DataFeed] | list[str] | str): _description_
        strategy (type[Strategy]): _description_
        feeder (type[DataFeeder]): _description_
        exchange (type[Exchange]): _description_
        account (type[Account]): _description_
        commander (type[Commander] | None, optional): _description_. Defaults to None.
        plotter (type[Plotter] | None, optional): _description_. Defaults to None.
        stats (type[BotStatistic] | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
    """
    logger.info("New bot: %s", name)

    self._strategy_cls = strategy
    self._feeder_cls = feeder
    self._exchange_cls = exchange
    self._account_cls = account
    self._commander_cls = commander
    self._plotter_cls = plotter
    self._stats_cls = stats

    self._name = name
    self._kwargs = kwargs

    # DataFeeds
    self.datas = datas
    self.data = self.datas[0]

account instance-attribute ¤

account: Account

Trading account handler

brain instance-attribute ¤

brain: Brain

Brain of bot

commander class-attribute instance-attribute ¤

commander: Commander | None = None

Control the bot

data instance-attribute ¤

data: DataFeed = datas[0]

Main DataFeed for bot

datas instance-attribute ¤

datas: list[DataFeed] = datas

DataFeed list for bot

exchange instance-attribute ¤

exchange: Exchange

Trading exchange and events

feeder instance-attribute ¤

feeder: DataFeeder

DataFeeder help to handle datas

plotter class-attribute instance-attribute ¤

plotter: Plotter | None = None

Plot graphic results

strategy instance-attribute ¤

strategy: Strategy

Strategy

init ¤

init()

Init objects from classes

Source code in lettrade/bot.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def init(self):
    """Init objects from classes"""
    # Feeder
    self.feeder = self._feeder_cls(**self._kwargs.get("feeder_kwargs", {}))
    self.feeder.init(self.datas)

    # Account
    self.account = self._account_cls(**self._kwargs.get("account_kwargs", {}))

    # Exchange
    self.exchange = self._exchange_cls(**self._kwargs.get("exchange_kwargs", {}))

    # Commander
    if self._commander_cls:
        self.commander = self._commander_cls(
            **self._kwargs.get("commander_kwargs", {})
        )

    # Strategy
    self.strategy = self._strategy_cls(
        feeder=self.feeder,
        exchange=self.exchange,
        account=self.account,
        commander=self.commander,
        **self._kwargs.get("strategy_kwargs", {}),
    )

    # Brain
    self.brain = Brain(
        strategy=self.strategy,
        exchange=self.exchange,
        feeder=self.feeder,
        commander=self.commander,
        **self._kwargs.get("brain_kwargs", {}),
    )

    # Init
    if self.commander:
        self.commander.init(
            bot=self,
            brain=self.brain,
            exchange=self.exchange,
            strategy=self.strategy,
        )
    self.exchange.init(
        brain=self.brain,
        feeder=self.feeder,
        account=self.account,
        commander=self.commander,
    )

    # Stats
    self.stats = self._stats_cls(
        feeder=self.feeder,
        exchange=self.exchange,
        strategy=self.strategy,
        **self._kwargs.get("stats_kwargs", {}),
    )

    # Plotter
    if self._plotter_cls:
        self.plotter = self._plotter_cls(
            self,
            **self._kwargs.get("plotter_kwargs", {}),
        )

    if __debug__:
        logger.debug("Bot %s inited with %d datas", self._name, len(self.datas))

new_bot classmethod ¤

new_bot(
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs
) -> LetTradeBot

Create new bot object

Parameters:

Returns:

Source code in lettrade/bot.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
@classmethod
def new_bot(
    cls,
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs,
) -> "LetTradeBot":
    """Create new bot object

    Args:
        datas (list[DataFeed]): _description_
        strategy_cls (type[Strategy]): _description_
        feeder_cls (type[DataFeeder]): _description_
        exchange_cls (type[Exchange]): _description_
        account_cls (type[Account]): _description_
        commander_cls (type[Commander]): _description_
        plotter_cls (type[Plotter]): _description_
        stats_cls (type[BotStatistic]): _description_
        name (str | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    bot = cls(
        strategy=strategy_cls,
        datas=datas,
        feeder=feeder_cls,
        exchange=exchange_cls,
        account=account_cls,
        commander=commander_cls,
        plotter=plotter_cls,
        stats=stats_cls,
        name=name,
        **kwargs,
    )
    return bot

plot ¤

plot(*args, **kwargs)

Plot bot result

Source code in lettrade/bot.py
186
187
188
189
190
191
192
193
194
def plot(self, *args, **kwargs):
    """Plot bot result"""
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, **kwargs)

run ¤

run()

Run bot

Source code in lettrade/bot.py
173
174
175
176
177
178
179
180
181
182
183
184
def run(self):
    """Run bot"""
    if self.commander:
        self.commander.start()

    self.brain.run()

    if self.commander:
        self.commander.stop()

    if self._stats_cls:
        self.stats.compute()

run_bot classmethod ¤

run_bot(
    bot: LetTradeBot | None = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs
) -> LetTradeBot | BotStatistic | str | None

Run bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

  • datas (list[DataFeed] | None, default: None ) –

    description. Defaults to None.

  • id (int | None, default: None ) –

    description. Defaults to None.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • result (Literal['str', 'stats', 'bot', None], default: 'str' ) –

    description. Defaults to "str".

Returns:

  • LetTradeBot | BotStatistic | str | None

    LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing. The cost higher from str, stats object, bot object

Source code in lettrade/bot.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
@classmethod
def run_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs,
) -> "LetTradeBot | BotStatistic | str | None":
    """Run bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.
        datas (list[DataFeed] | None, optional): _description_. Defaults to None.
        id (int | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        result (Literal["str", "stats", "bot", None], optional): _description_. Defaults to "str".

    Returns:
        LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing.
            The cost higher from `str`, `stats` object, `bot` object
    """
    # Set name for current processing
    if name is None:
        d = datas[0] if datas else bot.data
        name = f"{id}-{os.getpid()}-{d.name}"

    if bot is None:
        bot = cls.start_bot(
            datas=datas,
            name=name,
            **kwargs,
        )

    # bot
    bot.run(**kwargs.get("run_kwargs", {}))

    # Return type
    if result == "stats":
        return bot.stats
    if result == "str":
        return str(bot.stats)

    return bot

start ¤

start()

Start bot

Source code in lettrade/bot.py
163
164
165
166
167
168
169
170
171
def start(self):
    """Start bot"""
    if not hasattr(self, "brain"):
        self.init()

    self.brain.start()

    if __debug__:
        logger.debug("Bot %s started with %d datas", self._name, len(self.datas))

start_bot classmethod ¤

start_bot(
    bot: LetTradeBot | None = None, **kwargs
) -> LetTradeBot

Init and start bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

Returns:

Source code in lettrade/bot.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
@classmethod
def start_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    **kwargs,
) -> "LetTradeBot":
    """Init and start bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    if bot is None:
        bot = cls.new_bot(**kwargs)
    bot.init(**kwargs.get("init_kwargs", {}))
    bot.start()
    return bot

stop ¤

stop()

Stop bot

Source code in lettrade/bot.py
196
197
198
199
200
def stop(self):
    """Stop bot"""
    self.brain.stop()
    if self.plotter is not None:
        self.plotter.stop()

LetTradeCCXT ¤

LetTradeCCXT(
    feeder: type[CCXTDataFeeder] = CCXTDataFeeder,
    exchange: type[CCXTExchange] = CCXTExchange,
    account: type[CCXTAccount] = CCXTAccount,
    **kwargs
)

Bases: LetTradeLive

Help to maintain CCXT bots

Parameters:

Source code in lettrade/exchange/ccxt/ccxt.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __init__(
    self,
    feeder: type[CCXTDataFeeder] = CCXTDataFeeder,
    exchange: type[CCXTExchange] = CCXTExchange,
    account: type[CCXTAccount] = CCXTAccount,
    **kwargs,
) -> None:
    """_summary_

    Args:
        feeder (Type[CCXTDataFeeder], optional): _description_. Defaults to CCXTDataFeeder.
        exchange (Type[CCXTExchange], optional): _description_. Defaults to CCXTExchange.
        account (Type[CCXTAccount], optional): _description_. Defaults to CCXTAccount.
    """
    super().__init__(
        feeder=feeder,
        exchange=exchange,
        account=account,
        **kwargs,
    )

plot ¤

plot(*args, jump: dict | None = None, **kwargs)

Plot strategy/optimize result

BotPlotter

Miror of BotPlotter.plot(). Plotly implement PlotlyBotPlotter.plot().

Args: jump (dict | None, optional): Miror of BotPlotter.jump()

Example: Jump to position_id

lt.plot(
    jump=dict(position_id=1, range=300),
    layout=dict(height=2000),
)

OptimizePlotter

Miror of OptimizePlotter.plot(). Plotly implement PlotlyOptimizePlotter.plot().

Example:

lt.plot(layout=dict(height=2000))

Source code in lettrade/lettrade.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def plot(self, *args, jump: dict | None = None, **kwargs):
    """Plot strategy/optimize result

    BotPlotter:
        Miror of [BotPlotter.plot()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.plot).
        Plotly implement [PlotlyBotPlotter.plot()](site:/reference/plot/plotly/plotly/#lettrade.plot.plotly.plotly.PlotlyBotPlotter.plot).

        Args:
            `jump` (dict | None, optional): Miror of [BotPlotter.jump()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.jump)

        Example:
            Jump to position_id
                ```python
                lt.plot(
                    jump=dict(position_id=1, range=300),
                    layout=dict(height=2000),
                )
                ```

    OptimizePlotter:
        Miror of [OptimizePlotter.plot()](site:/reference/plot/optimize/#lettrade.plot.optimize.OptimizePlotter.plot).
        Plotly implement [PlotlyOptimizePlotter.plot()](site:/reference/exchange/backtest/plotly/optimize/#lettrade.exchange.backtest.plotly.optimize.PlotlyOptimizePlotter.plot).

        Example:
                ```python
                lt.plot(layout=dict(height=2000))
                ```
    """
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, jump=jump, **kwargs)

run ¤

run(worker: int | None = None, **kwargs)

Run LetTrade in single or multiple processing

Parameters:

  • worker (int | None, default: None ) –

    Number of processing. Defaults to None.

Source code in lettrade/lettrade.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def run(self, worker: int | None = None, **kwargs):
    """Run LetTrade in single or multiple processing

    Args:
        worker (int | None, optional): Number of processing. Defaults to None.
    """
    if worker or isinstance(self.data, list):
        if not isinstance(self.data, list):
            # self.data = self.datas
            self.datas = [self.data]

        worker = self._max_workers(worker)

        self._multiprocess()

        datas_source = self._kwargs.pop("datas")
        with ProcessPoolExecutor(max_workers=worker) as executor:
            futures = [
                executor.submit(
                    self._bot_cls.run_bot,
                    datas=datas,
                    id=i,
                    result="str",
                    **self._kwargs,
                )
                for i, datas in enumerate(datas_source)
            ]
            for future in futures:
                result = future.result()
                print(result)
    else:
        self._bot = self._bot_cls.run_bot(
            bot=self._bot,
            result="bot",
            **self._kwargs,
        )
        print(str(self._bot.stats))

start ¤

start(force: bool = False)

Start LetTrade by init bot object and loading datafeeds

Parameters:

  • force (bool, default: False ) –

    description. Defaults to False.

Source code in lettrade/lettrade.py
108
109
110
111
112
113
114
115
116
117
def start(self, force: bool = False):
    """Start LetTrade by init bot object and loading datafeeds

    Args:
        force (bool, optional): _description_. Defaults to False.
    """
    if force and self._bot is not None:
        self._bot = None

    self._bot = self._bot_cls.start_bot(bot=self._bot, **self._kwargs)

stop ¤

stop()

Stop LetTrade

Source code in lettrade/lettrade.py
183
184
185
186
187
188
def stop(self):
    """Stop LetTrade"""
    if self._bot is not None:
        return self._bot.stop()
    if self.stats:
        self.stats.stop()

LetTradeCCXTBot ¤

LetTradeCCXTBot(api: LiveAPI | None = LiveAPI, **kwargs)

Bases: LetTradeLiveBot

LetTradeBot for CCXT

Parameters:

  • api (LiveAPI | None, default: LiveAPI ) –

    description. Defaults to LiveAPI.

Source code in lettrade/exchange/live/live.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def __init__(self, api: LiveAPI | None = LiveAPI, **kwargs) -> None:
    """_summary_

    Args:
        api (LiveAPI | None, optional): _description_. Defaults to LiveAPI.
    """
    super().__init__(**kwargs)

    if issubclass(api, LiveAPI):
        self._api = api(**self._kwargs.get("api_kwargs", {}))
    else:
        self._api = api

    self._kwargs.setdefault("feeder_kwargs", dict()).update(api=self._api)
    self._kwargs.setdefault("exchange_kwargs", dict()).update(api=self._api)
    self._kwargs.setdefault("account_kwargs", dict()).update(api=self._api)

    for data in self.datas:
        data._api = self._api

account instance-attribute ¤

account: Account

Trading account handler

brain instance-attribute ¤

brain: Brain

Brain of bot

commander class-attribute instance-attribute ¤

commander: Commander | None = None

Control the bot

data instance-attribute ¤

data: DataFeed = datas[0]

Main DataFeed for bot

exchange instance-attribute ¤

exchange: Exchange

Trading exchange and events

feeder instance-attribute ¤

feeder: DataFeeder

DataFeeder help to handle datas

plotter class-attribute instance-attribute ¤

plotter: Plotter | None = None

Plot graphic results

strategy instance-attribute ¤

strategy: Strategy

Strategy

new_bot classmethod ¤

new_bot(
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs
) -> LetTradeBot

Create new bot object

Parameters:

Returns:

Source code in lettrade/bot.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
@classmethod
def new_bot(
    cls,
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs,
) -> "LetTradeBot":
    """Create new bot object

    Args:
        datas (list[DataFeed]): _description_
        strategy_cls (type[Strategy]): _description_
        feeder_cls (type[DataFeeder]): _description_
        exchange_cls (type[Exchange]): _description_
        account_cls (type[Account]): _description_
        commander_cls (type[Commander]): _description_
        plotter_cls (type[Plotter]): _description_
        stats_cls (type[BotStatistic]): _description_
        name (str | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    bot = cls(
        strategy=strategy_cls,
        datas=datas,
        feeder=feeder_cls,
        exchange=exchange_cls,
        account=account_cls,
        commander=commander_cls,
        plotter=plotter_cls,
        stats=stats_cls,
        name=name,
        **kwargs,
    )
    return bot

plot ¤

plot(*args, **kwargs)

Plot bot result

Source code in lettrade/bot.py
186
187
188
189
190
191
192
193
194
def plot(self, *args, **kwargs):
    """Plot bot result"""
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, **kwargs)

run ¤

run()

Run bot

Source code in lettrade/bot.py
173
174
175
176
177
178
179
180
181
182
183
184
def run(self):
    """Run bot"""
    if self.commander:
        self.commander.start()

    self.brain.run()

    if self.commander:
        self.commander.stop()

    if self._stats_cls:
        self.stats.compute()

run_bot classmethod ¤

run_bot(
    bot: LetTradeBot | None = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs
) -> LetTradeBot | BotStatistic | str | None

Run bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

  • datas (list[DataFeed] | None, default: None ) –

    description. Defaults to None.

  • id (int | None, default: None ) –

    description. Defaults to None.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • result (Literal['str', 'stats', 'bot', None], default: 'str' ) –

    description. Defaults to "str".

Returns:

  • LetTradeBot | BotStatistic | str | None

    LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing. The cost higher from str, stats object, bot object

Source code in lettrade/bot.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
@classmethod
def run_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs,
) -> "LetTradeBot | BotStatistic | str | None":
    """Run bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.
        datas (list[DataFeed] | None, optional): _description_. Defaults to None.
        id (int | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        result (Literal["str", "stats", "bot", None], optional): _description_. Defaults to "str".

    Returns:
        LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing.
            The cost higher from `str`, `stats` object, `bot` object
    """
    # Set name for current processing
    if name is None:
        d = datas[0] if datas else bot.data
        name = f"{id}-{os.getpid()}-{d.name}"

    if bot is None:
        bot = cls.start_bot(
            datas=datas,
            name=name,
            **kwargs,
        )

    # bot
    bot.run(**kwargs.get("run_kwargs", {}))

    # Return type
    if result == "stats":
        return bot.stats
    if result == "str":
        return str(bot.stats)

    return bot

start ¤

start()

Start bot

Source code in lettrade/bot.py
163
164
165
166
167
168
169
170
171
def start(self):
    """Start bot"""
    if not hasattr(self, "brain"):
        self.init()

    self.brain.start()

    if __debug__:
        logger.debug("Bot %s started with %d datas", self._name, len(self.datas))

start_bot classmethod ¤

start_bot(
    bot: LetTradeBot | None = None, **kwargs
) -> LetTradeBot

Init and start bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

Returns:

Source code in lettrade/bot.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
@classmethod
def start_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    **kwargs,
) -> "LetTradeBot":
    """Init and start bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    if bot is None:
        bot = cls.new_bot(**kwargs)
    bot.init(**kwargs.get("init_kwargs", {}))
    bot.start()
    return bot

stop ¤

stop()

Stop bot

Source code in lettrade/bot.py
196
197
198
199
200
def stop(self):
    """Stop bot"""
    self.brain.stop()
    if self.plotter is not None:
        self.plotter.stop()

LetTradeLive ¤

LetTradeLive(
    datas: DataFeed | list[DataFeed] | str | list[str],
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    name: str | None = None,
    bot: type[LetTradeBot] = LetTradeBot,
    **kwargs
)

Bases: LetTrade

Help to maintain live bots

Parameters:

Source code in lettrade/lettrade.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def __init__(
    self,
    datas: DataFeed | list[DataFeed] | str | list[str],
    strategy: type[Strategy],
    feeder: type[DataFeeder],
    exchange: type[Exchange],
    account: type[Account],
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    name: str | None = None,
    bot: type[LetTradeBot] = LetTradeBot,
    **kwargs,
) -> None:
    """_summary_

    Args:
        datas (DataFeed | list[DataFeed] | str | list[str]): _description_
        strategy (type[Strategy]): _description_
        feeder (type[DataFeeder]): _description_
        exchange (type[Exchange]): _description_
        account (type[Account]): _description_
        commander (type[Commander] | None, optional): _description_. Defaults to None.
        stats (type[BotStatistic], optional): _description_. Defaults to BotStatistic.
        plotter (type[Plotter] | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        bot (type[LetTradeBot], optional): _description_. Defaults to LetTradeBot.
    """
    self._kwargs = kwargs
    self._kwargs["strategy_cls"] = strategy
    self._kwargs["feeder_cls"] = feeder
    self._kwargs["exchange_cls"] = exchange
    self._kwargs["account_cls"] = account
    self._kwargs["commander_cls"] = commander
    self._kwargs["stats_cls"] = stats
    self._kwargs["plotter_cls"] = plotter
    self._kwargs["bot_cls"] = bot
    self._kwargs["name"] = name

    self._kwargs["datas"] = self._init_datafeeds(datas)

plot ¤

plot(*args, jump: dict | None = None, **kwargs)

Plot strategy/optimize result

BotPlotter

Miror of BotPlotter.plot(). Plotly implement PlotlyBotPlotter.plot().

Args: jump (dict | None, optional): Miror of BotPlotter.jump()

Example: Jump to position_id

lt.plot(
    jump=dict(position_id=1, range=300),
    layout=dict(height=2000),
)

OptimizePlotter

Miror of OptimizePlotter.plot(). Plotly implement PlotlyOptimizePlotter.plot().

Example:

lt.plot(layout=dict(height=2000))

Source code in lettrade/lettrade.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def plot(self, *args, jump: dict | None = None, **kwargs):
    """Plot strategy/optimize result

    BotPlotter:
        Miror of [BotPlotter.plot()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.plot).
        Plotly implement [PlotlyBotPlotter.plot()](site:/reference/plot/plotly/plotly/#lettrade.plot.plotly.plotly.PlotlyBotPlotter.plot).

        Args:
            `jump` (dict | None, optional): Miror of [BotPlotter.jump()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.jump)

        Example:
            Jump to position_id
                ```python
                lt.plot(
                    jump=dict(position_id=1, range=300),
                    layout=dict(height=2000),
                )
                ```

    OptimizePlotter:
        Miror of [OptimizePlotter.plot()](site:/reference/plot/optimize/#lettrade.plot.optimize.OptimizePlotter.plot).
        Plotly implement [PlotlyOptimizePlotter.plot()](site:/reference/exchange/backtest/plotly/optimize/#lettrade.exchange.backtest.plotly.optimize.PlotlyOptimizePlotter.plot).

        Example:
                ```python
                lt.plot(layout=dict(height=2000))
                ```
    """
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, jump=jump, **kwargs)

run ¤

run(worker: int | None = None, **kwargs)

Run LetTrade in single or multiple processing

Parameters:

  • worker (int | None, default: None ) –

    Number of processing. Defaults to None.

Source code in lettrade/lettrade.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def run(self, worker: int | None = None, **kwargs):
    """Run LetTrade in single or multiple processing

    Args:
        worker (int | None, optional): Number of processing. Defaults to None.
    """
    if worker or isinstance(self.data, list):
        if not isinstance(self.data, list):
            # self.data = self.datas
            self.datas = [self.data]

        worker = self._max_workers(worker)

        self._multiprocess()

        datas_source = self._kwargs.pop("datas")
        with ProcessPoolExecutor(max_workers=worker) as executor:
            futures = [
                executor.submit(
                    self._bot_cls.run_bot,
                    datas=datas,
                    id=i,
                    result="str",
                    **self._kwargs,
                )
                for i, datas in enumerate(datas_source)
            ]
            for future in futures:
                result = future.result()
                print(result)
    else:
        self._bot = self._bot_cls.run_bot(
            bot=self._bot,
            result="bot",
            **self._kwargs,
        )
        print(str(self._bot.stats))

start ¤

start(force: bool = False)

Start LetTrade by init bot object and loading datafeeds

Parameters:

  • force (bool, default: False ) –

    description. Defaults to False.

Source code in lettrade/lettrade.py
108
109
110
111
112
113
114
115
116
117
def start(self, force: bool = False):
    """Start LetTrade by init bot object and loading datafeeds

    Args:
        force (bool, optional): _description_. Defaults to False.
    """
    if force and self._bot is not None:
        self._bot = None

    self._bot = self._bot_cls.start_bot(bot=self._bot, **self._kwargs)

stop ¤

stop()

Stop LetTrade

Source code in lettrade/lettrade.py
183
184
185
186
187
188
def stop(self):
    """Stop LetTrade"""
    if self._bot is not None:
        return self._bot.stop()
    if self.stats:
        self.stats.stop()

LetTradeLiveBot ¤

LetTradeLiveBot(api: LiveAPI | None = LiveAPI, **kwargs)

Bases: LetTradeBot

Live bot object

Parameters:

  • api (LiveAPI | None, default: LiveAPI ) –

    description. Defaults to LiveAPI.

Source code in lettrade/exchange/live/live.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def __init__(self, api: LiveAPI | None = LiveAPI, **kwargs) -> None:
    """_summary_

    Args:
        api (LiveAPI | None, optional): _description_. Defaults to LiveAPI.
    """
    super().__init__(**kwargs)

    if issubclass(api, LiveAPI):
        self._api = api(**self._kwargs.get("api_kwargs", {}))
    else:
        self._api = api

    self._kwargs.setdefault("feeder_kwargs", dict()).update(api=self._api)
    self._kwargs.setdefault("exchange_kwargs", dict()).update(api=self._api)
    self._kwargs.setdefault("account_kwargs", dict()).update(api=self._api)

    for data in self.datas:
        data._api = self._api

account instance-attribute ¤

account: Account

Trading account handler

brain instance-attribute ¤

brain: Brain

Brain of bot

commander class-attribute instance-attribute ¤

commander: Commander | None = None

Control the bot

data instance-attribute ¤

data: DataFeed = datas[0]

Main DataFeed for bot

exchange instance-attribute ¤

exchange: Exchange

Trading exchange and events

feeder instance-attribute ¤

feeder: DataFeeder

DataFeeder help to handle datas

plotter class-attribute instance-attribute ¤

plotter: Plotter | None = None

Plot graphic results

strategy instance-attribute ¤

strategy: Strategy

Strategy

new_bot classmethod ¤

new_bot(
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs
) -> LetTradeBot

Create new bot object

Parameters:

Returns:

Source code in lettrade/bot.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
@classmethod
def new_bot(
    cls,
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs,
) -> "LetTradeBot":
    """Create new bot object

    Args:
        datas (list[DataFeed]): _description_
        strategy_cls (type[Strategy]): _description_
        feeder_cls (type[DataFeeder]): _description_
        exchange_cls (type[Exchange]): _description_
        account_cls (type[Account]): _description_
        commander_cls (type[Commander]): _description_
        plotter_cls (type[Plotter]): _description_
        stats_cls (type[BotStatistic]): _description_
        name (str | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    bot = cls(
        strategy=strategy_cls,
        datas=datas,
        feeder=feeder_cls,
        exchange=exchange_cls,
        account=account_cls,
        commander=commander_cls,
        plotter=plotter_cls,
        stats=stats_cls,
        name=name,
        **kwargs,
    )
    return bot

plot ¤

plot(*args, **kwargs)

Plot bot result

Source code in lettrade/bot.py
186
187
188
189
190
191
192
193
194
def plot(self, *args, **kwargs):
    """Plot bot result"""
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, **kwargs)

run ¤

run()

Run bot

Source code in lettrade/bot.py
173
174
175
176
177
178
179
180
181
182
183
184
def run(self):
    """Run bot"""
    if self.commander:
        self.commander.start()

    self.brain.run()

    if self.commander:
        self.commander.stop()

    if self._stats_cls:
        self.stats.compute()

run_bot classmethod ¤

run_bot(
    bot: LetTradeBot | None = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs
) -> LetTradeBot | BotStatistic | str | None

Run bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

  • datas (list[DataFeed] | None, default: None ) –

    description. Defaults to None.

  • id (int | None, default: None ) –

    description. Defaults to None.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • result (Literal['str', 'stats', 'bot', None], default: 'str' ) –

    description. Defaults to "str".

Returns:

  • LetTradeBot | BotStatistic | str | None

    LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing. The cost higher from str, stats object, bot object

Source code in lettrade/bot.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
@classmethod
def run_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs,
) -> "LetTradeBot | BotStatistic | str | None":
    """Run bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.
        datas (list[DataFeed] | None, optional): _description_. Defaults to None.
        id (int | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        result (Literal["str", "stats", "bot", None], optional): _description_. Defaults to "str".

    Returns:
        LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing.
            The cost higher from `str`, `stats` object, `bot` object
    """
    # Set name for current processing
    if name is None:
        d = datas[0] if datas else bot.data
        name = f"{id}-{os.getpid()}-{d.name}"

    if bot is None:
        bot = cls.start_bot(
            datas=datas,
            name=name,
            **kwargs,
        )

    # bot
    bot.run(**kwargs.get("run_kwargs", {}))

    # Return type
    if result == "stats":
        return bot.stats
    if result == "str":
        return str(bot.stats)

    return bot

start ¤

start()

Start bot

Source code in lettrade/bot.py
163
164
165
166
167
168
169
170
171
def start(self):
    """Start bot"""
    if not hasattr(self, "brain"):
        self.init()

    self.brain.start()

    if __debug__:
        logger.debug("Bot %s started with %d datas", self._name, len(self.datas))

start_bot classmethod ¤

start_bot(
    bot: LetTradeBot | None = None, **kwargs
) -> LetTradeBot

Init and start bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

Returns:

Source code in lettrade/bot.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
@classmethod
def start_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    **kwargs,
) -> "LetTradeBot":
    """Init and start bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    if bot is None:
        bot = cls.new_bot(**kwargs)
    bot.init(**kwargs.get("init_kwargs", {}))
    bot.start()
    return bot

stop ¤

stop()

Stop bot

Source code in lettrade/bot.py
196
197
198
199
200
def stop(self):
    """Stop bot"""
    self.brain.stop()
    if self.plotter is not None:
        self.plotter.stop()

LetTradeMetaTrader ¤

LetTradeMetaTrader(
    feeder: type[
        MetaTraderDataFeeder
    ] = MetaTraderDataFeeder,
    exchange: type[MetaTraderExchange] = MetaTraderExchange,
    account: type[MetaTraderAccount] = MetaTraderAccount,
    **kwargs
)

Bases: LetTradeLive

Help to maintain MetaTrader bots

Parameters:

Source code in lettrade/exchange/metatrader/metatrader.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def __init__(
    self,
    feeder: type[MetaTraderDataFeeder] = MetaTraderDataFeeder,
    exchange: type[MetaTraderExchange] = MetaTraderExchange,
    account: type[MetaTraderAccount] = MetaTraderAccount,
    **kwargs,
) -> None:
    """_summary_

    Args:
        feeder (Type[MetaTraderDataFeeder], optional): _description_. Defaults to MetaTraderDataFeeder.
        exchange (Type[MetaTraderExchange], optional): _description_. Defaults to MetaTraderExchange.
        account (Type[MetaTraderAccount], optional): _description_. Defaults to MetaTraderAccount.
    """
    super().__init__(
        feeder=feeder,
        exchange=exchange,
        account=account,
        **kwargs,
    )

plot ¤

plot(*args, jump: dict | None = None, **kwargs)

Plot strategy/optimize result

BotPlotter

Miror of BotPlotter.plot(). Plotly implement PlotlyBotPlotter.plot().

Args: jump (dict | None, optional): Miror of BotPlotter.jump()

Example: Jump to position_id

lt.plot(
    jump=dict(position_id=1, range=300),
    layout=dict(height=2000),
)

OptimizePlotter

Miror of OptimizePlotter.plot(). Plotly implement PlotlyOptimizePlotter.plot().

Example:

lt.plot(layout=dict(height=2000))

Source code in lettrade/lettrade.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def plot(self, *args, jump: dict | None = None, **kwargs):
    """Plot strategy/optimize result

    BotPlotter:
        Miror of [BotPlotter.plot()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.plot).
        Plotly implement [PlotlyBotPlotter.plot()](site:/reference/plot/plotly/plotly/#lettrade.plot.plotly.plotly.PlotlyBotPlotter.plot).

        Args:
            `jump` (dict | None, optional): Miror of [BotPlotter.jump()](site:/reference/plot/bot/#lettrade.plot.bot.BotPlotter.jump)

        Example:
            Jump to position_id
                ```python
                lt.plot(
                    jump=dict(position_id=1, range=300),
                    layout=dict(height=2000),
                )
                ```

    OptimizePlotter:
        Miror of [OptimizePlotter.plot()](site:/reference/plot/optimize/#lettrade.plot.optimize.OptimizePlotter.plot).
        Plotly implement [PlotlyOptimizePlotter.plot()](site:/reference/exchange/backtest/plotly/optimize/#lettrade.exchange.backtest.plotly.optimize.PlotlyOptimizePlotter.plot).

        Example:
                ```python
                lt.plot(layout=dict(height=2000))
                ```
    """
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, jump=jump, **kwargs)

run ¤

run(worker: int | None = None, **kwargs)

Run LetTrade in single or multiple processing

Parameters:

  • worker (int | None, default: None ) –

    Number of processing. Defaults to None.

Source code in lettrade/lettrade.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def run(self, worker: int | None = None, **kwargs):
    """Run LetTrade in single or multiple processing

    Args:
        worker (int | None, optional): Number of processing. Defaults to None.
    """
    if worker or isinstance(self.data, list):
        if not isinstance(self.data, list):
            # self.data = self.datas
            self.datas = [self.data]

        worker = self._max_workers(worker)

        self._multiprocess()

        datas_source = self._kwargs.pop("datas")
        with ProcessPoolExecutor(max_workers=worker) as executor:
            futures = [
                executor.submit(
                    self._bot_cls.run_bot,
                    datas=datas,
                    id=i,
                    result="str",
                    **self._kwargs,
                )
                for i, datas in enumerate(datas_source)
            ]
            for future in futures:
                result = future.result()
                print(result)
    else:
        self._bot = self._bot_cls.run_bot(
            bot=self._bot,
            result="bot",
            **self._kwargs,
        )
        print(str(self._bot.stats))

start ¤

start(force: bool = False)

Start LetTrade by init bot object and loading datafeeds

Parameters:

  • force (bool, default: False ) –

    description. Defaults to False.

Source code in lettrade/lettrade.py
108
109
110
111
112
113
114
115
116
117
def start(self, force: bool = False):
    """Start LetTrade by init bot object and loading datafeeds

    Args:
        force (bool, optional): _description_. Defaults to False.
    """
    if force and self._bot is not None:
        self._bot = None

    self._bot = self._bot_cls.start_bot(bot=self._bot, **self._kwargs)

stop ¤

stop()

Stop LetTrade

Source code in lettrade/lettrade.py
183
184
185
186
187
188
def stop(self):
    """Stop LetTrade"""
    if self._bot is not None:
        return self._bot.stop()
    if self.stats:
        self.stats.stop()

LetTradeMetaTraderBot ¤

LetTradeMetaTraderBot(
    api: LiveAPI | None = LiveAPI, **kwargs
)

Bases: LetTradeLiveBot

LetTradeBot for MetaTrader

Parameters:

  • api (LiveAPI | None, default: LiveAPI ) –

    description. Defaults to LiveAPI.

Source code in lettrade/exchange/live/live.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def __init__(self, api: LiveAPI | None = LiveAPI, **kwargs) -> None:
    """_summary_

    Args:
        api (LiveAPI | None, optional): _description_. Defaults to LiveAPI.
    """
    super().__init__(**kwargs)

    if issubclass(api, LiveAPI):
        self._api = api(**self._kwargs.get("api_kwargs", {}))
    else:
        self._api = api

    self._kwargs.setdefault("feeder_kwargs", dict()).update(api=self._api)
    self._kwargs.setdefault("exchange_kwargs", dict()).update(api=self._api)
    self._kwargs.setdefault("account_kwargs", dict()).update(api=self._api)

    for data in self.datas:
        data._api = self._api

account instance-attribute ¤

account: Account

Trading account handler

brain instance-attribute ¤

brain: Brain

Brain of bot

commander class-attribute instance-attribute ¤

commander: Commander | None = None

Control the bot

data instance-attribute ¤

data: DataFeed = datas[0]

Main DataFeed for bot

exchange instance-attribute ¤

exchange: Exchange

Trading exchange and events

feeder instance-attribute ¤

feeder: DataFeeder

DataFeeder help to handle datas

plotter class-attribute instance-attribute ¤

plotter: Plotter | None = None

Plot graphic results

strategy instance-attribute ¤

strategy: Strategy

Strategy

new_bot classmethod ¤

new_bot(
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs
) -> LetTradeBot

Create new bot object

Parameters:

Returns:

Source code in lettrade/bot.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
@classmethod
def new_bot(
    cls,
    datas: list[DataFeed],
    strategy_cls: type[Strategy],
    feeder_cls: type[DataFeeder],
    exchange_cls: type[Exchange],
    account_cls: type[Account],
    commander_cls: type[Commander],
    plotter_cls: type[Plotter],
    stats_cls: type[BotStatistic],
    name: str | None = None,
    **kwargs,
) -> "LetTradeBot":
    """Create new bot object

    Args:
        datas (list[DataFeed]): _description_
        strategy_cls (type[Strategy]): _description_
        feeder_cls (type[DataFeeder]): _description_
        exchange_cls (type[Exchange]): _description_
        account_cls (type[Account]): _description_
        commander_cls (type[Commander]): _description_
        plotter_cls (type[Plotter]): _description_
        stats_cls (type[BotStatistic]): _description_
        name (str | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    bot = cls(
        strategy=strategy_cls,
        datas=datas,
        feeder=feeder_cls,
        exchange=exchange_cls,
        account=account_cls,
        commander=commander_cls,
        plotter=plotter_cls,
        stats=stats_cls,
        name=name,
        **kwargs,
    )
    return bot

plot ¤

plot(*args, **kwargs)

Plot bot result

Source code in lettrade/bot.py
186
187
188
189
190
191
192
193
194
def plot(self, *args, **kwargs):
    """Plot bot result"""
    if __debug__:
        from .utils.docs import is_docs_session

        if is_docs_session():
            return

    self.plotter.plot(*args, **kwargs)

run ¤

run()

Run bot

Source code in lettrade/bot.py
173
174
175
176
177
178
179
180
181
182
183
184
def run(self):
    """Run bot"""
    if self.commander:
        self.commander.start()

    self.brain.run()

    if self.commander:
        self.commander.stop()

    if self._stats_cls:
        self.stats.compute()

run_bot classmethod ¤

run_bot(
    bot: LetTradeBot | None = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs
) -> LetTradeBot | BotStatistic | str | None

Run bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

  • datas (list[DataFeed] | None, default: None ) –

    description. Defaults to None.

  • id (int | None, default: None ) –

    description. Defaults to None.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • result (Literal['str', 'stats', 'bot', None], default: 'str' ) –

    description. Defaults to "str".

Returns:

  • LetTradeBot | BotStatistic | str | None

    LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing. The cost higher from str, stats object, bot object

Source code in lettrade/bot.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
@classmethod
def run_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    datas: list[DataFeed] | None = None,
    id: int | None = None,
    name: str | None = None,
    result: Literal["str", "stats", "bot", None] = "str",
    **kwargs,
) -> "LetTradeBot | BotStatistic | str | None":
    """Run bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.
        datas (list[DataFeed] | None, optional): _description_. Defaults to None.
        id (int | None, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        result (Literal["str", "stats", "bot", None], optional): _description_. Defaults to "str".

    Returns:
        LetTradeBot | BotStatistic | str | None: Return value will be pickle across multiprocessing.
            The cost higher from `str`, `stats` object, `bot` object
    """
    # Set name for current processing
    if name is None:
        d = datas[0] if datas else bot.data
        name = f"{id}-{os.getpid()}-{d.name}"

    if bot is None:
        bot = cls.start_bot(
            datas=datas,
            name=name,
            **kwargs,
        )

    # bot
    bot.run(**kwargs.get("run_kwargs", {}))

    # Return type
    if result == "stats":
        return bot.stats
    if result == "str":
        return str(bot.stats)

    return bot

start ¤

start()

Start bot

Source code in lettrade/bot.py
163
164
165
166
167
168
169
170
171
def start(self):
    """Start bot"""
    if not hasattr(self, "brain"):
        self.init()

    self.brain.start()

    if __debug__:
        logger.debug("Bot %s started with %d datas", self._name, len(self.datas))

start_bot classmethod ¤

start_bot(
    bot: LetTradeBot | None = None, **kwargs
) -> LetTradeBot

Init and start bot object

Parameters:

  • bot (LetTradeBot | None, default: None ) –

    description. Defaults to None.

Returns:

Source code in lettrade/bot.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
@classmethod
def start_bot(
    cls,
    bot: "LetTradeBot | None" = None,
    **kwargs,
) -> "LetTradeBot":
    """Init and start bot object

    Args:
        bot (LetTradeBot | None, optional): _description_. Defaults to None.

    Returns:
        LetTradeBot: _description_
    """
    if bot is None:
        bot = cls.new_bot(**kwargs)
    bot.init(**kwargs.get("init_kwargs", {}))
    bot.start()
    return bot

stop ¤

stop()

Stop bot

Source code in lettrade/bot.py
196
197
198
199
200
def stop(self):
    """Stop bot"""
    self.brain.stop()
    if self.plotter is not None:
        self.plotter.stop()

LiveAPI ¤

LiveAPI(**kwargs)

Bases: ABC

Source code in lettrade/exchange/live/api.py
18
19
def __init__(self, **kwargs):
    """"""

account abstractmethod ¤

account() -> dict
Source code in lettrade/exchange/live/api.py
65
66
67
@abstractmethod
def account(self) -> dict:
    """"""

bars abstractmethod ¤

bars(
    symbol,
    timeframe,
    since: int | datetime | None = 0,
    to: int | datetime | None = 1000,
) -> list[list]
Source code in lettrade/exchange/live/api.py
48
49
50
51
52
53
54
55
56
@abstractmethod
def bars(
    self,
    symbol,
    timeframe,
    since: int | datetime | None = 0,
    to: int | datetime | None = 1_000,
) -> list[list]:
    """"""

execution_get abstractmethod ¤

execution_get(id: str, **kwargs) -> dict
Source code in lettrade/exchange/live/api.py
128
129
130
@abstractmethod
def execution_get(self, id: str, **kwargs) -> dict:
    """"""

executions_get abstractmethod ¤

executions_get(
    position_id: str | None = None,
    search: str | None = None,
    **kwargs
) -> list[dict]
Source code in lettrade/exchange/live/api.py
119
120
121
122
123
124
125
126
@abstractmethod
def executions_get(
    self,
    position_id: str | None = None,
    search: str | None = None,
    **kwargs,
) -> list[dict]:
    """"""

executions_total abstractmethod ¤

executions_total(
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> int
Source code in lettrade/exchange/live/api.py
110
111
112
113
114
115
116
117
@abstractmethod
def executions_total(
    self,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> int:
    """"""

heartbeat ¤

heartbeat() -> bool
Source code in lettrade/exchange/live/api.py
34
35
36
def heartbeat(self) -> bool:
    """"""
    return True

init ¤

init(**kwargs)
Source code in lettrade/exchange/live/api.py
21
22
def init(self, **kwargs):
    """"""

market abstractmethod ¤

market(symbol: str) -> dict
Source code in lettrade/exchange/live/api.py
39
40
41
@abstractmethod
def market(self, symbol: str) -> dict:
    """"""

markets abstractmethod ¤

markets(symbols: list[str]) -> dict
Source code in lettrade/exchange/live/api.py
43
44
45
@abstractmethod
def markets(self, symbols: list[str]) -> dict:
    """"""

multiprocess classmethod ¤

multiprocess(kwargs: dict, **other_kwargs)
Source code in lettrade/exchange/live/api.py
14
15
16
@classmethod
def multiprocess(cls, kwargs: dict, **other_kwargs):
    """"""

next ¤

next()
Source code in lettrade/exchange/live/api.py
27
28
def next(self):
    """"""

order_close abstractmethod ¤

order_close(order: LiveOrder, **kwargs) -> dict
Source code in lettrade/exchange/live/api.py
96
97
98
@abstractmethod
def order_close(self, order: "LiveOrder", **kwargs) -> dict:
    """"""

order_open abstractmethod ¤

order_open(**kwargs) -> dict
Source code in lettrade/exchange/live/api.py
88
89
90
@abstractmethod
def order_open(self, **kwargs) -> dict:
    """"""

order_update abstractmethod ¤

order_update(
    order: LiveOrder, sl=None, tp=None, **kwargs
) -> dict
Source code in lettrade/exchange/live/api.py
92
93
94
@abstractmethod
def order_update(self, order: "LiveOrder", sl=None, tp=None, **kwargs) -> dict:
    """"""

orders_get abstractmethod ¤

orders_get(
    id: str | None = None,
    symbol: str | None = None,
    **kwargs
)
Source code in lettrade/exchange/live/api.py
79
80
81
82
83
84
85
86
@abstractmethod
def orders_get(
    self,
    id: str | None = None,
    symbol: str | None = None,
    **kwargs,
):
    """"""

orders_history_get ¤

orders_history_get(
    id: str | None = None,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> list[dict]
Source code in lettrade/exchange/live/api.py
100
101
102
103
104
105
106
107
def orders_history_get(
    self,
    id: str | None = None,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> list[dict]:
    """"""

orders_total abstractmethod ¤

orders_total(
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> int
Source code in lettrade/exchange/live/api.py
70
71
72
73
74
75
76
77
@abstractmethod
def orders_total(
    self,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> int:
    """"""

position_close abstractmethod ¤

position_close(position: LivePosition, **kwargs) -> dict
Source code in lettrade/exchange/live/api.py
156
157
158
@abstractmethod
def position_close(self, position: "LivePosition", **kwargs) -> dict:
    """"""

position_update abstractmethod ¤

position_update(
    position: LivePosition,
    sl: float | None = None,
    tp: float | None = None,
    **kwargs
) -> dict
Source code in lettrade/exchange/live/api.py
146
147
148
149
150
151
152
153
154
@abstractmethod
def position_update(
    self,
    position: "LivePosition",
    sl: float | None = None,
    tp: float | None = None,
    **kwargs,
) -> dict:
    """"""

positions_get abstractmethod ¤

positions_get(
    id: str = None, symbol: str = None, **kwargs
) -> list[dict]
Source code in lettrade/exchange/live/api.py
142
143
144
@abstractmethod
def positions_get(self, id: str = None, symbol: str = None, **kwargs) -> list[dict]:
    """"""

positions_total abstractmethod ¤

positions_total(
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> int
Source code in lettrade/exchange/live/api.py
133
134
135
136
137
138
139
140
@abstractmethod
def positions_total(
    self,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> int:
    """"""

start ¤

start(exchange: LiveExchange)
Source code in lettrade/exchange/live/api.py
24
25
def start(self, exchange: "LiveExchange"):
    """"""

stop ¤

stop()
Source code in lettrade/exchange/live/api.py
30
31
def stop(self):
    """"""

tick_get abstractmethod ¤

tick_get(symbol: str) -> dict
Source code in lettrade/exchange/live/api.py
59
60
61
@abstractmethod
def tick_get(self, symbol: str) -> dict:
    """"""

LiveAccount ¤

LiveAccount(api: LiveAPI, **kwargs)

Bases: Account

Parameters:

Source code in lettrade/exchange/live/account.py
18
19
20
21
22
23
24
25
26
27
28
29
def __init__(self, api: LiveAPI, **kwargs) -> None:
    """Account for live trading

    Args:
        api (LiveAPI): _description_
        **kwargs (dict, optional): Mirror of [lettrade.account.Account()](site:/reference/account/account/#lettrade.account.account.Account).
    """
    super().__init__(**kwargs)
    self._api = api

    self._balance = 0.0
    self._equity = 0.0

balance property ¤

balance: float

Balance of account

Returns:

  • float ( float ) –

    description

account_refresh ¤

account_refresh()

Refresh account balance

Source code in lettrade/exchange/live/account.py
53
54
55
56
57
58
59
60
61
def account_refresh(self):
    """Refresh account balance"""
    self._account = self._api.account()

    if __debug__:
        logger.debug("Account: %s", str(self._account))

    self._balance = self._account.balance
    self._equity = self._account.equity

next ¤

next()

Next account

Source code in lettrade/account/account.py
73
74
def next(self):
    """Next account"""

next_next ¤

next_next()

Call after strategy.next()

Source code in lettrade/account/account.py
76
77
78
def next_next(self):
    """Call after strategy.next()"""
    self._equity_snapshot()

risk ¤

risk(side: TradeSide, size: float, **kwargs) -> float

Risk calculation

Source code in lettrade/account/account.py
87
88
89
90
91
def risk(self, side: "TradeSide", size: float, **kwargs) -> float:
    """Risk calculation"""
    if size is None:
        return side * abs(self._risk)
    return side * abs(size)

start ¤

start()

Start live account by load account info from API

Source code in lettrade/exchange/live/account.py
34
35
36
37
38
def start(self):
    """Start live account by load account info from API"""
    self.account_refresh()
    self._margin = self._account.margin
    self._leverage = self._account.leverage

stop ¤

stop()

Stop account

Source code in lettrade/account/account.py
80
81
82
83
84
85
def stop(self):
    """Stop account"""
    try:
        self._equity_snapshot()
    except LetAccountInsufficientException:
        pass

LiveDataFeed ¤

LiveDataFeed(
    symbol: str,
    timeframe: str | int | Timedelta,
    name: str | None = None,
    columns: list[str] | None = None,
    api: LiveAPI | None = None,
    api_kwargs: dict | None = None,
    **kwargs
)

Bases: DataFeed

Live trading DataFeed

Parameters:

  • symbol (str) –

    Symbol of DataFeed

  • timeframe (str | int | Timedelta) –

    TimeFrame of DataFeed

  • name (str | None, default: None ) –

    Name of DataFeed, auto generate {symbol}_{timeframe} if none. Defaults to None.

  • columns (list[str] | None, default: None ) –

    List of DataFeed columns. Defaults to None.

  • api (LiveAPI | None, default: None ) –

    Live trading API. Defaults to None.

Source code in lettrade/exchange/live/data.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def __init__(
    self,
    symbol: str,
    timeframe: str | int | pd.Timedelta,
    name: str | None = None,
    columns: list[str] | None = None,
    api: LiveAPI | None = None,
    api_kwargs: dict | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        symbol (str): Symbol of DataFeed
        timeframe (str | int | pd.Timedelta): TimeFrame of DataFeed
        name (str | None, optional): Name of DataFeed, auto generate `{symbol}_{timeframe}` if none. Defaults to None.
        columns (list[str] | None, optional): List of DataFeed columns. Defaults to None.
        api (LiveAPI | None, optional): Live trading API. Defaults to None.
    """
    super().__init__(
        name=name or f"{symbol}_{timeframe}",
        timeframe=timeframe,
        columns=columns or ["open", "high", "low", "close", "volume"],
        **kwargs,
    )

    self.meta.update(symbol=symbol, base_columns=self.columns.copy())

    if api is not None:
        self._api = api
    elif api_kwargs is not None:
        self._api = self._api_cls(**api_kwargs)
    else:
        raise RuntimeError("Parameter: api or api_kwargs cannot missing")

i property ¤

Alias to lettrade.indicator and using in DataFeed by call: DataFeed.i.indicator_name()

is_main property ¤

is_main: bool

Property to check DataFeed is main DataFeed or not

l instance-attribute ¤

LetTrade DataFeed wrapper using to manage index pointer of DataFeed

meta property ¤

meta: dict

Property to get metadata of DataFeed

name property ¤

name: str

Property to get name of DataFeed

now property ¤

now: Timestamp

Property to get current index value of DataFeed

symbol property ¤

symbol: str

Property to get symbol of DataFeed

timeframe property ¤

timeframe: TimeFrame

Property to get timeframe of DataFeed

bar ¤

bar(i: int = 0) -> Timestamp

Get current pd.Timestamp value of DataFeed

Parameters:

  • i (int, default: 0 ) –

    Index. Defaults to 0.

Returns:

  • Timestamp

    pd.Timestamp: description

Source code in lettrade/data/data.py
108
109
110
111
112
113
114
115
116
117
def bar(self, i: int = 0) -> pd.Timestamp:
    """Get current pd.Timestamp value of DataFeed

    Args:
        i (int, optional): Index. Defaults to 0.

    Returns:
        pd.Timestamp: _description_
    """
    return self.l.index[i]

bars ¤

bars(
    since: int | str | Timestamp, to: int | str | Timestamp
) -> list

Get bars from LiveAPI

Parameters:

  • since (int | str | Timestamp) –

    description

  • to (int | str | Timestamp) –

    description

Returns:

  • list ( list ) –

    list of bar

Source code in lettrade/exchange/live/data.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def bars(
    self,
    since: int | str | pd.Timestamp,
    to: int | str | pd.Timestamp,
) -> list:
    """Get bars from LiveAPI

    Args:
        since (int | str | pd.Timestamp): _description_
        to (int | str | pd.Timestamp): _description_

    Returns:
        list: list of bar
    """
    return self._api.bars(
        symbol=self.symbol,
        timeframe=self.timeframe.string,
        since=since,
        to=to,
    )

bars_load ¤

bars_load(
    since: int | str | Timestamp, to: int | str | Timestamp
) -> bool

Get bar from API and push to DataFeed

Parameters:

  • since (int | str | Timestamp) –

    description

  • to (int | str | Timestamp) –

    description

Returns:

  • bool ( bool ) –

    True if has data, False if no data

Source code in lettrade/exchange/live/data.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def bars_load(
    self,
    since: int | str | pd.Timestamp,
    to: int | str | pd.Timestamp,
) -> bool:
    """Get bar from API and push to DataFeed

    Args:
        since (int | str | pd.Timestamp): _description_
        to (int | str | pd.Timestamp): _description_

    Returns:
        bool: True if has data, False if no data
    """
    bars = self.bars(since=since, to=to)

    if bars is None or len(bars) == 0:
        logger.warning("No bars data for %s", self.name)
        return False

    self.push(bars, unit=self._bar_datetime_unit)
    return True

drop ¤

drop(
    *args,
    since: int | str | Timestamp | None = None,
    to: int | str | Timestamp | None = None,
    **kwargs
) -> None

summary

Parameters:

  • since (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

  • to (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/data/data.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def drop(
    self,
    *args,
    since: int | str | pd.Timestamp | None = None,
    to: int | str | pd.Timestamp | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        since (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.
        to (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if since is None and to is None:
        super().drop(*args, **kwargs)
        return

    condiction = None

    # Since
    if since is not None:
        if isinstance(since, int):
            loc = self.l.index[since]
        elif isinstance(since, str):
            loc = pd.to_datetime(since, utc=True)
        elif isinstance(since, pd.Timestamp):
            loc = since
        else:
            raise RuntimeError(f"DataFeed.drop since {since} is invalid")
        condiction = self.index < loc

    # To
    if to is not None:
        if isinstance(to, int):
            loc = self.l.index[to]
        elif isinstance(to, str):
            loc = pd.to_datetime(to, utc=True)
        elif isinstance(to, pd.Timestamp):
            loc = to
        else:
            raise RuntimeError(f"DataFeed.drop to {to} is invalid")

        if condiction is None:
            condiction = self.index > loc
        else:
            condiction = condiction | (self.index > loc)

    index = self[condiction].index
    super().drop(index=index, inplace=True)
    self.l.reset()

    if __debug__:
        logger.debug("BackTestDataFeed %s dropped %s rows", self.name, len(index))

dump_csv ¤

dump_csv(
    path: str | None = None,
    since: int | str | datetime | None = 0,
    to: int | str | datetime | None = 1000,
    **kwargs
)

summary

Parameters:

  • path (str | None, default: None ) –

    description. Defaults to None.

  • since (int | str | datetime | None, default: 0 ) –

    description. Defaults to 0.

  • to (int | str | datetime | None, default: 1000 ) –

    description. Defaults to 1_000.

Source code in lettrade/exchange/live/data.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def dump_csv(
    self,
    path: str | None = None,
    since: int | str | datetime | None = 0,
    to: int | str | datetime | None = 1_000,
    **kwargs,
):
    """_summary_

    Args:
        path (str | None, optional): _description_. Defaults to None.
        since (int  |  str  |  datetime | None, optional): _description_. Defaults to 0.
        to (int  |  str  |  datetime | None, optional): _description_. Defaults to 1_000.
    """
    if self.empty:
        if isinstance(since, str):
            since = pd.to_datetime(since).to_pydatetime()
        if isinstance(to, str):
            to = pd.to_datetime(to).to_pydatetime()

        self.bars_load(since=since, to=to)

    if path is None:
        path = f"data/{self.name}-{since}_{to}.csv"

    from lettrade.data.extra.csv import csv_export

    csv_export(dataframe=self, path=path, **kwargs)

next ¤

next(size=1, tick=0) -> bool

Drop extra columns and load next DataFeed

Parameters:

  • size (int, default: 1 ) –

    description. Defaults to 1.

  • tick (int, default: 0 ) –

    description. Defaults to 0.

Returns:

  • bool ( bool ) –

    description

Source code in lettrade/exchange/live/data.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def next(self, size=1, tick=0) -> bool:
    """Drop extra columns and load next DataFeed

    Args:
        size (int, optional): _description_. Defaults to 1.
        tick (int, optional): _description_. Defaults to 0.

    Returns:
        bool: _description_
    """
    # Drop existed extra columns to skip reusing calculated data
    self.drop(columns=self.columns.difference(self._base_columns), inplace=True)

    self.bars_load(since=0, to=size + 1)
    self.l.go_stop()
    return True

push ¤

push(
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs
)

Push new rows to DataFeed

Parameters:

  • rows (list[list[int | float]]) –

    list of rows [["timestamp", "open price", "high price"...]]

  • unit (str | None, default: None ) –

    pandas.Timestamp parsing unit. Defaults to None.

  • utc (bool, default: True ) –

    description. Defaults to True.

Source code in lettrade/data/data.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def push(
    self,
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs,
):
    """Push new rows to DataFeed

    Args:
        rows (list[list[int | float]]): list of rows `[["timestamp", "open price", "high price"...]]`
        unit (str | None, optional): pandas.Timestamp parsing unit. Defaults to None.
        utc (bool, optional): _description_. Defaults to True.
    """
    for row in rows:
        dt = pd.to_datetime(row[0], unit=unit, utc=utc, **kwargs)
        self.at[
            dt,
            (
                "open",
                "high",
                "low",
                "close",
                "volume",
            ),
        ] = (
            row[1],  # open
            row[2],  # high
            row[3],  # low
            row[4],  # close
            row[5],  # volume
        )

    if __debug__:
        logger.debug("[%s] Update bar: \n%s", self.name, self.tail(len(rows)))

symbol_info ¤

symbol_info()

Get symbol information from API

Source code in lettrade/exchange/live/data.py
73
74
75
def symbol_info(self):
    """Get symbol information from API"""
    return self._api.market(symbol=self.symbol)

LiveDataFeeder ¤

LiveDataFeeder(
    api: LiveAPI | None = None,
    tick: bool = 5,
    start_size: int = 500,
    api_kwargs: dict | None = None,
    **kwargs
)

Bases: DataFeeder

tick == 0: wait until new bar change value
tick > 0: sleep tick time (in seconds) then update
Source code in lettrade/exchange/live/feeder.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def __init__(
    self,
    api: LiveAPI | None = None,
    tick: bool = 5,
    start_size: int = 500,
    api_kwargs: dict | None = None,
    **kwargs,
) -> None:
    """
    tick:
        tick < 0: no tick, just get completed bar
        tick == 0: wait until new bar change value
        tick > 0: sleep tick time (in seconds) then update
    """
    super().__init__()

    # API init
    if api is None:
        if api_kwargs is None:
            raise RuntimeError("api or api_kwargs cannot missing")
        api = self._api_cls(**api_kwargs)

    self._api = api
    self._tick = tick
    self._start_size = start_size
    self._config = kwargs

    if isinstance(self._tick, int):
        self._wait_timeframe = TimeFrame(f"{self._tick}s")
    else:
        self._wait_timeframe = None

is_continous property ¤

is_continous

Flag check is realtime continous datafeeder

LiveExchange ¤

LiveExchange(api: LiveAPI, *args, **kwargs)

Bases: Exchange

MetaTrade 5 exchange module for lettrade

Parameters:

  • api (LiveAPI) –

    API connect to rpyc MeTrader 5 Terminal server through module mt5linux

  • *args (list, default: () ) –

    Exchange list parameters

  • **kwargs (dict, default: {} ) –

    Exchange dict parameters

Source code in lettrade/exchange/live/exchange.py
21
22
23
24
25
26
27
28
29
30
def __init__(self, api: LiveAPI, *args, **kwargs):
    """_summary_

    Args:
        api (LiveAPI): API connect to rpyc MeTrader 5 Terminal server through module `mt5linux`
        *args (list): `Exchange` list parameters
        **kwargs (dict): `Exchange` dict parameters
    """
    super().__init__(*args, **kwargs)
    self._api = api

data instance-attribute ¤

data: DataFeed

main DataFeed

datas instance-attribute ¤

datas: list[DataFeed]

List of all available DataFeed

executions instance-attribute ¤

executions: dict[str, Execution] = dict()

Execution dict by Execution.id key

history_orders instance-attribute ¤

history_orders: dict[str, Order] = dict()

History Order dict by Order.id key

history_positions instance-attribute ¤

history_positions: dict[str, Position] = dict()

History Position dict by Position.id key

orders instance-attribute ¤

orders: dict[str, Order] = dict()

Available Order dict by Order.id key

positions instance-attribute ¤

positions: dict[str, Position] = dict()

Available Position dict by Position.id key

init ¤

init(
    brain: Brain,
    feeder: DataFeeder,
    account: Account,
    commander: Commander,
) -> None

Init Exchange dependencies. Set data/datas from feeder. Set state to ExchangeState.Start.

Parameters:

Source code in lettrade/exchange/exchange.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def init(
    self,
    brain: "Brain",
    feeder: DataFeeder,
    account: Account,
    commander: Commander,
) -> None:
    """Init Exchange dependencies.
    Set data/datas from feeder.
    Set state to `ExchangeState.Start`.

    Args:
        brain (Brain): _description_
        feeder (DataFeeder): _description_
        account (Account): _description_
        commander (Commander): _description_
    """
    self._brain = brain
    self._feeder = feeder
    self._account = account
    self._commander = commander

    self.data = self._feeder.data
    self.datas = self._feeder.datas

    self._account.init(exchange=self)

    self._state = ExchangeState.Start

new_order ¤

new_order(
    size: float,
    type: OrderType | None = OrderType.Market,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    data: DataFeed | None = None,
    **kwargs
) -> OrderResult

Place new order to server

Parameters:

  • size (float) –

    description

  • type (OrderType | None, default: Market ) –

    description. Defaults to OrderType.Market.

  • limit (float | None, default: None ) –

    description. Defaults to None.

  • stop (float | None, default: None ) –

    description. Defaults to None.

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • tag (str | None, default: None ) –

    description. Defaults to None.

  • data (DataFeed | None, default: None ) –

    description. Defaults to None.

Returns:

Source code in lettrade/exchange/live/exchange.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def new_order(
    self,
    size: float,
    type: OrderType | None = OrderType.Market,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    data: DataFeed | None = None,
    **kwargs,
) -> OrderResult:
    """Place new order to server

    Args:
        size (float): _description_
        type (OrderType | None, optional): _description_. Defaults to OrderType.Market.
        limit (float | None, optional): _description_. Defaults to None.
        stop (float | None, optional): _description_. Defaults to None.
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        tag (str | None, optional): _description_. Defaults to None.
        data (DataFeed | None, optional): _description_. Defaults to None.

    Returns:
        OrderResult: _description_
    """
    if not data:
        data = self.data

    if type == OrderType.Market:
        limit = 0
        stop = 0

    order = self._order_cls(
        id=-1,
        exchange=self,
        data=data,
        size=size,
        type=type,
        limit_price=limit,
        stop_price=stop,
        sl_price=sl,
        tp_price=tp,
        tag=tag,
        **kwargs,
    )
    ok = order.place()

    # if __debug__:
    #     logger.info("New order %s at %s", order, self.data.now)

    return ok

next_next ¤

next_next()

Call after strategy.next()

Source code in lettrade/exchange/exchange.py
109
110
111
def next_next(self):
    """Call after strategy.next()"""
    self._account.next_next()

on_execution ¤

on_execution(
    execution: Execution,
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Execution event from exchange then store and notify Brain

Parameters:

  • execution (Execution) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Source code in lettrade/exchange/exchange.py
118
119
120
121
122
123
124
125
126
127
128
129
130
def on_execution(
    self,
    execution: Execution,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Execution event from exchange then store and notify Brain

    Args:
        execution (Execution): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.
    """
    self.on_executions(executions=[execution], broadcast=broadcast, **kwargs)

on_executions ¤

on_executions(
    executions: list[Execution],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Execution event from exchange then store and notify Brain

Source code in lettrade/exchange/exchange.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def on_executions(
    self,
    executions: list[Execution],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """
    Receive Execution event from exchange then store and notify Brain
    """
    for execution in executions:
        if not isinstance(execution, Execution):
            raise RuntimeError(f"{execution} is not instance of type Execution")

        if execution.id in self.executions:
            # Merge to keep Execution handler for strategy using
            # when strategy want to store Execution object
            # and object will be automatic update directly
            self.executions[execution.id].merge(execution)
            execution = self.executions[execution.id]
        else:
            self.executions[execution.id] = execution

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_executions(executions)

on_order ¤

on_order(
    order: Order, broadcast: bool | None = True, **kwargs
) -> None

Receive order event from exchange then store and notify Brain

Parameters:

  • order (Order) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Source code in lettrade/exchange/exchange.py
160
161
162
163
164
165
166
167
168
169
170
171
172
def on_order(
    self,
    order: Order,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive order event from exchange then store and notify Brain

    Args:
        order (Order): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.
    """
    self.on_orders(orders=[order], broadcast=broadcast, **kwargs)

on_orders ¤

on_orders(
    orders: list[Order],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive orders event from exchange then store and notify Brain

Parameters:

  • orders (list[Order]) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def on_orders(
    self,
    orders: list[Order],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive orders event from exchange then store and notify Brain

    Args:
        orders (list[Order]): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.

    Raises:
        RuntimeError: _description_
    """
    for order in orders:
        if not isinstance(order, Order):
            raise RuntimeError(f"{order} is not instance of type Order")

        if order.is_closed:
            if order.id in self.history_orders:
                logger.warning("Order closed recall: %s", order)

            self.history_orders[order.id] = order
            if order.id in self.orders:
                del self.orders[order.id]
        else:
            if order.id in self.history_orders:
                raise RuntimeError(f"Order {order.id} closed")

            if order.id in self.orders:
                # Merge to keep Order handler for strategy using
                # when strategy want to store Order object
                # and object will be automatic update directly
                self.orders[order.id].merge(order)
                order = self.orders[order.id]
            else:
                self.orders[order.id] = order

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_orders(orders)

on_position ¤

on_position(
    position: Position,
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Position event from exchange then store and notify Brain

Parameters:

  • position (Position) –

    new comming Position

  • broadcast (bool | None, default: True ) –

    Flag notify to Brain. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def on_position(
    self,
    position: Position,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Position event from exchange then store and notify Brain

    Args:
        position (Position): new comming `Position`
        broadcast (bool | None, optional): Flag notify to Brain. Defaults to True.

    Raises:
        RuntimeError: validate `Position` instance
    """
    self.on_positions(positions=[position], broadcast=broadcast, **kwargs)

on_positions ¤

on_positions(
    positions: list[Position],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Positions event from exchange then store and notify Brain

Parameters:

  • positions (list[Position]) –

    list of new comming Position

  • broadcast (bool | None, default: True ) –

    Flag notify to Brain. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def on_positions(
    self,
    positions: list[Position],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Positions event from exchange then store and notify Brain

    Args:
        positions (list[Position]): list of new comming `Position`
        broadcast (bool | None, optional): Flag notify to Brain. Defaults to True.

    Raises:
        RuntimeError: validate `Position` instance
    """
    for position in positions:
        if not isinstance(position, Position):
            raise RuntimeError(f"{position} is not instance of type Position")

        if position.is_exited:
            if position.id in self.history_positions:
                logger.warning("Position exited recall: %s", position)

            self.history_positions[position.id] = position
            if position.id in self.positions:
                del self.positions[position.id]

            # self._account._on_position_exit(position)
        else:
            if position.id in self.history_positions:
                raise RuntimeError(f"Position {position.id} closed: {position}")
            if position.id in self.positions:
                # Merge to keep Position handler for strategy using
                # when strategy want to store Position object
                # and object will be automatic update directly
                self.positions[position.id].merge(position)
                position = self.positions[position.id]
            else:
                self.positions[position.id] = position
                # self._account._on_position_entry(position)

    self._account.on_positions(positions)

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_positions(positions)

start ¤

start() -> None

Start Live exchange by: Sync positions from server

Source code in lettrade/exchange/live/exchange.py
32
33
34
35
def start(self) -> None:
    """Start Live exchange by: Sync positions from server"""
    self._api.start(exchange=self)
    return super().start()

stop ¤

stop() -> None

Stop Exchange

Source code in lettrade/exchange/exchange.py
113
114
115
116
def stop(self) -> None:
    """Stop Exchange"""
    self._state = ExchangeState.Stop
    self._account.stop()

MetaTraderAPI ¤

MetaTraderAPI(
    login: int,
    password: str,
    server: str,
    timeout: int = 60,
    retry: int = 20,
    host: str = "localhost",
    port: int = 18812,
    wine: str | None = None,
    path: str | None = None,
    magic: int = 88888888,
    **kwargs
)

Bases: LiveAPI

API to connect MetaTrader 5 Terminal

Parameters:

  • login (int) –

    description

  • password (str) –

    description

  • server (str) –

    description

  • timeout (int, default: 60 ) –

    description. Defaults to 60.

  • retry (int, default: 20 ) –

    description. Defaults to 20.

  • host (str, default: 'localhost' ) –

    description. Defaults to "localhost".

  • port (int, default: 18812 ) –

    description. Defaults to 18812.

  • wine (str | None, default: None ) –

    description. Defaults to None.

  • magic (int, default: 88888888 ) –

    description. Defaults to 88888888.

Raises:

Source code in lettrade/exchange/metatrader/api.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def __init__(
    self,
    login: int,
    password: str,
    server: str,
    timeout: int = 60,
    retry: int = 20,
    host: str = "localhost",
    port: int = 18812,
    wine: str | None = None,
    path: str | None = None,
    magic: int = 88888888,
    **kwargs,
):
    """_summary_

    Args:
        login (int): _description_
        password (str): _description_
        server (str): _description_
        timeout (int, optional): _description_. Defaults to 60.
        retry (int, optional): _description_. Defaults to 20.
        host (str, optional): _description_. Defaults to "localhost".
        port (int, optional): _description_. Defaults to 18812.
        wine (str | None, optional): _description_. Defaults to None.
        magic (int, optional): _description_. Defaults to 88888888.

    Raises:
        ConnectionRefusedError: _description_
        RuntimeError: _description_
    """
    # Parameters
    self._config = kwargs
    self._magic = magic

    self._load_history_since = datetime.now() - timedelta(days=7)
    self._deal_time_checked = datetime.now() - timedelta(days=1)
    self._orders_stored = dict()
    self._executions_stored = dict()
    self._positions_stored = dict()

    # Update config
    self._config.update(
        host=host,
        port=port,
        login=int(login),
        password=password,
        server=server,
        wine=wine,
        path=path,
        retry=retry,
    )

    # Start enviroments
    self._refresh_environments()

account ¤

account(**kwargs) -> dict

Metatrader 5 account information

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/exchange/metatrader/api.py
364
365
366
367
368
369
370
371
372
373
374
@mt5_connection
def account(self, **kwargs) -> dict:
    """Metatrader 5 account information

    Returns:
        dict: _description_
    """
    raw = self._mt5.account_info()
    if raw is None:
        raise _RetryException()
    return raw

bars ¤

bars(
    symbol,
    timeframe,
    since: int | datetime | None = 0,
    to: int | datetime | None = 1000,
    **kwargs
) -> list[list]

summary

Parameters:

  • symbol (_type_) –

    description

  • timeframe (_type_) –

    description

  • since (int | datetime | None, default: 0 ) –

    description. Defaults to 0.

  • to (int | datetime | None, default: 1000 ) –

    description. Defaults to 1_000.

Returns:

  • list[list]

    list[list]: description

Source code in lettrade/exchange/metatrader/api.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
@mt5_connection
def bars(
    self,
    symbol,
    timeframe,
    since: int | datetime | None = 0,
    to: int | datetime | None = 1_000,
    **kwargs,
) -> list[list]:
    """_summary_

    Args:
        symbol (_type_): _description_
        timeframe (_type_): _description_
        since (int | datetime | None, optional): _description_. Defaults to 0.
        to (int | datetime | None, optional): _description_. Defaults to 1_000.

    Returns:
        list[list]: _description_
    """
    timeframe = TIMEFRAME_L2M[timeframe]

    if isinstance(since, int):
        raw = self._mt5.copy_rates_from_pos(symbol, timeframe, since, to)

    elif isinstance(to, int):
        raw = self._mt5.copy_rates_from(symbol, timeframe, since, to)
    else:
        raw = self._mt5.copy_rates_range(symbol, timeframe, since, to)

    if raw is None:
        raise _RetryException()
    return raw

do_order_open ¤

do_order_open(
    symbol: str,
    size: float,
    type: int,
    price: float,
    sl: float = None,
    tp: float = None,
    tag: str | None = None,
    deviation: int = 10,
    **kwargs
) -> dict

summary

Parameters:

  • symbol (str) –

    description

  • size (float) –

    description

  • type (int) –

    description

  • price (float) –

    description

  • sl (float, default: None ) –

    description. Defaults to None.

  • tp (float, default: None ) –

    description. Defaults to None.

  • tag (str, default: None ) –

    description. Defaults to "".

  • deviation (int, default: 10 ) –

    description. Defaults to 10.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/exchange/metatrader/api.py
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
@mt5_connection
def do_order_open(
    self,
    symbol: str,
    size: float,
    type: int,
    price: float,
    sl: float = None,
    tp: float = None,
    tag: str | None = None,
    deviation: int = 10,
    **kwargs,
) -> dict:
    """_summary_

    Args:
        symbol (str): _description_
        size (float): _description_
        type (int): _description_
        price (float): _description_
        sl (float, optional): _description_. Defaults to None.
        tp (float, optional): _description_. Defaults to None.
        tag (str, optional): _description_. Defaults to "".
        deviation (int, optional): _description_. Defaults to 10.

    Returns:
        dict: _description_
    """
    request = self._parse_trade_request(
        symbol=symbol,
        size=size,
        type=type,
        price=price,
        sl=sl,
        tp=tp,
        tag=tag,
        deviation=deviation,
    )
    raw = self._mt5.order_send(request)

    # Retry
    if raw is None:
        raise _RetryException()

    raw = self._parse_trade_send_response(raw)
    if raw.code != 0:
        raise LetLiveOrderInvalidException(raw.error, raw=raw)

    return raw

do_position_update ¤

do_position_update(
    id: int,
    sl: float | None = None,
    tp: float | None = None,
    **kwargs
) -> dict

summary

Parameters:

  • id (int) –

    description

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/exchange/metatrader/api.py
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
@mt5_connection
def do_position_update(
    self,
    id: int,
    # symbol: str,
    sl: float | None = None,
    tp: float | None = None,
    **kwargs,
) -> dict:
    """_summary_

    Args:
        id (int): _description_
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.

    Returns:
        dict: _description_
    """
    request = self._parse_trade_request(
        position=id,
        # symbol=symbol,
        sl=sl,
        tp=tp,
        action=MT5.TRADE_ACTION_SLTP,
        **kwargs,
    )
    raw = self._mt5.order_send(request)

    # Retry
    if raw is None:
        raise _RetryException()

    return self._parse_trade_send_response(raw)

execution_get ¤

execution_get(id: str, **kwargs) -> dict

summary

Parameters:

  • id (str) –

    description

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/exchange/metatrader/api.py
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
@mt5_connection
def execution_get(self, id: str, **kwargs) -> dict:
    """_summary_

    Args:
        id (str): _description_

    Returns:
        dict: _description_
    """
    if id is not None:
        kwargs["ticket"] = int(id)

    raws = self._mt5.history_deals_get(**kwargs)

    # Retry
    if raws is None:
        raise _RetryException()

    if __debug__:
        logger.debug("Raw execution: %s", raws)

    return self._execution_parse_response(raws[0])

executions_get ¤

executions_get(
    position_id: str | None = None,
    search: str | None = None,
    **kwargs
) -> list[dict]

summary

Parameters:

  • position_id (str | None, default: None ) –

    description. Defaults to None.

  • search (str | None, default: None ) –

    description. Defaults to None.

Returns:

  • list[dict]

    list[dict]: description

Source code in lettrade/exchange/metatrader/api.py
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
@mt5_connection
def executions_get(
    self,
    position_id: str | None = None,
    search: str | None = None,
    **kwargs,
) -> list[dict]:
    """_summary_

    Args:
        position_id (str | None, optional): _description_. Defaults to None.
        search (str | None, optional): _description_. Defaults to None.

    Returns:
        list[dict]: _description_
    """
    if position_id is not None:
        kwargs["position"] = int(position_id)
    if search is not None:
        kwargs["group"] = search

    raws = self._mt5.history_deals_get(**kwargs)

    # Retry
    if raws is None:
        raise _RetryException()

    # May be wrong account when position exist but no execution
    if not raws and position_id is not None:
        logger.warning(
            "Execution retry check connection when position=%s exist but no execution",
            position_id,
        )

        # Retry check mt5 connection
        raise _RetryException()

    if __debug__:
        logger.debug("Raw executions: %s", raws)

    return [self._execution_parse_response(raw) for raw in raws]

executions_total ¤

executions_total(
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> int

summary

Parameters:

  • since (datetime | None, default: None ) –

    description. Defaults to None.

  • to (datetime | None, default: None ) –

    description. Defaults to None.

Returns:

  • int ( int ) –

    description

Source code in lettrade/exchange/metatrader/api.py
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
@mt5_connection
def executions_total(
    self,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> int:
    """_summary_

    Args:
        since (datetime | None, optional): _description_. Defaults to None.
        to (datetime | None, optional): _description_. Defaults to None.

    Returns:
        int: _description_
    """
    if since is not None:
        kwargs["date_from"] = since
    if to is not None:
        kwargs["date_to"] = to

    raw = self._mt5.history_deals_total(**kwargs)
    if raw is None:
        raise _RetryException()
    return raw

heartbeat ¤

heartbeat() -> bool

Heartbeat

Returns:

  • bool ( bool ) –

    description

Source code in lettrade/exchange/metatrader/api.py
255
256
257
258
259
260
261
def heartbeat(self) -> bool:
    """Heartbeat

    Returns:
        bool: _description_
    """
    return True

init ¤

init(**kwargs)
Source code in lettrade/exchange/live/api.py
21
22
def init(self, **kwargs):
    """"""

market ¤

market(symbol: str, **kwargs) -> dict

summary

Parameters:

  • symbol (str) –

    description

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/exchange/metatrader/api.py
265
266
267
268
269
270
271
272
273
274
275
276
277
278
@mt5_connection
def market(self, symbol: str, **kwargs) -> dict:
    """_summary_

    Args:
        symbol (str): _description_

    Returns:
        dict: _description_
    """
    raw = self._mt5.symbol_info(symbol)
    if raw is None:
        raise _RetryException()
    return self._market_parse_response(raw)

markets ¤

markets(search: str | None = None, **kwargs) -> list[dict]

The filter for arranging a group of necessary symbols. If the group is specified, the function returns only symbols meeting a specified criteria.

Search example

Get symbols whose names do not contain USD, EUR, JPY and GBP search="*,!*USD*,!*EUR*,!*JPY*,!*GBP*"

Parameters:

  • search (str | None, default: None ) –

    description. Defaults to None.

Returns:

  • list[dict]

    list[dict]: description

Source code in lettrade/exchange/metatrader/api.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
@mt5_connection
def markets(self, search: str | None = None, **kwargs) -> list[dict]:
    """The filter for arranging a group of necessary symbols.
    If the group is specified, the function returns only symbols meeting a specified criteria.

    Search example:
        Get symbols whose names do not contain USD, EUR, JPY and GBP
        `search="*,!*USD*,!*EUR*,!*JPY*,!*GBP*"`

    Args:
        search (str | None, optional): _description_. Defaults to None.

    Returns:
        list[dict]: _description_
    """
    raws = self._mt5.symbols_get(search)
    if raws is None:
        raise _RetryException()
    return [self._market_parse_response(raw) for raw in raws]

next ¤

next()

Next tick action

Source code in lettrade/exchange/metatrader/api.py
251
252
253
def next(self):
    """Next tick action"""
    self._check_transaction_events()

order_open ¤

order_open(order: MetaTraderOrder) -> dict

summary

Parameters:

Raises:

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/exchange/metatrader/api.py
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
def order_open(self, order: "MetaTraderOrder") -> dict:
    """_summary_

    Args:
        order (MetaTraderOrder): _description_

    Raises:
        NotImplementedError: _description_

    Returns:
        dict: _description_
    """
    match order.type:
        case OrderType.Limit:
            price = order.limit_price
        case OrderType.Stop:
            price = order.stop_price
        case OrderType.Market:
            tick = self.tick_get(order.data.symbol)
            price = tick.ask if order.is_long else tick.bid
        case _:
            raise NotImplementedError(
                f"Open order type {order.type} is not implement yet"
            )

    type = MT5.ORDER_TYPE_BUY if order.is_long else MT5.ORDER_TYPE_SELL

    return self.do_order_open(
        symbol=order.data.symbol,
        type=type,
        size=order.size,
        price=price,
        sl=order.sl,
        tp=order.tp,
        tag=order.tag,
    )

orders_get ¤

orders_get(
    id: str | None = None,
    symbol: str | None = None,
    **kwargs
) -> list[dict]

summary

Parameters:

  • id (str | None, default: None ) –

    description. Defaults to None.

  • symbol (str | None, default: None ) –

    description. Defaults to None.

Returns:

  • list[dict]

    list[dict]: description

Source code in lettrade/exchange/metatrader/api.py
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
@mt5_connection
def orders_get(
    self,
    id: str | None = None,
    symbol: str | None = None,
    **kwargs,
) -> list[dict]:
    """_summary_

    Args:
        id (str | None, optional): _description_. Defaults to None.
        symbol (str | None, optional): _description_. Defaults to None.

    Returns:
        list[dict]: _description_
    """
    if id is not None:
        kwargs["ticket"] = int(id)
    if symbol is not None:
        kwargs["symbol"] = symbol

    raws = self._mt5.orders_get(**kwargs)

    # Return None to retry
    if raws is None:
        raise _RetryException()

    return [self._order_parse_response(raw) for raw in raws]

orders_history_get ¤

orders_history_get(
    id: str | None = None,
    position_id: str | None = None,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> list[dict]

summary

Parameters:

  • id (str | None, default: None ) –

    description. Defaults to None.

  • position_id (str | None, default: None ) –

    description. Defaults to None.

  • since (datetime | None, default: None ) –

    description. Defaults to None.

  • to (datetime | None, default: None ) –

    description. Defaults to None.

Returns:

  • list[dict]

    list[dict]: description

Source code in lettrade/exchange/metatrader/api.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
@mt5_connection
def orders_history_get(
    self,
    id: str | None = None,
    position_id: str | None = None,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> list[dict]:
    """_summary_

    Args:
        id (str | None, optional): _description_. Defaults to None.
        position_id (str | None, optional): _description_. Defaults to None.
        since (datetime | None, optional): _description_. Defaults to None.
        to (datetime | None, optional): _description_. Defaults to None.

    Returns:
        list[dict]: _description_
    """
    if id is not None:
        kwargs["ticket"] = int(id)
    if position_id is not None:
        kwargs["position"] = int(position_id)
    if since is not None:
        kwargs["date_from"] = since
    if to is not None:
        kwargs["date_to"] = to

    raws = self._mt5.history_orders_get(**kwargs)

    # Retry
    if raws is None:
        raise _RetryException()

    return [self._order_parse_response(raw) for raw in raws]

orders_history_total ¤

orders_history_total(
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> int

summary

Parameters:

  • since (datetime | None, default: None ) –

    description. Defaults to None.

  • to (datetime | None, default: None ) –

    description. Defaults to None.

Returns:

  • int ( int ) –

    description

Source code in lettrade/exchange/metatrader/api.py
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
@mt5_connection
def orders_history_total(
    self,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> int:
    """_summary_

    Args:
        since (datetime | None, optional): _description_. Defaults to None.
        to (datetime | None, optional): _description_. Defaults to None.

    Returns:
        int: _description_
    """
    if since is not None:
        kwargs["date_from"] = since
    if to is not None:
        kwargs["date_to"] = to

    raw = self._mt5.history_orders_get(**kwargs)
    if raw is None:
        raise _RetryException()
    return raw

orders_total ¤

orders_total(
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> int

summary

Parameters:

  • since (datetime | None, default: None ) –

    description. Defaults to None.

  • to (datetime | None, default: None ) –

    description. Defaults to None.

Returns:

  • int ( int ) –

    description

Source code in lettrade/exchange/metatrader/api.py
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
@mt5_connection
def orders_total(
    self,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> int:
    """_summary_

    Args:
        since (datetime | None, optional): _description_. Defaults to None.
        to (datetime | None, optional): _description_. Defaults to None.

    Returns:
        int: _description_
    """
    if since is not None:
        kwargs["date_from"] = since
    if to is not None:
        kwargs["date_to"] = to

    raw = self._mt5.orders_total(**kwargs)
    if raw is None:
        raise _RetryException()

    return raw

position_close ¤

position_close(
    position: MetaTraderPosition, **kwargs
) -> dict

Close a position

Parameters:

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/exchange/metatrader/api.py
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
def position_close(self, position: "MetaTraderPosition", **kwargs) -> dict:
    """Close a position

    Args:
        position (MetaTraderPosition): _description_

    Returns:
        dict: _description_
    """
    tick = self.tick_get(position.data.symbol)
    price = tick.ask if position.is_long else tick.bid

    # Opposite with position side
    type = MT5.ORDER_TYPE_SELL if position.is_long else MT5.ORDER_TYPE_BUY

    return self.do_position_close(
        id=int(position.id),
        symbol=position.data.symbol,
        type=type,
        size=position.size,
        price=price,
        **kwargs,
    )

position_update ¤

position_update(
    position: MetaTraderPosition,
    sl: float | None = None,
    tp: float | None = None,
    **kwargs
) -> dict

summary

Parameters:

  • position (MetaTraderPosition) –

    description

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/exchange/metatrader/api.py
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
def position_update(
    self,
    position: "MetaTraderPosition",
    sl: float | None = None,
    tp: float | None = None,
    **kwargs,
) -> dict:
    """_summary_

    Args:
        position (MetaTraderPosition): _description_
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.

    Returns:
        dict: _description_
    """
    return self.do_position_update(
        id=int(position.id),
        symbol=position.data.symbol,
        sl=sl,
        tp=tp,
        **kwargs,
    )

positions_get ¤

positions_get(
    id: str = None, symbol: str = None, **kwargs
) -> list[dict]

summary

Parameters:

  • id (str, default: None ) –

    description. Defaults to None.

  • symbol (str, default: None ) –

    description. Defaults to None.

Returns:

  • list[dict]

    list[dict]: description

Source code in lettrade/exchange/metatrader/api.py
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
@mt5_connection
def positions_get(
    self,
    id: str = None,
    symbol: str = None,
    **kwargs,
) -> list[dict]:
    """_summary_

    Args:
        id (str, optional): _description_. Defaults to None.
        symbol (str, optional): _description_. Defaults to None.

    Returns:
        list[dict]: _description_
    """
    if id is not None:
        kwargs.update(ticket=int(id))
    if symbol is not None:
        kwargs.update(symbol=symbol)

    raws = self._mt5.positions_get(**kwargs)

    # Retry
    if raws is None:
        raise _RetryException()

    return [self._position_parse_response(raw) for raw in raws]

positions_total ¤

positions_total(
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs
) -> int

summary

Parameters:

  • since (datetime | None, default: None ) –

    description. Defaults to None.

  • to (datetime | None, default: None ) –

    description. Defaults to None.

Returns:

  • int ( int ) –

    description

Source code in lettrade/exchange/metatrader/api.py
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
@mt5_connection
def positions_total(
    self,
    since: datetime | None = None,
    to: datetime | None = None,
    **kwargs,
) -> int:
    """_summary_

    Args:
        since (datetime | None, optional): _description_. Defaults to None.
        to (datetime | None, optional): _description_. Defaults to None.

    Returns:
        int: _description_
    """
    if since is not None:
        kwargs["date_from"] = since
    if to is not None:
        kwargs["date_to"] = to

    raw = self._mt5.positions_total(**kwargs)
    if raw is None:
        raise _RetryException()
    return raw

start ¤

start(exchange: MetaTraderExchange)

summary

Parameters:

Source code in lettrade/exchange/metatrader/api.py
236
237
238
239
240
241
242
243
244
245
def start(self, exchange: "MetaTraderExchange"):
    """_summary_

    Args:
        exchange (MetaTraderExchange): _description_
    """
    self._exchange = exchange

    self._load_history_transactions()
    self._check_transaction_events()

stop ¤

stop()

Stop MetaTrader 5 Terminal

Source code in lettrade/exchange/metatrader/api.py
247
248
249
def stop(self):
    """Stop MetaTrader 5 Terminal"""
    self._mt5.shutdown()

tick_get ¤

tick_get(symbol: str, **kwargs) -> dict

summary

Parameters:

  • symbol (str) –

    description

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/exchange/metatrader/api.py
313
314
315
316
317
318
319
320
321
322
323
324
325
326
@mt5_connection
def tick_get(self, symbol: str, **kwargs) -> dict:
    """_summary_

    Args:
        symbol (str): _description_

    Returns:
        dict: _description_
    """
    raw = self._mt5.symbol_info_tick(symbol)
    if raw is None:
        raise _RetryException()
    return raw

MetaTraderAccount ¤

MetaTraderAccount(api: LiveAPI, **kwargs)

Bases: LiveAccount

Account for MetaTrader

Parameters:

Source code in lettrade/exchange/live/account.py
18
19
20
21
22
23
24
25
26
27
28
29
def __init__(self, api: LiveAPI, **kwargs) -> None:
    """Account for live trading

    Args:
        api (LiveAPI): _description_
        **kwargs (dict, optional): Mirror of [lettrade.account.Account()](site:/reference/account/account/#lettrade.account.account.Account).
    """
    super().__init__(**kwargs)
    self._api = api

    self._balance = 0.0
    self._equity = 0.0

balance property ¤

balance: float

Balance of account

Returns:

  • float ( float ) –

    description

account_refresh ¤

account_refresh()

Refresh account balance

Source code in lettrade/exchange/live/account.py
53
54
55
56
57
58
59
60
61
def account_refresh(self):
    """Refresh account balance"""
    self._account = self._api.account()

    if __debug__:
        logger.debug("Account: %s", str(self._account))

    self._balance = self._account.balance
    self._equity = self._account.equity

next ¤

next()

Next account

Source code in lettrade/account/account.py
73
74
def next(self):
    """Next account"""

next_next ¤

next_next()

Call after strategy.next()

Source code in lettrade/account/account.py
76
77
78
def next_next(self):
    """Call after strategy.next()"""
    self._equity_snapshot()

risk ¤

risk(side: TradeSide, size: float, **kwargs) -> float

Risk calculation

Source code in lettrade/account/account.py
87
88
89
90
91
def risk(self, side: "TradeSide", size: float, **kwargs) -> float:
    """Risk calculation"""
    if size is None:
        return side * abs(self._risk)
    return side * abs(size)

start ¤

start()

Start live account by load account info from API

Source code in lettrade/exchange/live/account.py
34
35
36
37
38
def start(self):
    """Start live account by load account info from API"""
    self.account_refresh()
    self._margin = self._account.margin
    self._leverage = self._account.leverage

stop ¤

stop()

Stop account

Source code in lettrade/account/account.py
80
81
82
83
84
85
def stop(self):
    """Stop account"""
    try:
        self._equity_snapshot()
    except LetAccountInsufficientException:
        pass

MetaTraderDataFeed ¤

MetaTraderDataFeed(
    symbol: str,
    timeframe: str | int | Timedelta,
    name: str | None = None,
    columns: list[str] | None = None,
    api: LiveAPI | None = None,
    api_kwargs: dict | None = None,
    **kwargs
)

Bases: LiveDataFeed

DataFeed for MetaTrader

Parameters:

  • symbol (str) –

    Symbol of DataFeed

  • timeframe (str | int | Timedelta) –

    TimeFrame of DataFeed

  • name (str | None, default: None ) –

    Name of DataFeed, auto generate {symbol}_{timeframe} if none. Defaults to None.

  • columns (list[str] | None, default: None ) –

    List of DataFeed columns. Defaults to None.

  • api (LiveAPI | None, default: None ) –

    Live trading API. Defaults to None.

Source code in lettrade/exchange/live/data.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def __init__(
    self,
    symbol: str,
    timeframe: str | int | pd.Timedelta,
    name: str | None = None,
    columns: list[str] | None = None,
    api: LiveAPI | None = None,
    api_kwargs: dict | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        symbol (str): Symbol of DataFeed
        timeframe (str | int | pd.Timedelta): TimeFrame of DataFeed
        name (str | None, optional): Name of DataFeed, auto generate `{symbol}_{timeframe}` if none. Defaults to None.
        columns (list[str] | None, optional): List of DataFeed columns. Defaults to None.
        api (LiveAPI | None, optional): Live trading API. Defaults to None.
    """
    super().__init__(
        name=name or f"{symbol}_{timeframe}",
        timeframe=timeframe,
        columns=columns or ["open", "high", "low", "close", "volume"],
        **kwargs,
    )

    self.meta.update(symbol=symbol, base_columns=self.columns.copy())

    if api is not None:
        self._api = api
    elif api_kwargs is not None:
        self._api = self._api_cls(**api_kwargs)
    else:
        raise RuntimeError("Parameter: api or api_kwargs cannot missing")

i property ¤

Alias to lettrade.indicator and using in DataFeed by call: DataFeed.i.indicator_name()

is_main property ¤

is_main: bool

Property to check DataFeed is main DataFeed or not

l instance-attribute ¤

LetTrade DataFeed wrapper using to manage index pointer of DataFeed

meta property ¤

meta: dict

Property to get metadata of DataFeed

name property ¤

name: str

Property to get name of DataFeed

now property ¤

now: Timestamp

Property to get current index value of DataFeed

symbol property ¤

symbol: str

Property to get symbol of DataFeed

timeframe property ¤

timeframe: TimeFrame

Property to get timeframe of DataFeed

bar ¤

bar(i: int = 0) -> Timestamp

Get current pd.Timestamp value of DataFeed

Parameters:

  • i (int, default: 0 ) –

    Index. Defaults to 0.

Returns:

  • Timestamp

    pd.Timestamp: description

Source code in lettrade/data/data.py
108
109
110
111
112
113
114
115
116
117
def bar(self, i: int = 0) -> pd.Timestamp:
    """Get current pd.Timestamp value of DataFeed

    Args:
        i (int, optional): Index. Defaults to 0.

    Returns:
        pd.Timestamp: _description_
    """
    return self.l.index[i]

bars ¤

bars(
    since: int | str | Timestamp, to: int | str | Timestamp
) -> list

Get bars from LiveAPI

Parameters:

  • since (int | str | Timestamp) –

    description

  • to (int | str | Timestamp) –

    description

Returns:

  • list ( list ) –

    list of bar

Source code in lettrade/exchange/live/data.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def bars(
    self,
    since: int | str | pd.Timestamp,
    to: int | str | pd.Timestamp,
) -> list:
    """Get bars from LiveAPI

    Args:
        since (int | str | pd.Timestamp): _description_
        to (int | str | pd.Timestamp): _description_

    Returns:
        list: list of bar
    """
    return self._api.bars(
        symbol=self.symbol,
        timeframe=self.timeframe.string,
        since=since,
        to=to,
    )

bars_load ¤

bars_load(
    since: int | str | Timestamp, to: int | str | Timestamp
) -> bool

Get bar from API and push to DataFeed

Parameters:

  • since (int | str | Timestamp) –

    description

  • to (int | str | Timestamp) –

    description

Returns:

  • bool ( bool ) –

    True if has data, False if no data

Source code in lettrade/exchange/live/data.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def bars_load(
    self,
    since: int | str | pd.Timestamp,
    to: int | str | pd.Timestamp,
) -> bool:
    """Get bar from API and push to DataFeed

    Args:
        since (int | str | pd.Timestamp): _description_
        to (int | str | pd.Timestamp): _description_

    Returns:
        bool: True if has data, False if no data
    """
    bars = self.bars(since=since, to=to)

    if bars is None or len(bars) == 0:
        logger.warning("No bars data for %s", self.name)
        return False

    self.push(bars, unit=self._bar_datetime_unit)
    return True

drop ¤

drop(
    *args,
    since: int | str | Timestamp | None = None,
    to: int | str | Timestamp | None = None,
    **kwargs
) -> None

summary

Parameters:

  • since (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

  • to (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/data/data.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def drop(
    self,
    *args,
    since: int | str | pd.Timestamp | None = None,
    to: int | str | pd.Timestamp | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        since (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.
        to (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if since is None and to is None:
        super().drop(*args, **kwargs)
        return

    condiction = None

    # Since
    if since is not None:
        if isinstance(since, int):
            loc = self.l.index[since]
        elif isinstance(since, str):
            loc = pd.to_datetime(since, utc=True)
        elif isinstance(since, pd.Timestamp):
            loc = since
        else:
            raise RuntimeError(f"DataFeed.drop since {since} is invalid")
        condiction = self.index < loc

    # To
    if to is not None:
        if isinstance(to, int):
            loc = self.l.index[to]
        elif isinstance(to, str):
            loc = pd.to_datetime(to, utc=True)
        elif isinstance(to, pd.Timestamp):
            loc = to
        else:
            raise RuntimeError(f"DataFeed.drop to {to} is invalid")

        if condiction is None:
            condiction = self.index > loc
        else:
            condiction = condiction | (self.index > loc)

    index = self[condiction].index
    super().drop(index=index, inplace=True)
    self.l.reset()

    if __debug__:
        logger.debug("BackTestDataFeed %s dropped %s rows", self.name, len(index))

dump_csv ¤

dump_csv(
    path: str | None = None,
    since: int | str | datetime | None = 0,
    to: int | str | datetime | None = 1000,
    **kwargs
)

summary

Parameters:

  • path (str | None, default: None ) –

    description. Defaults to None.

  • since (int | str | datetime | None, default: 0 ) –

    description. Defaults to 0.

  • to (int | str | datetime | None, default: 1000 ) –

    description. Defaults to 1_000.

Source code in lettrade/exchange/live/data.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def dump_csv(
    self,
    path: str | None = None,
    since: int | str | datetime | None = 0,
    to: int | str | datetime | None = 1_000,
    **kwargs,
):
    """_summary_

    Args:
        path (str | None, optional): _description_. Defaults to None.
        since (int  |  str  |  datetime | None, optional): _description_. Defaults to 0.
        to (int  |  str  |  datetime | None, optional): _description_. Defaults to 1_000.
    """
    if self.empty:
        if isinstance(since, str):
            since = pd.to_datetime(since).to_pydatetime()
        if isinstance(to, str):
            to = pd.to_datetime(to).to_pydatetime()

        self.bars_load(since=since, to=to)

    if path is None:
        path = f"data/{self.name}-{since}_{to}.csv"

    from lettrade.data.extra.csv import csv_export

    csv_export(dataframe=self, path=path, **kwargs)

next ¤

next(size=1, tick=0) -> bool

Drop extra columns and load next DataFeed

Parameters:

  • size (int, default: 1 ) –

    description. Defaults to 1.

  • tick (int, default: 0 ) –

    description. Defaults to 0.

Returns:

  • bool ( bool ) –

    description

Source code in lettrade/exchange/live/data.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def next(self, size=1, tick=0) -> bool:
    """Drop extra columns and load next DataFeed

    Args:
        size (int, optional): _description_. Defaults to 1.
        tick (int, optional): _description_. Defaults to 0.

    Returns:
        bool: _description_
    """
    # Drop existed extra columns to skip reusing calculated data
    self.drop(columns=self.columns.difference(self._base_columns), inplace=True)

    self.bars_load(since=0, to=size + 1)
    self.l.go_stop()
    return True

push ¤

push(
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs
)

Push new rows to DataFeed

Parameters:

  • rows (list[list[int | float]]) –

    list of rows [["timestamp", "open price", "high price"...]]

  • unit (str | None, default: None ) –

    pandas.Timestamp parsing unit. Defaults to None.

  • utc (bool, default: True ) –

    description. Defaults to True.

Source code in lettrade/data/data.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def push(
    self,
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs,
):
    """Push new rows to DataFeed

    Args:
        rows (list[list[int | float]]): list of rows `[["timestamp", "open price", "high price"...]]`
        unit (str | None, optional): pandas.Timestamp parsing unit. Defaults to None.
        utc (bool, optional): _description_. Defaults to True.
    """
    for row in rows:
        dt = pd.to_datetime(row[0], unit=unit, utc=utc, **kwargs)
        self.at[
            dt,
            (
                "open",
                "high",
                "low",
                "close",
                "volume",
            ),
        ] = (
            row[1],  # open
            row[2],  # high
            row[3],  # low
            row[4],  # close
            row[5],  # volume
        )

    if __debug__:
        logger.debug("[%s] Update bar: \n%s", self.name, self.tail(len(rows)))

symbol_info ¤

symbol_info()

Get symbol information from API

Source code in lettrade/exchange/live/data.py
73
74
75
def symbol_info(self):
    """Get symbol information from API"""
    return self._api.market(symbol=self.symbol)

MetaTraderDataFeeder ¤

MetaTraderDataFeeder(
    api: LiveAPI | None = None,
    tick: bool = 5,
    start_size: int = 500,
    api_kwargs: dict | None = None,
    **kwargs
)

Bases: LiveDataFeeder

DataFeeder for MetaTrader

tick == 0: wait until new bar change value
tick > 0: sleep tick time (in seconds) then update
Source code in lettrade/exchange/live/feeder.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def __init__(
    self,
    api: LiveAPI | None = None,
    tick: bool = 5,
    start_size: int = 500,
    api_kwargs: dict | None = None,
    **kwargs,
) -> None:
    """
    tick:
        tick < 0: no tick, just get completed bar
        tick == 0: wait until new bar change value
        tick > 0: sleep tick time (in seconds) then update
    """
    super().__init__()

    # API init
    if api is None:
        if api_kwargs is None:
            raise RuntimeError("api or api_kwargs cannot missing")
        api = self._api_cls(**api_kwargs)

    self._api = api
    self._tick = tick
    self._start_size = start_size
    self._config = kwargs

    if isinstance(self._tick, int):
        self._wait_timeframe = TimeFrame(f"{self._tick}s")
    else:
        self._wait_timeframe = None

is_continous property ¤

is_continous

Flag check is realtime continous datafeeder

MetaTraderExchange ¤

MetaTraderExchange(api: LiveAPI, *args, **kwargs)

Bases: LiveExchange

MetaTrade 5 exchange module for lettrade

Parameters:

  • api (LiveAPI) –

    API connect to rpyc MeTrader 5 Terminal server through module mt5linux

  • *args (list, default: () ) –

    Exchange list parameters

  • **kwargs (dict, default: {} ) –

    Exchange dict parameters

Source code in lettrade/exchange/live/exchange.py
21
22
23
24
25
26
27
28
29
30
def __init__(self, api: LiveAPI, *args, **kwargs):
    """_summary_

    Args:
        api (LiveAPI): API connect to rpyc MeTrader 5 Terminal server through module `mt5linux`
        *args (list): `Exchange` list parameters
        **kwargs (dict): `Exchange` dict parameters
    """
    super().__init__(*args, **kwargs)
    self._api = api

data instance-attribute ¤

data: DataFeed

main DataFeed

datas instance-attribute ¤

datas: list[DataFeed]

List of all available DataFeed

executions instance-attribute ¤

executions: dict[str, Execution] = dict()

Execution dict by Execution.id key

history_orders instance-attribute ¤

history_orders: dict[str, Order] = dict()

History Order dict by Order.id key

history_positions instance-attribute ¤

history_positions: dict[str, Position] = dict()

History Position dict by Position.id key

orders instance-attribute ¤

orders: dict[str, Order] = dict()

Available Order dict by Order.id key

positions instance-attribute ¤

positions: dict[str, Position] = dict()

Available Position dict by Position.id key

init ¤

init(
    brain: Brain,
    feeder: DataFeeder,
    account: Account,
    commander: Commander,
) -> None

Init Exchange dependencies. Set data/datas from feeder. Set state to ExchangeState.Start.

Parameters:

Source code in lettrade/exchange/exchange.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def init(
    self,
    brain: "Brain",
    feeder: DataFeeder,
    account: Account,
    commander: Commander,
) -> None:
    """Init Exchange dependencies.
    Set data/datas from feeder.
    Set state to `ExchangeState.Start`.

    Args:
        brain (Brain): _description_
        feeder (DataFeeder): _description_
        account (Account): _description_
        commander (Commander): _description_
    """
    self._brain = brain
    self._feeder = feeder
    self._account = account
    self._commander = commander

    self.data = self._feeder.data
    self.datas = self._feeder.datas

    self._account.init(exchange=self)

    self._state = ExchangeState.Start

new_order ¤

new_order(
    size: float,
    type: OrderType | None = OrderType.Market,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    data: DataFeed | None = None,
    **kwargs
) -> OrderResult

Place new order to server

Parameters:

  • size (float) –

    description

  • type (OrderType | None, default: Market ) –

    description. Defaults to OrderType.Market.

  • limit (float | None, default: None ) –

    description. Defaults to None.

  • stop (float | None, default: None ) –

    description. Defaults to None.

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • tag (str | None, default: None ) –

    description. Defaults to None.

  • data (DataFeed | None, default: None ) –

    description. Defaults to None.

Returns:

Source code in lettrade/exchange/live/exchange.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def new_order(
    self,
    size: float,
    type: OrderType | None = OrderType.Market,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    data: DataFeed | None = None,
    **kwargs,
) -> OrderResult:
    """Place new order to server

    Args:
        size (float): _description_
        type (OrderType | None, optional): _description_. Defaults to OrderType.Market.
        limit (float | None, optional): _description_. Defaults to None.
        stop (float | None, optional): _description_. Defaults to None.
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        tag (str | None, optional): _description_. Defaults to None.
        data (DataFeed | None, optional): _description_. Defaults to None.

    Returns:
        OrderResult: _description_
    """
    if not data:
        data = self.data

    if type == OrderType.Market:
        limit = 0
        stop = 0

    order = self._order_cls(
        id=-1,
        exchange=self,
        data=data,
        size=size,
        type=type,
        limit_price=limit,
        stop_price=stop,
        sl_price=sl,
        tp_price=tp,
        tag=tag,
        **kwargs,
    )
    ok = order.place()

    # if __debug__:
    #     logger.info("New order %s at %s", order, self.data.now)

    return ok

next_next ¤

next_next()

Call after strategy.next()

Source code in lettrade/exchange/exchange.py
109
110
111
def next_next(self):
    """Call after strategy.next()"""
    self._account.next_next()

on_execution ¤

on_execution(
    execution: Execution,
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Execution event from exchange then store and notify Brain

Parameters:

  • execution (Execution) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Source code in lettrade/exchange/exchange.py
118
119
120
121
122
123
124
125
126
127
128
129
130
def on_execution(
    self,
    execution: Execution,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Execution event from exchange then store and notify Brain

    Args:
        execution (Execution): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.
    """
    self.on_executions(executions=[execution], broadcast=broadcast, **kwargs)

on_executions ¤

on_executions(
    executions: list[Execution],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Execution event from exchange then store and notify Brain

Source code in lettrade/exchange/exchange.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def on_executions(
    self,
    executions: list[Execution],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """
    Receive Execution event from exchange then store and notify Brain
    """
    for execution in executions:
        if not isinstance(execution, Execution):
            raise RuntimeError(f"{execution} is not instance of type Execution")

        if execution.id in self.executions:
            # Merge to keep Execution handler for strategy using
            # when strategy want to store Execution object
            # and object will be automatic update directly
            self.executions[execution.id].merge(execution)
            execution = self.executions[execution.id]
        else:
            self.executions[execution.id] = execution

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_executions(executions)

on_orders ¤

on_orders(
    orders: list[Order],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive orders event from exchange then store and notify Brain

Parameters:

  • orders (list[Order]) –

    description

  • broadcast (bool | None, default: True ) –

    description. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def on_orders(
    self,
    orders: list[Order],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive orders event from exchange then store and notify Brain

    Args:
        orders (list[Order]): _description_
        broadcast (bool | None, optional): _description_. Defaults to True.

    Raises:
        RuntimeError: _description_
    """
    for order in orders:
        if not isinstance(order, Order):
            raise RuntimeError(f"{order} is not instance of type Order")

        if order.is_closed:
            if order.id in self.history_orders:
                logger.warning("Order closed recall: %s", order)

            self.history_orders[order.id] = order
            if order.id in self.orders:
                del self.orders[order.id]
        else:
            if order.id in self.history_orders:
                raise RuntimeError(f"Order {order.id} closed")

            if order.id in self.orders:
                # Merge to keep Order handler for strategy using
                # when strategy want to store Order object
                # and object will be automatic update directly
                self.orders[order.id].merge(order)
                order = self.orders[order.id]
            else:
                self.orders[order.id] = order

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_orders(orders)

on_position ¤

on_position(
    position: Position,
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Position event from exchange then store and notify Brain

Parameters:

  • position (Position) –

    new comming Position

  • broadcast (bool | None, default: True ) –

    Flag notify to Brain. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def on_position(
    self,
    position: Position,
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Position event from exchange then store and notify Brain

    Args:
        position (Position): new comming `Position`
        broadcast (bool | None, optional): Flag notify to Brain. Defaults to True.

    Raises:
        RuntimeError: validate `Position` instance
    """
    self.on_positions(positions=[position], broadcast=broadcast, **kwargs)

on_positions ¤

on_positions(
    positions: list[Position],
    broadcast: bool | None = True,
    **kwargs
) -> None

Receive Positions event from exchange then store and notify Brain

Parameters:

  • positions (list[Position]) –

    list of new comming Position

  • broadcast (bool | None, default: True ) –

    Flag notify to Brain. Defaults to True.

Raises:

Source code in lettrade/exchange/exchange.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def on_positions(
    self,
    positions: list[Position],
    broadcast: bool | None = True,
    **kwargs,
) -> None:
    """Receive Positions event from exchange then store and notify Brain

    Args:
        positions (list[Position]): list of new comming `Position`
        broadcast (bool | None, optional): Flag notify to Brain. Defaults to True.

    Raises:
        RuntimeError: validate `Position` instance
    """
    for position in positions:
        if not isinstance(position, Position):
            raise RuntimeError(f"{position} is not instance of type Position")

        if position.is_exited:
            if position.id in self.history_positions:
                logger.warning("Position exited recall: %s", position)

            self.history_positions[position.id] = position
            if position.id in self.positions:
                del self.positions[position.id]

            # self._account._on_position_exit(position)
        else:
            if position.id in self.history_positions:
                raise RuntimeError(f"Position {position.id} closed: {position}")
            if position.id in self.positions:
                # Merge to keep Position handler for strategy using
                # when strategy want to store Position object
                # and object will be automatic update directly
                self.positions[position.id].merge(position)
                position = self.positions[position.id]
            else:
                self.positions[position.id] = position
                # self._account._on_position_entry(position)

    self._account.on_positions(positions)

    if self._state != ExchangeState.Run:
        return

    if broadcast:
        self._brain.on_positions(positions)

start ¤

start() -> None

Start Live exchange by: Sync positions from server

Source code in lettrade/exchange/live/exchange.py
32
33
34
35
def start(self) -> None:
    """Start Live exchange by: Sync positions from server"""
    self._api.start(exchange=self)
    return super().start()

stop ¤

stop() -> None

Stop Exchange

Source code in lettrade/exchange/exchange.py
113
114
115
116
def stop(self) -> None:
    """Stop Exchange"""
    self._state = ExchangeState.Stop
    self._account.stop()

MetaTraderExecution ¤

MetaTraderExecution(
    pl: float | None = None,
    fee: float | None = None,
    tag: str | None = None,
    **kwargs
)

Bases: LiveExecution

Execution for MetaTrader

Source code in lettrade/exchange/metatrader/trade.py
36
37
38
39
40
41
42
43
44
45
46
def __init__(
    self,
    pl: float | None = None,
    fee: float | None = None,
    tag: str | None = None,
    **kwargs,
):
    super().__init__(**kwargs)
    self.pl: float = pl
    self.fee: float = fee
    self.tag: str | None = tag

is_long property ¤

is_long: bool

True if side is long (size is positive).

Returns:

  • bool ( bool ) –

    True/False

is_short property ¤

is_short: bool

True if side is short (size is negative).

Returns:

  • bool ( bool ) –

    description

side property ¤

side: TradeSide

True if side is short (size is negative).

Returns:

from_raw classmethod ¤

from_raw(
    raw,
    exchange: MetaTraderExchange,
    data: LiveDataFeed | None = None,
    api: MetaTraderAPI | None = None,
) -> MetaTraderExecution | None

Building new MetaTraderExecution from live api raw object

Parameters:

Returns:

Source code in lettrade/exchange/metatrader/trade.py
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
@classmethod
def from_raw(
    cls,
    raw,
    exchange: "MetaTraderExchange",
    data: "LiveDataFeed | None" = None,
    api: MetaTraderAPI | None = None,
) -> "MetaTraderExecution | None":
    """Building new MetaTraderExecution from live api raw object

    Args:
        raw (_type_): _description_
        exchange (MetaTraderExchange): _description_

    Returns:
        MetaTraderExecution: _description_
    """
    # DataFeed
    if data is None:
        for d in exchange.datas:
            if d.symbol == raw.symbol:
                data = d
                break
        if data is None:
            logger.warning("Raw execution %s is not handling %s", raw.symbol, raw)
            return
    # Side
    match raw.type:
        case MT5.DEAL_TYPE_BUY:
            side = TradeSide.Buy
        case MT5.DEAL_TYPE_SELL:
            side = TradeSide.Sell
        case _:
            logger.warning(
                "Raw execution %s type %s is not handling %s",
                raw.symbol,
                raw.type,
                raw,
            )
            return

    return cls(
        exchange=exchange,
        id=raw.ticket,
        data=data,
        order_id=raw.order,
        position_id=raw.position_id,
        size=side * raw.volume,
        price=raw.price,
        pl=raw.profit,
        fee=raw.fee + raw.swap + raw.commission,
        at=pd.to_datetime(raw.time_msc, unit="ms", utc=True),
        tag=raw.comment,
        api=api,
        raw=raw,
    )

merge ¤

merge(other: Execution)

Merge to keep object handler but not overwrite for Strategy using when Strategy want to store object and object will be automatic update directly

Source code in lettrade/exchange/execution.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def merge(self, other: "Execution"):
    """
    Merge to keep object handler but not overwrite for Strategy using when Strategy want to store object and object will be automatic update directly
    """
    if other is self:
        return

    if self.id != other.id:
        raise RuntimeError(f"Merge difference id {self.id} != {other.id} execution")

    self.price = other.price
    self.size = other.size

    if other.at:
        self.at = other.at
    if other.order_id:
        self.order_id = other.order_id
    if other.order:
        self.order = other.order
    if other.position_id:
        self.position_id = other.position_id
    if other.position:
        self.position = other.position

MetaTraderOrder ¤

MetaTraderOrder(is_real: bool = True, **kwargs)

Bases: LiveOrder

Order for MetaTrader

Source code in lettrade/exchange/metatrader/trade.py
111
112
113
114
115
def __init__(self, is_real: bool = True, **kwargs):
    super().__init__(**kwargs)

    self.is_real: bool = is_real
    """Flag to check `Order` is real, cannot duplicate id, cannot recall from history"""

is_closed property ¤

is_closed: bool

Flag to check Order closed

Returns:

  • bool ( bool ) –

    True if state in [OrderState.Filled, OrderState.Canceled]

is_long property ¤

is_long: bool

True if side is long (size is positive).

Returns:

  • bool ( bool ) –

    True/False

is_opening property ¤

is_opening: bool

Flag to check Order still alive

Returns:

  • bool ( bool ) –

    True if state in [OrderState.Pending, OrderState.Placed, OrderState.Partial]

is_real instance-attribute ¤

is_real: bool = is_real

Flag to check Order is real, cannot duplicate id, cannot recall from history

is_short property ¤

is_short: bool

True if side is short (size is negative).

Returns:

  • bool ( bool ) –

    description

is_sl_order property ¤

is_sl_order: bool

Order is stop-loss order of a Position

Returns:

  • bool ( bool ) –

    description

is_tp_order property ¤

is_tp_order: bool

Order is take-profit order of a Position

Returns:

  • bool ( bool ) –

    description

limit property ¤

limit: float | None

Getter of limit_price

Returns:

  • float | None

    float | None: float or None

place_price property ¤

place_price: float | None

Getter of place_price

Returns:

  • float | None

    float | None: float or None

side property ¤

side: TradeSide

True if side is short (size is negative).

Returns:

sl property ¤

sl: float | None

Getter of sl_price

Returns:

  • float | None

    float | None: float or None

stop property ¤

stop: float | None

Getter of stop_price

Returns:

  • float | None

    float | None: float or None

tp property ¤

tp: float | None

Getter of tp_price

Returns:

  • float | None

    float | None: float or None

cancel ¤

cancel(**kwargs) -> OrderResult

Cancel order

Returns:

Source code in lettrade/exchange/metatrader/trade.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def cancel(self, **kwargs) -> OrderResult:
    """Cancel order

    Returns:
        OrderResult: _description_
    """
    if not self.parent:
        # Abandon order
        result = self._api.order_close(order=self, **kwargs)
    else:
        # Virtual SL/TP order of trade
        result = None

    return super(LiveOrder, self).cancel(raw=result)

fill ¤

fill(
    price: float, at: Timestamp, raw: object | None = None
) -> OrderResult

Fill Order. Set status to OrderState.Executed. Send event to Exchange

Parameters:

  • price (float) –

    Executed price

  • at (Timestamp) –

    Executed bar

Raises:

Returns:

Source code in lettrade/exchange/order.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
def fill(
    self,
    price: float,
    at: pd.Timestamp,
    raw: object | None = None,
) -> "OrderResult":
    """Fill `Order`.
    Set `status` to `OrderState.Executed`.
    Send event to `Exchange`

    Args:
        price (float): Executed price
        at (pd.Timestamp): Executed bar

    Raises:
        RuntimeError: _description_

    Returns:
        OrderResult: result of `Order`
    """
    if self.state != OrderState.Placed:
        raise RuntimeError(f"Order {self.id} state {self.state} is not Placed")

    self.filled_at = at
    self.filled_price = price
    self.state = OrderState.Filled
    self.exchange.on_order(self)
    return OrderResultOk(order=self, raw=raw)

from_position classmethod ¤

from_position(
    position: MetaTraderPosition,
    sl: float | None = None,
    tp: float | None = None,
) -> MetaTraderOrder

summary

Parameters:

  • position (MetaTraderPosition) –

    description

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

Raises:

Returns:

Source code in lettrade/exchange/metatrader/trade.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
@classmethod
def from_position(
    cls,
    position: "MetaTraderPosition",
    sl: float | None = None,
    tp: float | None = None,
) -> "MetaTraderOrder":
    """_summary_

    Args:
        position (MetaTraderPosition): _description_
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_

    Returns:
        MetaTraderOrder: _description_
    """
    if not sl and not tp:
        raise RuntimeError("not sl and not tp")
    return cls(
        id=f"{position.id}-{'sl' if sl else 'tp'}",
        exchange=position.exchange,
        data=position.data,
        state=OrderState.Placed,
        type=OrderType.Stop if sl else OrderType.Limit,
        size=-position.size,
        limit_price=tp,
        stop_price=sl,
        parent=position,
        placed_at=position.entry_at,
        is_real=False,
    )

from_raw classmethod ¤

from_raw(
    raw: Any,
    exchange: MetaTraderExchange,
    data: LiveDataFeed | None = None,
    api: MetaTraderAPI | None = None,
) -> MetaTraderOrder | None

summary

Parameters:

Raises:

Returns:

Source code in lettrade/exchange/metatrader/trade.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
@classmethod
def from_raw(
    cls,
    raw: Any,
    exchange: "MetaTraderExchange",
    data: "LiveDataFeed | None" = None,
    api: MetaTraderAPI | None = None,
) -> "MetaTraderOrder | None":
    """_summary_

    Args:
        raw (Any): _description_
        exchange (MetaTraderExchange): _description_
        data (LiveDataFeed | None, optional): _description_. Defaults to None.
        api (MetaTraderAPI | None, optional): _description_. Defaults to None.

    Raises:
        NotImplementedError: _description_

    Returns:
        MetaTraderOrder | None: _description_
    """
    # DataFeed
    if data is None:
        for d in exchange.datas:
            if d.symbol == raw.symbol:
                data = d
                break
        if data is None:
            logger.warning("Raw order %s is not handling %s", raw.symbol, raw)
            return

    # Prices & Side & Type
    limit_price = None
    stop_price = None
    match raw.type:
        case MT5.ORDER_TYPE_BUY:
            side = TradeSide.Buy
            type = OrderType.Market
        case MT5.ORDER_TYPE_SELL:
            side = TradeSide.Sell
            type = OrderType.Market
        case MT5.ORDER_TYPE_BUY_LIMIT:
            side = TradeSide.Buy
            type = OrderType.Limit
            limit_price = raw.price_open
        case MT5.ORDER_TYPE_SELL_LIMIT:
            side = TradeSide.Sell
            type = OrderType.Limit
            limit_price = raw.price_open
        case MT5.ORDER_TYPE_BUY_STOP:
            side = TradeSide.Buy
            type = OrderType.Stop
            stop_price = raw.price_open
        case MT5.ORDER_TYPE_SELL_STOP:
            side = TradeSide.Sell
            type = OrderType.Stop
            stop_price = raw.price_open
        # case MT5.ORDER_TYPE_BUY_STOP_LIMIT:
        #     side = TradeSide.Buy
        #     type = OrderType.StopLimit
        #     # TODO
        #     limit_price = raw.price_open
        #     stop_price = raw.price_open
        # case MT5.ORDER_TYPE_SELL_STOP_LIMIT:
        #     side = TradeSide.Sell
        #     type = OrderType.StopLimit
        #     # TODO
        #     limit_price = raw.price_open
        #     stop_price = raw.price_open
        # case MT5.ORDER_TYPE_CLOSE_BY:
        case _:
            raise NotImplementedError(
                f"Order type {raw.type} is not implement",
                raw,
            )
    # State
    match raw.state:
        case MT5.ORDER_STATE_STARTED:
            state = OrderState.Pending
        case MT5.ORDER_STATE_PLACED:
            state = OrderState.Placed
        case MT5.ORDER_STATE_CANCELED:
            state = OrderState.Canceled
        case MT5.ORDER_STATE_PARTIAL:
            state = OrderState.Partial
        case MT5.ORDER_STATE_FILLED:
            state = OrderState.Filled
        case MT5.ORDER_STATE_REJECTED:
            state = OrderState.Canceled
        case MT5.ORDER_STATE_EXPIRED:
            state = OrderState.Canceled
        case MT5.ORDER_STATE_REQUEST_ADD:
            state = OrderState.Placed
        case MT5.ORDER_STATE_REQUEST_MODIFY:
            state = OrderState.Placed
        case MT5.ORDER_STATE_REQUEST_CANCEL:
            state = OrderState.Canceled
        case _:
            raise NotImplementedError(
                f"Raw order state {raw.state} is not implement"
            )

    order = cls(
        exchange=exchange,
        id=raw.ticket,
        state=state,
        data=data,
        size=side * (raw.volume_current or raw.volume_initial),
        type=type,
        limit_price=limit_price,
        stop_price=stop_price,
        sl_price=raw.sl or None,
        tp_price=raw.tp or None,
        tag=raw.comment,
        placed_at=pd.to_datetime(raw.time_setup_msc, unit="ms", utc=True),
        api=api,
        raw=raw,
    )

    if hasattr(raw, "time_done_msc"):
        order.filled_price = raw.price_current
        order.filled_at = pd.to_datetime(raw.time_done_msc, unit="ms", utc=True)

    return order

merge ¤

merge(other: Order)

Update current Order variables by other Order

Parameters:

  • other (Order) –

    Merge source Order

Raises:

Source code in lettrade/exchange/order.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def merge(self, other: "Order"):
    """Update current `Order` variables by other `Order`

    Args:
        other (Order): Merge source `Order`

    Raises:
        RuntimeError: Validate same id
    """
    if other is self:
        return

    if self.id != other.id:
        raise RuntimeError(f"Merge difference id {self.id} != {other.id} order")

    self.size = other.size
    self.sl_price = other.sl_price
    self.tp_price = other.tp_price

    if other.limit_price:
        self.limit_price = other.limit_price
    if other.stop_price:
        self.stop_price = other.stop_price
    if other.placed_at:
        self.placed_at = other.placed_at

    if other.filled_price:
        self.filled_price = other.filled_price
    if other.filled_at:
        self.filled_at = other.filled_at

    if other.parent:
        self.parent = other.parent

place ¤

place() -> OrderResult

summary

Raises:

Returns:

Source code in lettrade/exchange/metatrader/trade.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def place(self) -> OrderResult:
    """_summary_

    Raises:
        RuntimeError: _description_

    Returns:
        OrderResult: _description_
    """
    if self.state != OrderState.Pending:
        raise RuntimeError(f"Order {self.id} state {self.state} is not Pending")

    try:
        result = self._api.order_open(self)

        self.raw = result
        self.id = result.order

        # TODO: get current order time
        return super(LiveOrder, self).place(at=self.data.l.index[0], raw=result)
    except LetLiveOrderInvalidException as e:
        error = OrderResultError(
            error=e.message,
            order=self,
            raw=e.raw,
        )
        logger.error("Place order %s", str(error))
        self.exchange.on_notify(error=error)
        return error

update ¤

update(
    limit_price: float | None = None,
    stop_price: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    caller: float | None = None,
    **kwargs
) -> OrderResult

summary

Parameters:

  • limit_price (float | None, default: None ) –

    description. Defaults to None.

  • stop_price (float | None, default: None ) –

    description. Defaults to None.

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • caller (float | None, default: None ) –

    description. Defaults to None.

Raises:

Returns:

Source code in lettrade/exchange/metatrader/trade.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def update(
    self,
    limit_price: float | None = None,
    stop_price: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    caller: float | None = None,
    **kwargs,
) -> OrderResult:
    """_summary_

    Args:
        limit_price (float | None, optional): _description_. Defaults to None.
        stop_price (float | None, optional): _description_. Defaults to None.
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        caller (float | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_

    Returns:
        OrderResult: _description_
    """
    if caller is self:
        raise RuntimeError(f"Order recusive update {self}")

    if self.parent is None:
        result = self._api.order_update(
            order=self,
            limit_price=limit_price,
            stop_price=stop_price,
            sl=sl,
            tp=tp,
            **kwargs,
        )
        return super(LiveOrder, self).update(
            limit_price=result.limit_price,
            stop_price=result.stop_price,
            sl=result.sl,
            tp=result.tp,
        )
    else:
        # SL/TP Order just a virtual order
        if caller is not self.parent:
            if self.is_sl_order:
                self.parent.update(sl=stop_price, caller=self)
            elif self.is_tp_order:
                self.parent.update(tp=limit_price, caller=self)
            else:
                raise RuntimeError(f"Abandon order {self}")

        return super(LiveOrder, self).update(
            limit_price=limit_price,
            stop_price=stop_price,
        )

MetaTraderPosition ¤

MetaTraderPosition(
    id: str,
    exchange: LiveExchange,
    data: LiveDataFeed,
    size: float,
    parent: Order,
    tag: str | None = None,
    state: PositionState = PositionState.Open,
    entry_price: float | None = None,
    entry_fee: float = 0.0,
    entry_at: int | None = None,
    sl_order: Order | None = None,
    tp_order: Order | None = None,
    api: LiveAPI | None = None,
    raw: object | None = None,
    **kwargs
)

Bases: LivePosition

Position for MetaTrader

Source code in lettrade/exchange/live/trade.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
def __init__(
    self,
    id: str,
    exchange: "LiveExchange",
    data: "LiveDataFeed",
    size: float,
    parent: Order,
    tag: str | None = None,
    state: PositionState = PositionState.Open,
    entry_price: float | None = None,
    entry_fee: float = 0.0,
    entry_at: int | None = None,
    sl_order: Order | None = None,
    tp_order: Order | None = None,
    api: LiveAPI | None = None,
    raw: object | None = None,
    **kwargs,
):
    super().__init__(
        id=id,
        exchange=exchange,
        data=data,
        size=size,
        parent=parent,
        tag=tag,
        state=state,
        entry_price=entry_price,
        entry_fee=entry_fee,
        entry_at=entry_at,
        sl_order=sl_order,
        tp_order=tp_order,
        api=api,
        raw=raw,
        **kwargs,
    )

fee property ¤

fee: float

Fee/Estimate Fee for trade

Returns:

  • float ( float ) –

    Fee

is_exited property ¤

is_exited: bool

Flag to check Position state.

Returns:

  • bool ( bool ) –

    True if the trade exited

is_long property ¤

is_long: bool

True if side is long (size is positive).

Returns:

  • bool ( bool ) –

    True/False

is_opening property ¤

is_opening: bool

Flag to check Position state.

Returns:

  • bool ( bool ) –

    True if the trade opening

is_short property ¤

is_short: bool

True if side is short (size is negative).

Returns:

  • bool ( bool ) –

    description

pl property ¤

pl: float

Estimate Profit or Loss of Position

Returns:

  • float ( float ) –

    PnL

side property ¤

side: TradeSide

True if side is short (size is negative).

Returns:

exit ¤

exit() -> PositionResult

summary

Returns:

Source code in lettrade/exchange/metatrader/trade.py
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
def exit(self) -> PositionResult:
    """_summary_

    Returns:
        PositionResult: _description_
    """
    result = self._api.position_close(position=self)
    if result.code != 0:
        logger.error("Update position %s", str(result))
        error = PositionResultError(
            error=result.error,
            position=self,
            raw=result,
        )
        self.exchange.on_notify(error=error)
        return error

    execution_raw = self._api.execution_get(id=result.execution_id)

    # TODO: execution object and event
    result.execution_raw = execution_raw

    return super(LivePosition, self).exit(
        price=result.price,
        at=pd.to_datetime(execution_raw.time_msc, unit="ms", utc=True),
        pl=execution_raw.profit,
        fee=execution_raw.fee + execution_raw.swap + execution_raw.commission,
        raw=result,
    )

from_raw classmethod ¤

from_raw(
    raw,
    exchange: MetaTraderExchange,
    state: PositionState = PositionState.Open,
    data: LiveDataFeed | None = None,
    api: MetaTraderAPI = None,
) -> MetaTraderPosition

summary

Parameters:

  • raw (_type_) –

    description

  • exchange (MetaTraderExchange) –

    description

  • data (LiveDataFeed, default: None ) –

    description. Defaults to None.

  • api (MetaTraderAPI, default: None ) –

    description. Defaults to None.

Raises:

Returns:

Source code in lettrade/exchange/metatrader/trade.py
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
@classmethod
def from_raw(
    cls,
    raw,
    exchange: "MetaTraderExchange",
    state: PositionState = PositionState.Open,
    data: "LiveDataFeed | None" = None,
    api: MetaTraderAPI = None,
) -> "MetaTraderPosition":
    """_summary_

    Args:
        raw (_type_): _description_
        exchange (MetaTraderExchange): _description_
        data (LiveDataFeed, optional): _description_. Defaults to None.
        api (MetaTraderAPI, optional): _description_. Defaults to None.

    Raises:
        NotImplementedError: _description_

    Returns:
        MetaTraderPosition: _description_
    """
    # DataFeed
    if data is None:
        for d in exchange.datas:
            if d.symbol == raw.symbol:
                data = d
                break
        if data is None:
            logger.warning("Raw position %s is not handling %s", raw.symbol, raw)
            return

    # Side
    match raw.type:
        case MT5.POSITION_TYPE_BUY:
            side = TradeSide.Buy
        case MT5.POSITION_TYPE_SELL:
            side = TradeSide.Sell
        case _:
            raise NotImplementedError(
                f"Position type {raw.type} is not implement",
                raw,
            )
    # API
    if api is None:
        api = exchange._api

    position = cls(
        exchange=exchange,
        id=raw.ticket,
        data=data,
        state=state,
        size=side * raw.volume,
        entry_price=raw.price_open,
        entry_fee=raw.swap,
        entry_at=pd.to_datetime(raw.time_msc, unit="ms", utc=True),
        parent=None,
        tag=raw.comment,
        api=api,
        raw=raw,
    )

    # SL
    if raw.sl > 0.0:
        position.sl_order = exchange._order_cls.from_position(
            position=position, sl=raw.sl
        )
        exchange.on_order(position.sl_order)

    # TP
    if raw.tp > 0.0:
        position.tp_order = exchange._order_cls.from_position(
            position=position, tp=raw.tp
        )
        exchange.on_order(position.tp_order)

    if position.state == PositionState.Exit:
        if position.exit_price is None:
            if not hasattr(raw, "executions"):
                raw.executions = api.executions_get(position_id=position.id)

            exchange.on_executions_event(raw.executions, broadcast=False)

            execution = exchange.executions[raw.executions[-1].ticket]
            position.exit_at = execution.at
            position.exit_price = execution.price
            position.exit_pl = execution.pl
            position.exit_fee = execution.fee

    return position

merge ¤

merge(other: LivePosition) -> bool

Merge LivePosition from another

Parameters:

Returns:

  • bool ( bool ) –

    description

Source code in lettrade/exchange/live/trade.py
288
289
290
291
292
293
294
295
296
297
298
299
300
def merge(self, other: "LivePosition") -> bool:
    """Merge LivePosition from another

    Args:
        other (LivePosition): _description_

    Returns:
        bool: _description_
    """
    if not super().merge(other):
        return False
    self.raw = other.raw
    return True

update ¤

update(
    sl: float | None = None,
    tp: float | None = None,
    caller: float | None = None,
    **kwargs
) -> PositionResult

summary

Parameters:

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • caller (float | None, default: None ) –

    description. Defaults to None.

Raises:

Returns:

Source code in lettrade/exchange/metatrader/trade.py
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
def update(
    self,
    sl: float | None = None,
    tp: float | None = None,
    caller: float | None = None,
    **kwargs,
) -> PositionResult:
    """_summary_

    Args:
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        caller (float | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
        RuntimeError: _description_

    Returns:
        PositionResult: _description_
    """
    if not sl and not tp:
        raise RuntimeError("Update sl=None and tp=None")
    if caller is self:
        raise RuntimeError(f"Position recusive update {self}")

    result = self._api.position_update(position=self, sl=sl, tp=tp)
    if result.code != 0:
        logger.error("Update position %s", str(result))
        error = PositionResultError(
            error=result.error,
            position=self,
            raw=result,
        )
        self.exchange.on_notify(error=error)
        return error

    if sl is not None:
        if self.sl_order:
            if caller is not self.sl_order:
                self.sl_order.update(stop_price=sl, caller=self)
        else:
            self.sl_order = self.exchange._order_cls.from_position(
                position=self, sl=sl
            )

    if tp is not None:
        if self.tp_order:
            if caller is not self.tp_order:
                self.tp_order.update(limit_price=tp, caller=self)
        else:
            self.tp_order = self.exchange._order_cls.from_position(
                position=self, tp=tp
            )

    return super(LivePosition, self).update(raw=result)

OptimizePlotter ¤

OptimizePlotter()

Bases: Plotter

Class help to plot lettrade

Source code in lettrade/exchange/backtest/plot.py
11
12
def __init__(self) -> None:
    super().__init__()

contour ¤

contour(x: str, y: str, z: str = 'equity', **kwargs)
Source code in lettrade/exchange/backtest/plot.py
26
27
def contour(self, x: str, y: str, z: str = "equity", **kwargs):
    """"""

heatmap ¤

heatmap(x: str, y: str, z: str = 'equity', **kwargs)
Source code in lettrade/exchange/backtest/plot.py
23
24
def heatmap(self, x: str, y: str, z: str = "equity", **kwargs):
    """"""

load abstractmethod ¤

load()

Load plot config from Strategy.plot() and setup candlestick/equity

Source code in lettrade/plot/plot.py
 9
10
11
@abstractmethod
def load(self):
    """Load plot config from `Strategy.plot()` and setup candlestick/equity"""

on_done ¤

on_done()
Source code in lettrade/exchange/backtest/plot.py
20
21
def on_done(self):
    """"""

on_result ¤

on_result(result)
Source code in lettrade/exchange/backtest/plot.py
17
18
def on_result(self, result):
    """"""

plot abstractmethod ¤

plot(**kwargs)

Plot equity, orders, and positions then show

Source code in lettrade/plot/plot.py
13
14
15
@abstractmethod
def plot(self, **kwargs):
    """Plot `equity`, `orders`, and `positions` then show"""

stop abstractmethod ¤

stop()

stop plotter

Source code in lettrade/plot/plot.py
17
18
19
@abstractmethod
def stop(self):
    """stop plotter"""

PlotColor ¤

Bases: str, Enum

Enum plot color

AMBER class-attribute instance-attribute ¤

AMBER = '#fa0'

BLUE class-attribute instance-attribute ¤

BLUE = '#4287ff'

CYAN class-attribute instance-attribute ¤

CYAN = '#00bad6'

DEEP_ORANGE class-attribute instance-attribute ¤

DEEP_ORANGE = '#ff6e42'

DEEP_PURPLE class-attribute instance-attribute ¤

DEEP_PURPLE = '#7c4dff'

GREEN class-attribute instance-attribute ¤

GREEN = '#00c753'

INDIGO class-attribute instance-attribute ¤

INDIGO = '#526cfe'

LIGHT_BLUE class-attribute instance-attribute ¤

LIGHT_BLUE = '#0091eb'

LIGHT_GREEN class-attribute instance-attribute ¤

LIGHT_GREEN = '#63de17'

LIGHT_PINK class-attribute instance-attribute ¤

LIGHT_PINK = '#f06292'

LIGHT_RED class-attribute instance-attribute ¤

LIGHT_RED = '#e6695b'

LIGHT_YELLOW class-attribute instance-attribute ¤

LIGHT_YELLOW = '#fff176'

LIME class-attribute instance-attribute ¤

LIME = '#b0eb00'

ORANGE class-attribute instance-attribute ¤

ORANGE = '#ff9100'

PINK class-attribute instance-attribute ¤

PINK = '#f50056'

PURPLE class-attribute instance-attribute ¤

PURPLE = '#df41fb'

RED class-attribute instance-attribute ¤

RED = '#ff1947'

TEAL class-attribute instance-attribute ¤

TEAL = '#00bda4'

YELLOW class-attribute instance-attribute ¤

YELLOW = '#ffd500'

PlotlyBotPlotter ¤

PlotlyBotPlotter(bot: LetTradeBot)

Bases: BotPlotter

Class help to plot lettrade

Source code in lettrade/plot/bot.py
34
35
36
37
38
39
40
41
def __init__(self, bot: "LetTradeBot") -> None:
    self.bot = bot
    self.feeder = bot.feeder
    self.exchange = bot.exchange
    self.account = bot.account
    self.strategy = bot.strategy

    self.datas = self.feeder.datas

data property writable ¤

data: DataFeed

Get plotting main datafeed

Returns:

datas instance-attribute ¤

datas: list[DataFeed] = datas

All plotting datafeeds

jump ¤

jump(
    since: int | str | Timestamp | None = None,
    order_id: str | None = None,
    position_id: str | None = None,
    range: int = 300,
    name: str | None = None,
)

Jump to place on datefeed

Parameters:

  • since (int | str | Timestamp | None, default: None ) –

    Jump to index/datetime. Defaults to None.

  • order_id (str | None, default: None ) –

    Jump to order id. Defaults to None.

  • position_id (str | None, default: None ) –

    Jump to position id. Defaults to None.

  • range (int, default: 300 ) –

    number of candle plot. Defaults to 300.

  • name (str | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/plot/bot.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def jump(
    self,
    since: int | str | pd.Timestamp | None = None,
    order_id: str | None = None,
    position_id: str | None = None,
    range: int = 300,
    name: str | None = None,
):
    """Jump to place on datefeed

    Args:
        since (int | str | pd.Timestamp | None, optional): Jump to index/datetime. Defaults to None.
        order_id (str | None, optional): Jump to order id. Defaults to None.
        position_id (str | None, optional): Jump to position id. Defaults to None.
        range (int, optional): number of candle plot. Defaults to 300.
        name (str | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if self._datas_stored is None:
        self._datas_stored = self.datas.copy()

    if since is None:
        if order_id is not None:  # Jump to order id
            if not isinstance(order_id, str):
                order_id = str(order_id)

            if order_id in self.exchange.orders:
                order = self.exchange.orders[order_id]
            elif order_id in self.exchange.history_orders:
                order = self.exchange.history_orders[order_id]
            else:
                raise RuntimeError(f"Order id {order_id} not found")

            loc = self._data_stored.l.index.get_loc(order.placed_at)
            since = loc - int(range / 2)

        elif position_id is not None:  # Jump to position id
            if not isinstance(position_id, str):
                position_id = str(position_id)

            if position_id in self.exchange.positions:
                position = self.exchange.positions[position_id]
            elif position_id in self.exchange.history_positions:
                position = self.exchange.history_positions[position_id]
            else:
                raise RuntimeError(f"Position id {position_id} not found")

            loc = self._data_stored.l.index.get_loc(position.entry_at)
            since = loc - int(range / 2)
        else:  # Reset
            self.jump_reset()
            return

    elif isinstance(since, str):  # Parse string to pd.Timestamp, then since=index
        since = pd.to_datetime(since, utc=True)
        since = self._data_stored.l.index.get_loc(since)
    elif isinstance(since, pd.Timestamp):  # Get index of Timestamp
        since = self._data_stored.l.index.get_loc(since)

    if name is None:
        name = self._data_stored.name

    # Since min at pointer_start
    if since < self._data_stored.l.pointer_start:
        since = self._data_stored.l.pointer_start
    # Since max at pointer_stop
    if since > self._data_stored.l.pointer_stop - range:
        since = self._data_stored.l.pointer_stop - range

    # Jump
    jump_start_dt = None
    jump_stop_dt = None
    for i, data in enumerate(self._datas_stored):
        if i == 0:
            self.datas[i] = data.__class__(
                data=data.l[since : since + range],
                name=data.name,
                timeframe=data.timeframe,
            )
            jump_start_dt = self.data.index[0]
            jump_stop_dt = self.data.index[-1]
        else:
            self.datas[i] = data.__class__(
                data=data.loc[
                    (data.index >= jump_start_dt) & (data.index <= jump_stop_dt)
                ],
                name=data.name,
                timeframe=data.timeframe,
            )

        if hasattr(data, DATAFRAME_PLOTTERS_NAME):
            object.__setattr__(
                self.datas[i],
                DATAFRAME_PLOTTERS_NAME,
                getattr(data, DATAFRAME_PLOTTERS_NAME),
            )

    # Reload data
    self.load()

jump_reset ¤

jump_reset() -> bool

Reset jump datafeeds back to bot datafeeds

Source code in lettrade/plot/bot.py
162
163
164
165
166
167
168
def jump_reset(self) -> bool:
    """Reset jump datafeeds back to bot datafeeds"""
    if not self._datas_stored or self.data is self._data_stored:
        return False

    self.datas = self._datas_stored.copy()
    return True

load ¤

load()

Load plot config from Strategy.plot() and setup candlestick/equity

Source code in lettrade/plot/plotly/plotly.py
593
594
595
596
597
598
599
600
601
602
603
604
605
def load(self):
    """Load plot config from `Strategy.plot()` and setup candlestick/equity"""

    config = self._config_default()
    config = self._config_strategy(config)
    config = self._config_standard(config)

    params: dict = config.setdefault("params", dict())
    self.figure = make_subplots(**params)

    self._plot_config(config)

    self.figure.update_layout(**config["layout"])

plot ¤

plot(jump: dict | None = None, **kwargs)

Plotly show figure

Parameters:

  • jump (dict | None, default: None ) –

    description. Defaults to None.

Source code in lettrade/plot/plotly/plotly.py
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
def plot(self, jump: dict | None = None, **kwargs):
    """Plotly show figure

    Args:
        jump (dict | None, optional): _description_. Defaults to None.
    """
    if jump is not None:
        self.jump(**jump)
    elif self.figure is None:
        self.load()
    else:
        if self.jump_reset():
            self.load()

    params = dict(layout_xaxis_rangeslider_visible=False)
    params.update(**kwargs)
    self.figure.update(**params)

    self.figure.show()

stop ¤

stop()

stop plotter

Source code in lettrade/plot/plotly/plotly.py
16
17
def stop(self):
    """stop plotter"""

PlotlyOptimizePlotter ¤

PlotlyOptimizePlotter(total=None, process_bar: bool = True)

Bases: OptimizePlotter

Source code in lettrade/exchange/backtest/plotly/optimize.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def __init__(self, total=None, process_bar: bool = True) -> None:
    super().__init__()

    self._total = total

    if process_bar:
        from rich.progress import (
            BarColumn,
            Console,
            Progress,
            SpinnerColumn,
            TaskProgressColumn,
            TextColumn,
            TimeElapsedColumn,
            TimeRemainingColumn,
        )

        console = Console(record=True, force_jupyter=False)
        self._process_bar = Progress(
            SpinnerColumn(),
            # *Progress.get_default_columns(),
            TextColumn("[progress.description]{task.description}"),
            BarColumn(),
            TaskProgressColumn(),
            TextColumn("[bold blue][{task.completed}/{task.total}]"),
            TimeRemainingColumn(),
            TimeElapsedColumn(),
            console=console,
            # transient=False,
        )
        self._process_bar.add_task("[cyan2]Optimizing", total=total)
        self._process_bar.start()
    else:
        self._process_bar = None

contour ¤

contour(
    x: str = None,
    y: str = None,
    z: str = "equity",
    histfunc="max",
    **kwargs
)

Plot optimize contour

Parameters:

  • x (str, default: None ) –

    description. Defaults to None.

  • y (str, default: None ) –

    description. Defaults to None.

  • z (str, default: 'equity' ) –

    description. Defaults to "equity".

  • histfunc (str, default: 'max' ) –

    description. Defaults to "max".

Source code in lettrade/exchange/backtest/plotly/optimize.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def contour(
    self,
    x: str = None,
    y: str = None,
    z: str = "equity",
    histfunc="max",
    **kwargs,
):
    """Plot optimize contour

    Args:
        x (str, optional): _description_. Defaults to None.
        y (str, optional): _description_. Defaults to None.
        z (str, optional): _description_. Defaults to "equity".
        histfunc (str, optional): _description_. Defaults to "max".
    """
    x, y, z = self._xyz_default(x, y, z)
    df = pd.DataFrame(self._xyzs(x=x, y=y, z=z))
    fig = px.density_contour(
        df,
        x=x,
        y=y,
        z=z,
        nbinsx=int(df[x].max() - df[x].min() + 1),
        nbinsy=int(df[y].max() - df[y].min() + 1),
        histfunc=histfunc,
        **kwargs,
    )
    fig.update_traces(contours_coloring="fill", contours_showlabels=True)
    fig.show()

heatmap ¤

heatmap(
    x: str = None,
    y: str = None,
    z: str = "equity",
    histfunc="max",
    **kwargs
)

Plot optimize heatmap

Parameters:

  • x (str, default: None ) –

    description. Defaults to None.

  • y (str, default: None ) –

    description. Defaults to None.

  • z (str, default: 'equity' ) –

    description. Defaults to "equity".

  • histfunc (str, default: 'max' ) –

    description. Defaults to "max".

Source code in lettrade/exchange/backtest/plotly/optimize.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def heatmap(
    self,
    x: str = None,
    y: str = None,
    z: str = "equity",
    histfunc="max",
    **kwargs,
):
    """Plot optimize heatmap

    Args:
        x (str, optional): _description_. Defaults to None.
        y (str, optional): _description_. Defaults to None.
        z (str, optional): _description_. Defaults to "equity".
        histfunc (str, optional): _description_. Defaults to "max".
    """
    x, y, z = self._xyz_default(x, y, z)
    df = pd.DataFrame(self._xyzs(x=x, y=y, z=z))
    fig = px.density_heatmap(
        df,
        x=x,
        y=y,
        z=z,
        nbinsx=int(df[x].max() - df[x].min() + 1),
        nbinsy=int(df[y].max() - df[y].min() + 1),
        histfunc=histfunc,
        color_continuous_scale="Viridis",
        **kwargs,
    )
    fig.show()

plot ¤

plot(**kwargs)

Plot optimize result

Source code in lettrade/exchange/backtest/plotly/optimize.py
69
70
71
72
73
74
75
76
77
78
79
80
def plot(self, **kwargs):
    """Plot optimize result"""
    ids = []
    equities = []
    for result in self.results:
        ids.append(result["index"])
        equities.append(result["result"]["equity"])

    df = pd.DataFrame({"id": ids, "equity": equities})

    fig = px.scatter(df, x="id", y="equity")
    fig.show()

Plotter ¤

Bases: ABC

Base class help to plot strategy

load abstractmethod ¤

load()

Load plot config from Strategy.plot() and setup candlestick/equity

Source code in lettrade/plot/plot.py
 9
10
11
@abstractmethod
def load(self):
    """Load plot config from `Strategy.plot()` and setup candlestick/equity"""

plot abstractmethod ¤

plot(**kwargs)

Plot equity, orders, and positions then show

Source code in lettrade/plot/plot.py
13
14
15
@abstractmethod
def plot(self, **kwargs):
    """Plot `equity`, `orders`, and `positions` then show"""

stop abstractmethod ¤

stop()

stop plotter

Source code in lettrade/plot/plot.py
17
18
19
@abstractmethod
def stop(self):
    """stop plotter"""

Strategy ¤

Strategy(
    feeder: DataFeeder,
    exchange: Exchange,
    account: Account,
    commander: Commander,
    is_optimize: bool = False,
    **kwargs
)

Base class to implement a strategy

Parameters:

  • feeder (DataFeeder) –

    DataFeeder for strategy

  • exchange (Exchange) –

    Trading exchange

  • account (Account) –

    Account manager

  • commander (Commander) –

    Event/Command manager

  • is_optimize (bool, default: False ) –

    flag validate optimize condiction. Defaults to False.

Raises:

Source code in lettrade/strategy/strategy.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(
    self,
    feeder: DataFeeder,
    exchange: Exchange,
    account: Account,
    commander: Commander,
    is_optimize: bool = False,
    **kwargs,
):
    """_summary_

    Args:
        feeder (DataFeeder): DataFeeder for strategy
        exchange (Exchange): Trading exchange
        account (Account): Account manager
        commander (Commander): Event/Command manager
        is_optimize (bool, optional): flag validate optimize condiction. Defaults to False.

    Raises:
        RuntimeError: Validate valid is_optimize flag
    """
    self.__feeder: DataFeeder = feeder
    self.__exchange: Exchange = exchange
    self.__account: Account = account
    self.__commander: Commander = commander

    self.__datas: list[DataFeed] = self.__feeder.datas
    self.__data: DataFeed = self.__feeder.data

    if is_optimize and self.is_live:
        raise RuntimeError("Optimize a live datafeeder")
    self.__is_optimize: bool = is_optimize

    # Set parameters
    if kwargs:
        logger.info("Update strategy parameters %s", kwargs)
        for k, v in kwargs.items():
            setattr(self, k, v)

account property ¤

account: Account

Getter of Account

Returns:

  • Account ( Account ) –

    description

commander property ¤

commander: Commander

Getter of Commander

Returns:

data property ¤

data: DataFeed

Getter of main DataFeed

Returns:

datas property ¤

datas: list[DataFeed]

Getter of all DataFeed

Returns:

exchange property ¤

exchange: Exchange

Getter of Exchange

Returns:

feeder property ¤

feeder: DataFeeder

Getter of DataFeeder

Returns:

history_orders property ¤

history_orders: dict[str, Order | BackTestOrder]

Getter of history Order dict

Returns:

history_positions property ¤

history_positions: dict[str, Position | BackTestPosition]

Getter of history Position dict

Returns:

is_backtest property ¤

is_backtest: bool

Flag to check strategy is running in backtest DataFeeder

Returns:

  • bool ( bool ) –

    description

is_live property ¤

is_live: bool

Flag to check strategy is running in live DataFeeder

Returns:

  • bool ( bool ) –

    description

is_optimize property ¤

is_optimize: bool

Flag to check strategy is running in optimize session

Returns:

  • bool ( bool ) –

    description

now property ¤

now: datetime

Getter of current datetime

Returns:

  • datetime ( datetime ) –

    current datetime of bar

orders property ¤

Getter of Order dict

Returns:

positions property ¤

Getter of Position dict

Returns:

indicators ¤

indicators(df: DataFeed) -> None

All indicator and signal should implement here to cacheable. Because of lettrade will cache/pre-load all DataFeed.

To add indicators for a special DataFeed, define a new function name indicators_<DataFeed name>.

Usage
df_eurusd = DataFeed(name="eurusd")
df_gbpusd = DataFeed(name="gbpusd")

# This function will load indicators for df_eurusd
def indicators(self, df: DataFeed):
    df['ema'] = df.i.ema(window=21)

# This function will load indicators for df_gbpusd
def indicators_gbpusd(self, df: DataFeed):
    df['ema'] = df.i.ema(window=25)

Parameters:

  • df (DataFeed) –

    DataFeed need to load indicators value

Source code in lettrade/strategy/strategy.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def indicators(self, df: DataFeed) -> None:
    """All indicator and signal should implement here to cacheable.
    Because of `lettrade` will cache/pre-load all `DataFeed`.

    To add indicators for a special DataFeed, define a new function name
    `indicators_<DataFeed name>`.

    Usage:
        ```python
        df_eurusd = DataFeed(name="eurusd")
        df_gbpusd = DataFeed(name="gbpusd")

        # This function will load indicators for df_eurusd
        def indicators(self, df: DataFeed):
            df['ema'] = df.i.ema(window=21)

        # This function will load indicators for df_gbpusd
        def indicators_gbpusd(self, df: DataFeed):
            df['ema'] = df.i.ema(window=25)
        ```

    Args:
        df (DataFeed): DataFeed need to load indicators value
    """

init ¤

init() -> None

Init strategy variables

Source code in lettrade/strategy/strategy.py
67
68
def init(self) -> None:
    """Init strategy variables"""

next ¤

next(df: DataFeed, *others: list[DataFeed]) -> None

Next bar event

Source code in lettrade/strategy/strategy.py
132
133
def next(self, df: DataFeed, *others: list[DataFeed]) -> None:
    """Next bar event"""

on_executions ¤

on_executions(executions: list[Execution])

Listen for Execution event since next() begin

Parameters:

Source code in lettrade/strategy/strategy.py
467
468
469
470
471
472
473
474
475
def on_executions(self, executions: list[Execution]):
    """Listen for `Execution` event since `next()` begin

    Args:
        executions (list[Execution]): _description_
    """
    if hasattr(self, "on_execution"):
        for execution in executions:
            self.on_execution(execution)

on_notify ¤

on_notify(*args, **kwargs) -> None

Listen for notify event since next() begin

Returns:

  • _type_ ( None ) –

    None

Source code in lettrade/strategy/strategy.py
497
498
499
500
501
502
def on_notify(self, *args, **kwargs) -> None:
    """Listen for `notify` event since `next()` begin

    Returns:
        _type_: `None`
    """

on_orders ¤

on_orders(orders: list[Order])

Listen for Order event

Parameters:

Source code in lettrade/strategy/strategy.py
477
478
479
480
481
482
483
484
485
def on_orders(self, orders: list[Order]):
    """Listen for `Order` event

    Args:
        orders (list[Order]): _description_
    """
    if hasattr(self, "on_order"):
        for order in orders:
            self.on_order(order)

on_positions ¤

on_positions(positions: list[Position])

Listen for Position event since next() begin

Parameters:

Source code in lettrade/strategy/strategy.py
487
488
489
490
491
492
493
494
495
def on_positions(self, positions: list[Position]):
    """Listen for `Position` event since `next()` begin

    Args:
        positions (list[Position]): _description_
    """
    if hasattr(self, "on_position"):
        for position in positions:
            self.on_position(position)

on_transactions ¤

on_transactions(trans: list[Execution | Order | Position])

Listen for transaction events since next() begin

Parameters:

Source code in lettrade/strategy/strategy.py
457
458
459
460
461
462
463
464
465
def on_transactions(self, trans: list[Execution | Order | Position]):
    """Listen for transaction events since `next()` begin

    Args:
        trans (list[Execution  |  Order  |  Position]): _description_
    """
    if hasattr(self, "on_transaction"):
        for tran in trans:
            self.on_transaction(tran)

order_buy ¤

order_buy(
    size: float | None = None,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    **kwargs
) -> OrderResult

Place a new long order.

Parameters:

  • size (float | None, default: None ) –

    description. Defaults to None.

  • limit (float | None, default: None ) –

    description. Defaults to None.

  • stop (float | None, default: None ) –

    description. Defaults to None.

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • tag (str | None, default: None ) –

    description. Defaults to None.

  • **kwargs (dict | None, default: {} ) –

    Extra-parameters send to Exchange.new_order

Returns:

  • OrderResult ( OrderResult ) –

    order result information

Source code in lettrade/strategy/strategy.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
@final
def order_buy(
    self,
    size: float | None = None,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    **kwargs,
) -> OrderResult:
    """Place a new long order.

    Args:
        size (float | None, optional): _description_. Defaults to None.
        limit (float | None, optional): _description_. Defaults to None.
        stop (float | None, optional): _description_. Defaults to None.
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        tag (str | None, optional): _description_. Defaults to None.
        **kwargs (dict | None, optional): Extra-parameters send to `Exchange.new_order`

    Returns:
        OrderResult: order result information
    """
    return self.order_place(
        side=TradeSide.Buy,
        size=size,
        limit=limit,
        stop=stop,
        sl=sl,
        tp=tp,
        tag=tag,
        **kwargs,
    )

order_place ¤

order_place(
    side: TradeSide,
    size: float | None = None,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    **kwargs
) -> OrderResult

summary

Parameters:

  • side (TradeSide) –

    description

  • size (float | None, default: None ) –

    description. Defaults to None.

  • limit (float | None, default: None ) –

    description. Defaults to None.

  • stop (float | None, default: None ) –

    description. Defaults to None.

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • tag (str | None, default: None ) –

    description. Defaults to None.

Returns:

Source code in lettrade/strategy/strategy.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
@final
def order_place(
    self,
    side: TradeSide,
    size: float | None = None,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    **kwargs,
) -> OrderResult:
    """_summary_

    Args:
        side (TradeSide): _description_
        size (float | None, optional): _description_. Defaults to None.
        limit (float | None, optional): _description_. Defaults to None.
        stop (float | None, optional): _description_. Defaults to None.
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        tag (str | None, optional): _description_. Defaults to None.

    Returns:
        OrderResult: _description_
    """
    params = dict(
        size=size,
        limit=limit,
        stop=stop,
        sl=sl,
        tp=tp,
        tag=tag,
        **kwargs,
    )
    params["size"] = side * abs(self.__account.risk(side=side, **params))
    return self.__exchange.new_order(**params)

order_sell ¤

order_sell(
    size: float | None = None,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    **kwargs
) -> OrderResult

Place a new short order.

Parameters:

  • size (float | None, default: None ) –

    description. Defaults to None.

  • limit (float | None, default: None ) –

    description. Defaults to None.

  • stop (float | None, default: None ) –

    description. Defaults to None.

  • sl (float | None, default: None ) –

    description. Defaults to None.

  • tp (float | None, default: None ) –

    description. Defaults to None.

  • tag (str | None, default: None ) –

    description. Defaults to None.

  • **kwargs (dict | None, default: {} ) –

    Extra-parameters send to Exchange.new_order

Returns:

  • OrderResult ( OrderResult ) –

    order result information

Source code in lettrade/strategy/strategy.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
@final
def order_sell(
    self,
    size: float | None = None,
    limit: float | None = None,
    stop: float | None = None,
    sl: float | None = None,
    tp: float | None = None,
    tag: str | None = None,
    **kwargs,
) -> OrderResult:
    """Place a new short order.

    Args:
        size (float | None, optional): _description_. Defaults to None.
        limit (float | None, optional): _description_. Defaults to None.
        stop (float | None, optional): _description_. Defaults to None.
        sl (float | None, optional): _description_. Defaults to None.
        tp (float | None, optional): _description_. Defaults to None.
        tag (str | None, optional): _description_. Defaults to None.
        **kwargs (dict | None, optional): Extra-parameters send to `Exchange.new_order`

    Returns:
        OrderResult: order result information
    """
    return self.order_place(
        side=TradeSide.Sell,
        size=size,
        limit=limit,
        stop=stop,
        sl=sl,
        tp=tp,
        tag=tag,
        **kwargs,
    )

plot ¤

plot(df: DataFeed, *others: list[DataFeed]) -> dict

Custom config of plot

Example
{
    # Global items is main datafeed item
    "items": [],

    # DataFeed plot config
    "datafeed_name_eurusd": {
        "items": [
            {
                "type": "scatter",
                "x": dataframe.index,
                "y": dataframe["close"],
                "name": "EMA",
                "mode": "lines",
                "line": { "color": '#00bad6', "width": 1 },
            }
        ]
    },

    # Layout config
    {
        "hovermode": "x unified"
    }
}

Parameters:

Returns:

  • dict ( dict ) –

    config

Source code in lettrade/strategy/strategy.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def plot(self, df: DataFeed, *others: list[DataFeed]) -> dict:
    """Custom config of plot

    Example:
        ```json
        {
            # Global items is main datafeed item
            "items": [],

            # DataFeed plot config
            "datafeed_name_eurusd": {
                "items": [
                    {
                        "type": "scatter",
                        "x": dataframe.index,
                        "y": dataframe["close"],
                        "name": "EMA",
                        "mode": "lines",
                        "line": { "color": '#00bad6', "width": 1 },
                    }
                ]
            },

            # Layout config
            {
                "hovermode": "x unified"
            }
        }
        ```

    Args:
        df (DataFeed): plot DataFeed

    Returns:
        dict: config
    """
    return dict()

send ¤

send(msg: str, **kwargs) -> Any

Send message to commander

Parameters:

  • msg (str) –

    message string

Returns:

  • Any ( Any ) –

    description

Source code in lettrade/strategy/strategy.py
505
506
507
508
509
510
511
512
513
514
515
@final
def send(self, msg: str, **kwargs) -> Any:
    """Send message to commander

    Args:
        msg (str): message string

    Returns:
        Any: _description_
    """
    return self.commander.send_message(msg=msg, **kwargs)

start ¤

start(df: DataFeed, *others: list[DataFeed]) -> None

call after init() and before first next() is called

Parameters:

Returns:

  • _type_ ( None ) –

    None

Source code in lettrade/strategy/strategy.py
116
117
118
119
120
121
122
123
124
def start(self, df: DataFeed, *others: list[DataFeed]) -> None:
    """call after `init()` and before first `next()` is called

    Args:
        df (DataFeed): _description_

    Returns:
        _type_: `None`
    """

stop ¤

stop(df: DataFeed, *others: list[DataFeed]) -> None

Call when strategy run completed

Parameters:

  • df (DataFeed) –

    main data of strategy

Source code in lettrade/strategy/strategy.py
139
140
141
142
143
144
def stop(self, df: DataFeed, *others: list[DataFeed]) -> None:
    """Call when strategy run completed

    Args:
        df (DataFeed): main data of strategy
    """

TelegramAPI ¤

TelegramAPI(token: str, chat_id: int, *args, **kwargs)

Singleton object communicate across multipprocessing

Source code in lettrade/commander/telegram.py
 98
 99
100
101
102
def __init__(self, token: str, chat_id: int, *args, **kwargs) -> None:
    self._token: str = token
    self._chat_id: int = int(chat_id)
    self._bots_queue = dict()
    self._bot_selected = None

cleanup ¤

cleanup() -> None

Stops all running telegram threads.

Source code in lettrade/commander/telegram.py
147
148
149
150
151
def cleanup(self) -> None:
    """Stops all running telegram threads."""
    # This can take up to `timeout` from the call to `start_polling`.
    asyncio.run_coroutine_threadsafe(self._cleanup_telegram(), self._loop)
    self._thread.join()

send_message ¤

send_message(msg: str, pname: str, **kwargs) -> None

Send message to Telegram Bot

Parameters:

  • msg (str) –

    Message

Returns:

  • _type_ ( None ) –

    None

Source code in lettrade/commander/telegram.py
129
130
131
132
133
134
135
136
137
138
139
def send_message(self, msg: str, pname: str, **kwargs) -> None:
    """Send message to Telegram Bot

    Args:
        msg (str): Message

    Returns:
        _type_: `None`
    """
    msg = f"*[Process: {pname}]*\n\n{escape_markdown(msg)}"
    asyncio.run_coroutine_threadsafe(self._send_msg(msg, **kwargs), self._loop)

start ¤

start(pname: str, action_queue: Queue)

Start

Source code in lettrade/commander/telegram.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def start(self, pname: str, action_queue: Queue):
    """Start"""
    if pname in self._bots_queue:
        logger.warning("Process name %s override existed action queue", pname)
    self._bots_queue[pname] = action_queue

    logger.info("New join process: %s", pname)

    # TODO: Lock for safe multipleprocessing
    if hasattr(self, "_keyboard"):
        return

    self._init_keyboard()
    self._start_thread()

TelegramCommander ¤

TelegramCommander(
    token: str,
    chat_id: int,
    api: TelegramAPI | None = None,
    *args,
    **kwargs
)

Bases: Commander

Send notify and receive command from Telegram Bot

Parameters:

  • token (str) –

    Telegram Bot token

  • chat_id (int) –

    Telegram chat_id

Source code in lettrade/commander/telegram.py
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
def __init__(
    self,
    token: str,
    chat_id: int,
    api: TelegramAPI | None = None,
    *args,
    **kwargs,
) -> None:
    """_summary_

    Args:
        token (str): Telegram Bot token
        chat_id (int): Telegram chat_id
    """
    super().__init__(*args, **kwargs)
    self._api = api or TelegramAPI(token=token, chat_id=chat_id)
    self._is_running = True

init ¤

init(
    bot: LetTradeBot,
    brain: Brain,
    exchange: Exchange,
    strategy: Strategy,
)

Init commander dependencies

Parameters:

Source code in lettrade/commander/commander.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def init(
    self,
    bot: "LetTradeBot",
    brain: "Brain",
    exchange: "Exchange",
    strategy: "Strategy",
):
    """Init commander dependencies

    Args:
        bot (LetTradeBot): LetTradeBot object
        brain (Brain): Brain of bot
        exchange (Exchange): Manage bot trading
        strategy (Strategy): Strategy of bot
    """
    self.bot = bot
    self.brain = brain
    self.exchange = exchange
    self.strategy = strategy

    self._name = self.bot._name

start ¤

start()

Start

Source code in lettrade/commander/telegram.py
556
557
558
559
560
def start(self):
    """Start"""
    logger.info("TelegramCommander start %s", self._name)
    q = self._t_action()
    self._api.start(pname=self._name, action_queue=q)

stop ¤

stop()

Stop

Source code in lettrade/commander/telegram.py
562
563
564
565
566
def stop(self):
    """Stop"""
    logger.info("TelegramCommander stop %s", self._name)
    self._api.cleanup()
    self._is_running = False

YFBackTestDataFeed ¤

YFBackTestDataFeed(
    name: str,
    ticker: str,
    since: str,
    to: str | None = None,
    period: str | None = None,
    interval: str = "1d",
    **kwargs
)

Bases: BackTestDataFeed

YahooFinance DataFeed

Parameters:

  • name (str) –

    description

  • ticker (str) –

    description

  • since (str) –

    description

  • to (str | None, default: None ) –

    description. Defaults to None.

  • period (str | None, default: None ) –

    description. Defaults to None.

  • interval (str, default: '1d' ) –

    description. Defaults to "1d".

Source code in lettrade/exchange/backtest/data.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
def __init__(
    self,
    name: str,
    ticker: str,
    since: str,
    to: str | None = None,
    period: str | None = None,
    interval: str = "1d",
    **kwargs,
) -> None:
    """_summary_

    Args:
        name (str): _description_
        ticker (str): _description_
        since (str): _description_
        to (str | None, optional): _description_. Defaults to None.
        period (str | None, optional): _description_. Defaults to None.
        interval (str, optional): _description_. Defaults to "1d".
    """
    params = dict(
        start=since,
        end=to,
        period=period,
        interval=interval,
    )

    # Download
    import yfinance as yf

    df = yf.download(ticker, **params)

    # Parse to lettrade datafeed
    from .extra.yfinance import yf_parse

    df = yf_parse(df)

    # Metadata
    meta = dict(yf=dict(ticker=ticker, **params))

    super().__init__(
        name=name,
        meta=meta,
        data=df,
        **kwargs,
    )

i property ¤

Alias to lettrade.indicator and using in DataFeed by call: DataFeed.i.indicator_name()

is_main property ¤

is_main: bool

Property to check DataFeed is main DataFeed or not

l instance-attribute ¤

LetTrade DataFeed wrapper using to manage index pointer of DataFeed

meta property ¤

meta: dict

Property to get metadata of DataFeed

name property ¤

name: str

Property to get name of DataFeed

now property ¤

now: Timestamp

Property to get current index value of DataFeed

timeframe property ¤

timeframe: TimeFrame

Property to get timeframe of DataFeed

bar ¤

bar(i: int = 0) -> Timestamp

Get current pd.Timestamp value of DataFeed

Parameters:

  • i (int, default: 0 ) –

    Index. Defaults to 0.

Returns:

  • Timestamp

    pd.Timestamp: description

Source code in lettrade/data/data.py
108
109
110
111
112
113
114
115
116
117
def bar(self, i: int = 0) -> pd.Timestamp:
    """Get current pd.Timestamp value of DataFeed

    Args:
        i (int, optional): Index. Defaults to 0.

    Returns:
        pd.Timestamp: _description_
    """
    return self.l.index[i]

copy ¤

copy(deep: bool = False, **kwargs) -> DataFeed

summary

Parameters:

  • deep (bool, default: False ) –

    description. Defaults to False.

Returns:

Source code in lettrade/data/data.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def copy(self, deep: bool = False, **kwargs) -> "DataFeed":
    """_summary_

    Args:
        deep (bool, optional): _description_. Defaults to False.

    Returns:
        DataFeed: _description_
    """
    df = super().copy(deep=deep)
    df = self.__class__(
        data=df,
        name=self.name,
        timeframe=self.timeframe,
        meta=self.meta.copy(),
        **kwargs,
    )
    return df

drop ¤

drop(
    *args,
    since: int | str | Timestamp | None = None,
    to: int | str | Timestamp | None = None,
    **kwargs
) -> None

summary

Parameters:

  • since (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

  • to (int | str | Timestamp | None, default: None ) –

    description. Defaults to None.

Raises:

Source code in lettrade/data/data.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def drop(
    self,
    *args,
    since: int | str | pd.Timestamp | None = None,
    to: int | str | pd.Timestamp | None = None,
    **kwargs,
) -> None:
    """_summary_

    Args:
        since (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.
        to (int | str | pd.Timestamp | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_
    """
    if since is None and to is None:
        super().drop(*args, **kwargs)
        return

    condiction = None

    # Since
    if since is not None:
        if isinstance(since, int):
            loc = self.l.index[since]
        elif isinstance(since, str):
            loc = pd.to_datetime(since, utc=True)
        elif isinstance(since, pd.Timestamp):
            loc = since
        else:
            raise RuntimeError(f"DataFeed.drop since {since} is invalid")
        condiction = self.index < loc

    # To
    if to is not None:
        if isinstance(to, int):
            loc = self.l.index[to]
        elif isinstance(to, str):
            loc = pd.to_datetime(to, utc=True)
        elif isinstance(to, pd.Timestamp):
            loc = to
        else:
            raise RuntimeError(f"DataFeed.drop to {to} is invalid")

        if condiction is None:
            condiction = self.index > loc
        else:
            condiction = condiction | (self.index > loc)

    index = self[condiction].index
    super().drop(index=index, inplace=True)
    self.l.reset()

    if __debug__:
        logger.debug("BackTestDataFeed %s dropped %s rows", self.name, len(index))

push ¤

push(
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs
)

Push new rows to DataFeed

Parameters:

  • rows (list[list[int | float]]) –

    list of rows [["timestamp", "open price", "high price"...]]

  • unit (str | None, default: None ) –

    pandas.Timestamp parsing unit. Defaults to None.

  • utc (bool, default: True ) –

    description. Defaults to True.

Source code in lettrade/data/data.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def push(
    self,
    rows: list[list[int | float]],
    unit: str | None = None,
    utc: bool = True,
    **kwargs,
):
    """Push new rows to DataFeed

    Args:
        rows (list[list[int | float]]): list of rows `[["timestamp", "open price", "high price"...]]`
        unit (str | None, optional): pandas.Timestamp parsing unit. Defaults to None.
        utc (bool, optional): _description_. Defaults to True.
    """
    for row in rows:
        dt = pd.to_datetime(row[0], unit=unit, utc=utc, **kwargs)
        self.at[
            dt,
            (
                "open",
                "high",
                "low",
                "close",
                "volume",
            ),
        ] = (
            row[1],  # open
            row[2],  # high
            row[3],  # low
            row[4],  # close
            row[5],  # volume
        )

    if __debug__:
        logger.debug("[%s] Update bar: \n%s", self.name, self.tail(len(rows)))

above ¤

above(
    series1: Series | str,
    series2: Series | str,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
) -> Series

Check a Series is above another Series

Parameters:

  • series1 (Series) –

    first Series

  • series2 (Series) –

    second Series

Returns:

  • Series

    pd.Series: True series1 is above series2 else False

Source code in lettrade/indicator/series.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def above(
    series1: pd.Series | str,
    series2: pd.Series | str,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    # **kwargs,
) -> pd.Series:
    """Check a Series is above another Series

    Args:
        series1 (pd.Series): first Series
        series2 (pd.Series): second Series

    Returns:
        pd.Series: True series1 is above series2 else False
    """
    if __debug__:
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    if isinstance(series1, str):
        series1 = dataframe[series1]
    if isinstance(series2, str):
        series2 = dataframe[series2]

    i = s_above(series1=series1, series2=series2)

    if inplace:
        name = name or f"{prefix}above"
        dataframe[name] = i

        if plot:
            _plot_mark(dataframe=dataframe, name=name, plot_kwargs=plot_kwargs)

    return i

authorized_only ¤

authorized_only(
    command_handler: Callable[
        ..., Coroutine[Any, Any, None]
    ]
)

Decorator to check if the message comes from the correct chat_id

Parameters:

Source code in lettrade/commander/telegram.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def authorized_only(command_handler: Callable[..., Coroutine[Any, Any, None]]):
    """Decorator to check if the message comes from the correct chat_id

    Args:
        command_handler (Callable[..., Coroutine[Any, Any, None]]): Telegram CommandHandler
    """

    @wraps(command_handler)
    async def wrapper(self: "TelegramCommander", *args, **kwargs):
        """Decorator logic"""
        update = kwargs.get("update") or args[0]

        # Reject unauthorized messages
        if update.callback_query:
            cchat_id = int(update.callback_query.message.chat.id)
        else:
            cchat_id = int(update.message.chat_id)

        if cchat_id != self._chat_id:
            logger.info(f"Rejected unauthorized message from: {update.message.chat_id}")
            return wrapper

        logger.debug(
            "Executing handler: %s for chat_id: %s",
            command_handler.__name__,
            self._chat_id,
        )
        try:
            return await command_handler(self, *args, **kwargs)
        except Exception as e:
            await self._send_msg(str(e))
            # except BaseException:
            logger.exception("Exception occurred within Telegram module", exc_info=e)

    return wrapper

below ¤

below(
    series1: Series | str,
    series2: Series | str,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
) -> Series

Check a Series is below another Series

Parameters:

  • series1 (Series) –

    first Series

  • series2 (Series) –

    second Series

Returns:

  • Series

    pd.Series: -100 series1 is below series2 else 0

Source code in lettrade/indicator/series.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def below(
    series1: pd.Series | str,
    series2: pd.Series | str,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    # **kwargs,
) -> pd.Series:
    """Check a Series is below another Series

    Args:
        series1 (pd.Series): first Series
        series2 (pd.Series): second Series

    Returns:
        pd.Series: -100 series1 is below series2 else 0
    """
    if __debug__:
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    if isinstance(series1, str):
        series1 = dataframe[series1]
    if isinstance(series2, str):
        series2 = dataframe[series2]

    i = s_below(series1, series2)

    if inplace:
        name = name or f"{prefix}below"
        dataframe[name] = i

        if plot:
            _plot_mark(dataframe=dataframe, name=name, plot_kwargs=plot_kwargs)

    return i

cdl_3blackcrows ¤

cdl_3blackcrows(
    dataframe: DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal[
        "candlestick", "mark"
    ] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: 'cdl_' ) –

    description. Defaults to "cdl_".

  • inplace (bool, default: False ) –

    description. Defaults to False.

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/candlestick/talib.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def cdl_3blackcrows(
    dataframe: pd.DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["candlestick", "mark"] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "cdl_".
        inplace (bool, optional): _description_. Defaults to False.

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    return cdl_pattern(
        dataframe=dataframe,
        pattern="3blackcrows",
        name=name,
        prefix=prefix,
        inplace=inplace,
        plot=plot,
        plot_type=plot_type,
        plot_kwargs=plot_kwargs,
        **kwargs,
    )

cdl_3inside ¤

cdl_3inside(
    dataframe: DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal[
        "candlestick", "mark"
    ] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: 'cdl_' ) –

    description. Defaults to "cdl_".

  • inplace (bool, default: False ) –

    description. Defaults to False.

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/candlestick/talib.py
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def cdl_3inside(
    dataframe: pd.DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["candlestick", "mark"] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "cdl_".
        inplace (bool, optional): _description_. Defaults to False.

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    return cdl_pattern(
        dataframe=dataframe,
        pattern="3inside",
        name=name,
        prefix=prefix,
        inplace=inplace,
        plot=plot,
        plot_type=plot_type,
        plot_kwargs=plot_kwargs,
        **kwargs,
    )

cdl_3whitesoldiers ¤

cdl_3whitesoldiers(
    dataframe: DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal[
        "candlestick", "mark"
    ] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: 'cdl_' ) –

    description. Defaults to "cdl_".

  • inplace (bool, default: False ) –

    description. Defaults to False.

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/candlestick/talib.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def cdl_3whitesoldiers(
    dataframe: pd.DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["candlestick", "mark"] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "cdl_".
        inplace (bool, optional): _description_. Defaults to False.

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    return cdl_pattern(
        dataframe=dataframe,
        pattern="3whitesoldiers",
        name=name,
        prefix=prefix,
        inplace=inplace,
        plot=plot,
        plot_type=plot_type,
        plot_kwargs=plot_kwargs,
        **kwargs,
    )

cdl_direction ¤

cdl_direction(
    dataframe: DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
) -> Series | DataFrame

Direction of candle

Parameters:

  • dataframe (DataFrame) –

    description

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: 'cdl_' ) –

    description. Defaults to "cdl_".

  • inplace (bool, default: False ) –

    description. Defaults to False.

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: pd.Series return [100, 0 , -100] - 100 for bull bar - -100 for bear bar - 0 for None

Source code in lettrade/indicator/candlestick/candlestick.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def cdl_direction(
    dataframe: pd.DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
) -> pd.Series | pd.DataFrame:
    """Direction of candle

    Args:
        dataframe (pd.DataFrame): _description_
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "cdl_".
        inplace (bool, optional): _description_. Defaults to False.

    Returns:
        pd.Series | pd.DataFrame: `pd.Series` return [100, 0 , -100]
            - `100` for bull bar
            - `-100` for bear bar
            - `0` for None
    """
    if __debug__:
        if not isinstance(dataframe, pd.DataFrame):
            raise RuntimeError(
                f"dataframe type '{type(dataframe)}' "
                "is not instance of pandas.DataFrame"
            )
    i = dataframe.apply(
        lambda r: 100 if r.open < r.close else -100 if r.open > r.close else 0,
        axis=1,
    ).astype(int)

    if inplace:
        dataframe[name or f"{prefix}direction"] = i
        return dataframe

    return i

cdl_doji ¤

cdl_doji(
    dataframe: DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal[
        "candlestick", "mark"
    ] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: 'cdl_' ) –

    description. Defaults to "cdl_".

  • inplace (bool, default: False ) –

    description. Defaults to False.

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/candlestick/talib.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def cdl_doji(
    dataframe: pd.DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["candlestick", "mark"] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "cdl_".
        inplace (bool, optional): _description_. Defaults to False.

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    return cdl_pattern(
        dataframe=dataframe,
        pattern="doji",
        name=name,
        prefix=prefix,
        inplace=inplace,
        plot=plot,
        plot_type=plot_type,
        plot_kwargs=plot_kwargs,
        **kwargs,
    )

cdl_engulfing ¤

cdl_engulfing(
    dataframe: DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal[
        "candlestick", "mark"
    ] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: 'cdl_' ) –

    description. Defaults to "cdl_".

  • inplace (bool, default: False ) –

    description. Defaults to False.

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/candlestick/talib.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
def cdl_engulfing(
    dataframe: pd.DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["candlestick", "mark"] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "cdl_".
        inplace (bool, optional): _description_. Defaults to False.

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    return cdl_pattern(
        dataframe=dataframe,
        pattern="engulfing",
        name=name,
        prefix=prefix,
        inplace=inplace,
        plot=plot,
        plot_type=plot_type,
        plot_kwargs=plot_kwargs,
        **kwargs,
    )

cdl_eveningstar ¤

cdl_eveningstar(
    dataframe: DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal[
        "candlestick", "mark"
    ] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: 'cdl_' ) –

    description. Defaults to "cdl_".

  • inplace (bool, default: False ) –

    description. Defaults to False.

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/candlestick/talib.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def cdl_eveningstar(
    dataframe: pd.DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["candlestick", "mark"] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "cdl_".
        inplace (bool, optional): _description_. Defaults to False.

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    return cdl_pattern(
        dataframe=dataframe,
        pattern="eveningstar",
        name=name,
        prefix=prefix,
        inplace=inplace,
        plot=plot,
        plot_type=plot_type,
        plot_kwargs=plot_kwargs,
        **kwargs,
    )

cdl_morningstar ¤

cdl_morningstar(
    dataframe: DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal[
        "candlestick", "mark"
    ] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: 'cdl_' ) –

    description. Defaults to "cdl_".

  • inplace (bool, default: False ) –

    description. Defaults to False.

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/candlestick/talib.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def cdl_morningstar(
    dataframe: pd.DataFrame,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["candlestick", "mark"] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "cdl_".
        inplace (bool, optional): _description_. Defaults to False.

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    return cdl_pattern(
        dataframe=dataframe,
        pattern="morningstar",
        name=name,
        prefix=prefix,
        inplace=inplace,
        plot=plot,
        plot_type=plot_type,
        plot_kwargs=plot_kwargs,
        **kwargs,
    )

cdl_pattern ¤

cdl_pattern(
    dataframe: DataFrame,
    pattern: str,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal[
        "candlestick", "mark"
    ] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

summary

Parameters:

  • dataframe (DataFrame) –

    pandas.DataFrame with ohlcv

  • pattern (str) –

    TA-Lib candle pattern name. Ex: 3whitesoldiers, 3blackcrows

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: 'cdl_' ) –

    description. Defaults to "cdl_".

  • inplace (bool, default: False ) –

    description. Defaults to False.

Raises:

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/candlestick/talib.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def cdl_pattern(
    dataframe: pd.DataFrame,
    pattern: str,
    name: str | None = None,
    prefix: str = "cdl_",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["candlestick", "mark"] = "candlestick",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """_summary_

    Args:
        dataframe (pd.DataFrame): pandas.DataFrame with ohlcv
        pattern (str): TA-Lib candle pattern name. Ex: `3whitesoldiers`, `3blackcrows`
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "cdl_".
        inplace (bool, optional): _description_. Defaults to False.

    Raises:
        RuntimeError: _description_

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    if __debug__:
        if not isinstance(dataframe, pd.DataFrame):
            raise RuntimeError(
                f"dataframe type '{type(dataframe)}' "
                "is not instance of pandas.DataFrame"
            )
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    i = getattr(ta, f"CDL{pattern.upper()}")(dataframe, **kwargs)

    if inplace:
        name = name or f"{prefix}{pattern.lower()}"
        dataframe[name] = i

        # Plot
        if plot:
            _plot_pattern(
                dataframe=dataframe,
                indicator=i,
                name=name,
                plot_type=plot_type,
                plot_kwargs=plot_kwargs,
            )

        return dataframe

    return i

crossover ¤

crossover(
    series1: Series | str,
    series2: Series | str,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
) -> Series

Check if a Series cross over another Series

Parameters:

  • series1 (Series) –

    first Series

  • series2 (Series) –

    second Series

Returns:

  • Series

    pd.Series: 100 if series1 cross over series2 else 0

Source code in lettrade/indicator/series.py
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
def crossover(
    series1: pd.Series | str,
    series2: pd.Series | str,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    # **kwargs,
) -> pd.Series:
    """Check if a Series cross over another Series

    Args:
        series1 (pd.Series): first Series
        series2 (pd.Series): second Series

    Returns:
        pd.Series: 100 if series1 cross over series2 else 0
    """
    if __debug__:
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    if isinstance(series1, str):
        series1 = dataframe[series1]
    if isinstance(series2, str):
        series2 = dataframe[series2]

    below1 = s_below(series1, series2).shift(1)
    above0 = s_above(series1, series2)
    i = (-below1 + above0).apply(lambda v: 100 if v >= 200 else 0)

    if inplace:
        name = name or f"{prefix}crossover"
        dataframe[name] = i

        if plot:
            _plot_mark(dataframe=dataframe, name=name, plot_kwargs=plot_kwargs)

    return i

crossunder ¤

crossunder(
    series1: Series | str,
    series2: Series | str,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
) -> Series

Check if a Series cross under another Series

Parameters:

  • series1 (Series) –

    first Series

  • series2 (Series) –

    second Series

Returns:

  • Series

    pd.Series: -100 if series1 cross under series2 else 0

Source code in lettrade/indicator/series.py
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
def crossunder(
    series1: pd.Series | str,
    series2: pd.Series | str,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    # **kwargs,
) -> pd.Series:
    """Check if a Series cross under another Series

    Args:
        series1 (pd.Series): first Series
        series2 (pd.Series): second Series

    Returns:
        pd.Series: -100 if series1 cross under series2 else 0
    """
    if __debug__:
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    if isinstance(series1, str):
        series1 = dataframe[series1]
    if isinstance(series2, str):
        series2 = dataframe[series2]

    above1 = s_above(series1, series2).shift(1)
    below0 = s_below(series1, series2)
    i = (below0 - above1).apply(lambda v: -100 if v <= -200 else 0)

    if inplace:
        name = name or f"{prefix}crossunder"
        dataframe[name] = i

        if plot:
            _plot_mark(dataframe=dataframe, name=name, plot_kwargs=plot_kwargs)

    return i

diff ¤

diff(
    series1: Series | str,
    series2: Series | str,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
) -> Series

Difference between 2 series

Parameters:

  • series1 (Series) –

    description

  • series2 (Series) –

    description

  • dataframe (DataFrame, default: None ) –

    description. Defaults to None.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: '' ) –

    description. Defaults to "".

  • inplace (bool, default: False ) –

    description. Defaults to False.

  • plot (bool | list, default: False ) –

    description. Defaults to False.

  • plot_kwargs (dict | None, default: None ) –

    description. Defaults to None.

Raises:

Returns:

  • Series

    pd.Series: Difference of 2 series (series1 - series2)

Source code in lettrade/indicator/series.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def diff(
    series1: pd.Series | str,
    series2: pd.Series | str,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    # **kwargs,
) -> pd.Series:
    """Difference between 2 series

    Args:
        series1 (pd.Series): _description_
        series2 (pd.Series): _description_
        dataframe (pd.DataFrame, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "".
        inplace (bool, optional): _description_. Defaults to False.
        plot (bool | list, optional): _description_. Defaults to False.
        plot_kwargs (dict | None, optional): _description_. Defaults to None.

    Raises:
        RuntimeError: _description_

    Returns:
        pd.Series: Difference of 2 series (series1 - series2)
    """
    if __debug__:
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    if isinstance(series1, str):
        series1 = dataframe[series1]
    if isinstance(series2, str):
        series2 = dataframe[series2]

    i = series1 - series2

    if inplace:
        name = name or f"{prefix}diff"
        dataframe[name] = i

        if plot:
            _plot_mark(dataframe=dataframe, name=name, plot_kwargs=plot_kwargs)

    return i

direction ¤

direction(
    series1: Series | str,
    series2: Series | str,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
) -> Series

Check a Series is direction another Series

Parameters:

  • series1 (Series) –

    first Series

  • series2 (Series) –

    second Series

Returns:

  • Series

    pd.Series: -100 if series1 < series2, 100 if series1 > series2, else 0

Source code in lettrade/indicator/series.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def direction(
    series1: pd.Series | str,
    series2: pd.Series | str,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    # **kwargs,
) -> pd.Series:
    """Check a Series is direction another Series

    Args:
        series1 (pd.Series): first Series
        series2 (pd.Series): second Series

    Returns:
        pd.Series: -100 if series1 < series2, 100 if series1 > series2, else 0
    """
    if __debug__:
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    if isinstance(series1, str):
        series1 = dataframe[series1]
    if isinstance(series2, str):
        series2 = dataframe[series2]

    i = s_direction(series1, series2)

    if inplace:
        name = name or f"{prefix}direction"
        dataframe[name] = i

        if plot:
            _plot_mark(dataframe=dataframe, name=name, plot_kwargs=plot_kwargs)

    return i

ema ¤

ema(
    series: Series | str = "close",
    window: int = None,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

Exponential Moving Average

Parameters:

  • series (Series | str, default: 'close' ) –

    description. Defaults to "close".

  • window (int, default: None ) –

    description. Defaults to None.

  • dataframe (DataFrame, default: None ) –

    description. Defaults to None.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • prefix (str, default: '' ) –

    description. Defaults to "".

  • inplace (bool, default: False ) –

    description. Defaults to False.

  • plot (bool | list, default: False ) –

    description. Defaults to False.

  • plot_kwargs (dict | None, default: None ) –

    description. Defaults to None.

Example
df.i.ema(window=21, name="ema", inplace=True, plot=True)

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/trend/ma.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def ema(
    series: pd.Series | str = "close",
    window: int = None,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """Exponential Moving Average

    Args:
        series (pd.Series | str, optional): _description_. Defaults to "close".
        window (int, optional): _description_. Defaults to None.
        dataframe (pd.DataFrame, optional): _description_. Defaults to None.
        name (str | None, optional): _description_. Defaults to None.
        prefix (str, optional): _description_. Defaults to "".
        inplace (bool, optional): _description_. Defaults to False.
        plot (bool | list, optional): _description_. Defaults to False.
        plot_kwargs (dict | None, optional): _description_. Defaults to None.

    Example:
        ```python
        df.i.ema(window=21, name="ema", inplace=True, plot=True)
        ```

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    return ma(
        series=series,
        window=window,
        mode="ema",
        dataframe=dataframe,
        name=name,
        prefix=prefix,
        inplace=inplace,
        plot=plot,
        plot_kwargs=plot_kwargs,
        **kwargs,
    )

indicators_inject_pandas ¤

indicators_inject_pandas(obj: object | None = None)

Inject indicators to Pandas

Source code in lettrade/indicator/__init__.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def indicators_inject_pandas(obj: object | None = None):
    """Inject indicators to Pandas"""
    if obj is None:
        from pandas.core.base import PandasObject

        obj = PandasObject

    # Flag to mark indicators injected
    if hasattr(obj, "_lt_indicators_injected"):
        return

    from .candlestick import pandas_inject as candlestick_pandas_inject
    from .dataframe import pandas_inject as dataframe_pandas_inject
    from .momentum import pandas_inject as momentum_pandas_inject
    from .series import pandas_inject as series_pandas_inject
    from .trend import pandas_inject as trend_pandas_inject
    from .volatility import pandas_inject as volatility_pandas_inject

    series_pandas_inject(obj)
    dataframe_pandas_inject(obj)

    candlestick_pandas_inject(obj)
    trend_pandas_inject(obj)
    volatility_pandas_inject(obj)
    momentum_pandas_inject(obj)

    # Flag to mark indicators injected
    obj._lt_indicators_injected = True

let_backtest ¤

let_backtest(
    datas: DataFeed | list[DataFeed] | str | list[str],
    strategy: type[Strategy],
    *,
    feeder: type[DataFeeder] = BackTestDataFeeder,
    exchange: type[Exchange] = BackTestExchange,
    account: type[Account] = BackTestAccount,
    commander: type[Commander] | None = BackTestCommander,
    stats: type[BotStatistic] | None = BotStatistic,
    optimize_stats: (
        type[OptimizeStatistic] | None
    ) = OptimizeStatistic,
    plotter: type[BotPlotter] | None = "PlotlyBotPlotter",
    optimize_plotter: (
        type[OptimizePlotter] | None
    ) = "PlotlyOptimizePlotter",
    bot: (
        type[LetTradeBackTestBot] | None
    ) = LetTradeBackTestBot,
    balance: float | None = 10000,
    commission: float | None = 0.2,
    leverage: float | None = 20,
    **kwargs
) -> LetTradeBackTest

Complete lettrade backtest depenencies

Parameters:

Raises:

Returns:

Source code in lettrade/exchange/backtest/backtest.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
def let_backtest(
    datas: DataFeed | list[DataFeed] | str | list[str],
    strategy: type[Strategy],
    *,
    feeder: type[DataFeeder] = BackTestDataFeeder,
    exchange: type[Exchange] = BackTestExchange,
    account: type[Account] = BackTestAccount,
    commander: type[Commander] | None = BackTestCommander,
    stats: type[BotStatistic] | None = BotStatistic,
    optimize_stats: type[OptimizeStatistic] | None = OptimizeStatistic,
    plotter: type[BotPlotter] | None = "PlotlyBotPlotter",
    optimize_plotter: type[OptimizePlotter] | None = "PlotlyOptimizePlotter",
    bot: type[LetTradeBackTestBot] | None = LetTradeBackTestBot,
    # Account kwargs
    balance: float | None = 10_000,
    commission: float | None = 0.2,
    leverage: float | None = 20,
    **kwargs,
) -> "LetTradeBackTest":
    """Complete `lettrade` backtest depenencies

    Args:
        datas (DataFeed | list[DataFeed] | str | list[str]): _description_
        strategy (Type[Strategy]): The Strategy implement class
        feeder (Type[DataFeeder], optional): _description_. Defaults to BackTestDataFeeder.
        exchange (Type[Exchange], optional): _description_. Defaults to BackTestExchange.
        account (Type[Account], optional): _description_. Defaults to BackTestAccount.
        commander (Type[Commander] | None, optional): _description_. Defaults to BackTestCommander.
        plotter (Type[Plotter] | None, optional): _description_. Defaults to PlotlyBotPlotter.

    Raises:
        RuntimeError: The validate parameter error

    Returns:
        LetTradeBackTest: The LetTrade backtesting object
    """
    account_kwargs: dict = kwargs.setdefault("account_kwargs", {})
    account_kwargs.update(
        balance=balance,
        commission=commission,
        leverage=leverage,
    )

    return LetTradeBackTest(
        strategy=strategy,
        datas=datas,
        feeder=feeder,
        exchange=exchange,
        commander=commander,
        account=account,
        stats=stats,
        plotter=plotter,
        bot=bot,
        # Backtest
        optimize_stats_cls=optimize_stats,
        optimize_plotter_cls=optimize_plotter,
        **kwargs,
    )

let_ccxt ¤

let_ccxt(
    datas: set[set[str]],
    strategy: type[Strategy],
    *,
    ccxt_exchange: str,
    ccxt_key: str,
    ccxt_secret: str,
    ccxt_type: Literal["spot", "margin", "future"] = "spot",
    ccxt_verbose: bool = False,
    feeder: type[CCXTDataFeeder] = CCXTDataFeeder,
    exchange: type[CCXTExchange] = CCXTExchange,
    account: type[CCXTAccount] = CCXTAccount,
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] | None = BotStatistic,
    plotter: type[Plotter] | None = None,
    bot: type[LetTradeCCXTBot] | None = LetTradeCCXTBot,
    lettrade: type[LetTradeCCXT] | None = LetTradeCCXT,
    api: type[CCXTAPI] | None = CCXTAPI,
    **kwargs
) -> LetTradeCCXT

Help to build LetTradeCCXT

Parameters:

  • datas (set[set[str]]) –

    description

  • strategy (Type[Strategy]) –

    description

  • ccxt_exchange (str) –

    description

  • ccxt_key (str) –

    description

  • ccxt_secret (str) –

    description

  • ccxt_type (Literal['spot', 'margin', 'future'], default: 'spot' ) –

    description. Defaults to "spot".

  • ccxt_verbose (bool, default: False ) –

    description. Defaults to False.

  • feeder (Type[CCXTDataFeeder], default: CCXTDataFeeder ) –

    description. Defaults to CCXTDataFeeder.

  • exchange (Type[CCXTExchange], default: CCXTExchange ) –

    description. Defaults to CCXTExchange.

  • account (Type[CCXTAccount], default: CCXTAccount ) –

    description. Defaults to CCXTAccount.

  • commander (Type[Commander] | None, default: None ) –

    description. Defaults to None.

  • stats (Type[BotStatistic] | None, default: BotStatistic ) –

    description. Defaults to BotStatistic.

  • plotter (Type[Plotter] | None, default: None ) –

    description. Defaults to None.

  • bot (Type[LetTradeCCXTBot] | None, default: LetTradeCCXTBot ) –

    description. Defaults to LetTradeCCXTBot.

  • lettrade (Type[LetTradeCCXT] | None, default: LetTradeCCXT ) –

    description. Defaults to LetTradeCCXT.

  • api (Type[CCXTAPI] | None, default: CCXTAPI ) –

    description. Defaults to CCXTAPI.

  • **kwargs (dict, default: {} ) –

    All remaining properties are passed to the constructor of LetTradeLive

Returns:

Source code in lettrade/exchange/ccxt/ccxt.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def let_ccxt(
    datas: set[set[str]],
    strategy: type[Strategy],
    *,
    ccxt_exchange: str,
    ccxt_key: str,
    ccxt_secret: str,
    ccxt_type: Literal["spot", "margin", "future"] = "spot",
    ccxt_verbose: bool = False,
    feeder: type[CCXTDataFeeder] = CCXTDataFeeder,
    exchange: type[CCXTExchange] = CCXTExchange,
    account: type[CCXTAccount] = CCXTAccount,
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] | None = BotStatistic,
    plotter: type[Plotter] | None = None,
    bot: type[LetTradeCCXTBot] | None = LetTradeCCXTBot,
    lettrade: type[LetTradeCCXT] | None = LetTradeCCXT,
    api: type[CCXTAPI] | None = CCXTAPI,
    **kwargs,
) -> LetTradeCCXT:
    """Help to build `LetTradeCCXT`

    Args:
        datas (set[set[str]]): _description_
        strategy (Type[Strategy]): _description_
        ccxt_exchange (str): _description_
        ccxt_key (str): _description_
        ccxt_secret (str): _description_
        ccxt_type (Literal["spot", "margin", "future"], optional): _description_. Defaults to "spot".
        ccxt_verbose (bool, optional): _description_. Defaults to False.
        feeder (Type[CCXTDataFeeder], optional): _description_. Defaults to CCXTDataFeeder.
        exchange (Type[CCXTExchange], optional): _description_. Defaults to CCXTExchange.
        account (Type[CCXTAccount], optional): _description_. Defaults to CCXTAccount.
        commander (Type[Commander] | None, optional): _description_. Defaults to None.
        stats (Type[BotStatistic] | None, optional): _description_. Defaults to BotStatistic.
        plotter (Type[Plotter] | None, optional): _description_. Defaults to None.
        bot (Type[LetTradeCCXTBot] | None, optional): _description_. Defaults to LetTradeCCXTBot.
        lettrade (Type[LetTradeCCXT] | None, optional): _description_. Defaults to LetTradeCCXT.
        api (Type[CCXTAPI] | None, optional): _description_. Defaults to CCXTAPI.
        **kwargs (dict): All remaining properties are passed to the constructor of `LetTradeLive`

    Returns:
        LetTradeCCXT: _description_
    """
    api_kwargs: dict = kwargs.setdefault("api_kwargs", {})
    api_kwargs.update(
        exchange=ccxt_exchange,
        key=ccxt_key,
        secret=ccxt_secret,
        type=ccxt_type,
        verbose=ccxt_verbose,
    )

    return let_live(
        strategy=strategy,
        datas=datas,
        feeder=feeder,
        exchange=exchange,
        account=account,
        commander=commander,
        plotter=plotter,
        stats=stats,
        bot=bot,
        lettrade=lettrade,
        api=api,
        **kwargs,
    )

let_live ¤

let_live(
    datas: set[set[str]],
    strategy: type[Strategy],
    *,
    commander: Commander | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    bot: type[LetTradeLiveBot] = LetTradeLiveBot,
    lettrade: type[LetTradeLive] = LetTradeLive,
    api: type[LiveAPI] = LiveAPI,
    **kwargs
) -> LetTradeLive

Help to build LetTradeLive

Parameters:

Returns:

Source code in lettrade/exchange/live/live.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def let_live(
    datas: set[set[str]],
    strategy: type[Strategy],
    *,
    commander: Commander | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    bot: type[LetTradeLiveBot] = LetTradeLiveBot,
    lettrade: type[LetTradeLive] = LetTradeLive,
    api: type[LiveAPI] = LiveAPI,
    **kwargs,
) -> "LetTradeLive":
    """Help to build `LetTradeLive`

    Args:
        datas (set[set[str]]): _description_
        strategy (type[Strategy]): _description_
        commander (Commander | None, optional): _description_. Defaults to None.
        stats (type[BotStatistic], optional): _description_. Defaults to BotStatistic.
        plotter (type[Plotter] | None, optional): _description_. Defaults to None.
        bot (type[LetTradeLiveBot], optional): _description_. Defaults to LetTradeLiveBot.
        lettrade (type[LetTradeLive], optional): _description_. Defaults to LetTradeLive.
        api (type[LiveAPI], optional): _description_. Defaults to LiveAPI.

    Returns:
        LetTradeLive: _description_
    """
    return lettrade(
        strategy=strategy,
        datas=datas,
        commander=commander,
        plotter=plotter,
        stats=stats,
        bot=bot,
        api=api,
        **kwargs,
    )

let_metatrader ¤

let_metatrader(
    datas: set[set[str]],
    strategy: type[Strategy],
    *,
    mt5_login: int,
    mt5_password: str,
    mt5_server: str,
    mt5_wine: str | None = None,
    mt5_path: str | None = None,
    feeder: type[
        MetaTraderDataFeeder
    ] = MetaTraderDataFeeder,
    exchange: type[MetaTraderExchange] = MetaTraderExchange,
    account: type[MetaTraderAccount] = MetaTraderAccount,
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    bot: type[
        LetTradeMetaTraderBot
    ] = LetTradeMetaTraderBot,
    lettrade: type[LetTradeMetaTrader] = LetTradeMetaTrader,
    api: type[MetaTraderAPI] = MetaTraderAPI,
    **kwargs
) -> LetTradeMetaTrader

Help to build LetTradeMetaTrader

Parameters:

Returns:

Source code in lettrade/exchange/metatrader/metatrader.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def let_metatrader(
    datas: set[set[str]],
    strategy: type[Strategy],
    *,
    mt5_login: int,
    mt5_password: str,
    mt5_server: str,
    mt5_wine: str | None = None,
    mt5_path: str | None = None,
    feeder: type[MetaTraderDataFeeder] = MetaTraderDataFeeder,
    exchange: type[MetaTraderExchange] = MetaTraderExchange,
    account: type[MetaTraderAccount] = MetaTraderAccount,
    commander: type[Commander] | None = None,
    stats: type[BotStatistic] = BotStatistic,
    plotter: type[Plotter] | None = None,
    bot: type[LetTradeMetaTraderBot] = LetTradeMetaTraderBot,
    lettrade: type[LetTradeMetaTrader] = LetTradeMetaTrader,
    api: type[MetaTraderAPI] = MetaTraderAPI,
    **kwargs,
) -> LetTradeMetaTrader:
    """Help to build `LetTradeMetaTrader`

    Args:
        datas (set[set[str]]): _description_
        strategy (type[Strategy]): _description_
        mt5_login (int): _description_
        mt5_password (str): _description_
        mt5_server (str): _description_
        mt5_wine (str | None, optional): WineHQ execute path. Defaults to None.
        feeder (type[MetaTraderDataFeeder], optional): _description_. Defaults to MetaTraderDataFeeder.
        exchange (type[MetaTraderExchange], optional): _description_. Defaults to MetaTraderExchange.
        account (type[MetaTraderAccount], optional): _description_. Defaults to MetaTraderAccount.
        commander (type[Commander] | None, optional): _description_. Defaults to None.
        stats (type[BotStatistic], optional): _description_. Defaults to BotStatistic.
        plotter (type[Plotter] | None, optional): _description_. Defaults to None.
        bot (type[LetTradeMetaTraderBot], optional): _description_. Defaults to LetTradeMetaTraderBot.
        lettrade (type[LetTradeMetaTrader], optional): _description_. Defaults to LetTradeMetaTrader.
        api (type[MetaTraderAPI], optional): _description_. Defaults to MetaTraderAPI.

    Returns:
        LetTradeMetaTrader: _description_
    """
    api_kwargs: dict = kwargs.setdefault("api_kwargs", {})
    api_kwargs.update(
        login=int(mt5_login),
        password=mt5_password,
        server=mt5_server,
        wine=mt5_wine,
        path=mt5_path,
    )

    return let_live(
        strategy=strategy,
        datas=datas,
        feeder=feeder,
        exchange=exchange,
        account=account,
        commander=commander,
        stats=stats,
        plotter=plotter,
        bot=bot,
        lettrade=lettrade,
        api=api,
        **kwargs,
    )

plot_bollinger_bands ¤

plot_bollinger_bands(
    dataframe: DataFrame,
    upper: str | None = "upper",
    basis: str | None = "basis",
    lower: str | None = "lower",
    width: int = 1,
    upper_color: str = "#33BDFF",
    basis_color: str = "#D105F5",
    lower_color: str = "#33BDFF",
    filter: Series | None = None,
) -> dict

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • upper (str, default: 'upper' ) –

    Column name. None mean skip plot. Defaults to "upper".

  • basis (str, default: 'basis' ) –

    Column name. None mean skip plot. Defaults to "basis".

  • lower (str, default: 'lower' ) –

    Column name. None mean skip plot. Defaults to "lower".

  • width (int, default: 1 ) –

    description. Defaults to 1.

  • upper_color (str, default: '#33BDFF' ) –

    Color code. Defaults to "#33BDFF".

  • lower_color (str, default: '#33BDFF' ) –

    Color code. Defaults to "#33BDFF".

  • basis_color (str, default: '#D105F5' ) –

    Color code. Defaults to "#D105F5".

  • filter (Series | None, default: None ) –

    description. Defaults to None.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/plot/plotly/indicator.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def plot_bollinger_bands(
    dataframe: pd.DataFrame,
    upper: str | None = "upper",
    basis: str | None = "basis",
    lower: str | None = "lower",
    width: int = 1,
    upper_color: str = "#33BDFF",
    basis_color: str = "#D105F5",
    lower_color: str = "#33BDFF",
    filter: pd.Series | None = None,
) -> dict:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        upper (str, optional): Column name. `None` mean skip plot. Defaults to "upper".
        basis (str, optional): Column name. `None` mean skip plot. Defaults to "basis".
        lower (str, optional): Column name. `None` mean skip plot. Defaults to "lower".
        width (int, optional): _description_. Defaults to 1.
        upper_color (str, optional): Color code. Defaults to "#33BDFF".
        lower_color (str, optional): Color code. Defaults to "#33BDFF".
        basis_color (str, optional): Color code. Defaults to "#D105F5".
        filter (pd.Series | None, optional): _description_. Defaults to None.

    Returns:
        dict: _description_
    """
    if filter is not None:
        df_name = dataframe.name
        dataframe = dataframe.loc[filter]
        object.__setattr__(dataframe, "name", df_name)

    items = []

    if upper is not None:
        items.append(
            dict(
                type="scatter",
                x=dataframe.index,
                y=dataframe[upper],
                name=upper,
                mode="lines",
                line=dict(color=upper_color, width=width),
            )
        )
    if basis is not None:
        items.append(
            dict(
                type="scatter",
                x=dataframe.index,
                y=dataframe[basis],
                name=basis,
                mode="lines",
                line=dict(color=basis_color, width=width),
            )
        )
    if lower is not None:
        items.append(
            dict(
                type="scatter",
                x=dataframe.index,
                y=dataframe[lower],
                name=lower,
                mode="lines",
                line=dict(color=lower_color, width=width),
            )
        )
    return {f"{dataframe.name}": dict(items=items)}

plot_candlestick ¤

plot_candlestick(
    dataframe: DataFrame,
    name: str = "Candlestick",
    width: int = 1,
    increasing_line_color="#26c6da",
    decreasing_line_color="#ab47bc",
    row: int = 1,
    col: int = 1,
    filter: Series | None = None,
    **kwargs
) -> dict

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • name (str, default: 'Candlestick' ) –

    description. Defaults to "Candlestick".

  • width (int, default: 1 ) –

    description. Defaults to 1.

  • increasing_line_color (str, default: '#26c6da' ) –

    description. Defaults to "#26c6da".

  • decreasing_line_color (str, default: '#ab47bc' ) –

    description. Defaults to "#ab47bc".

  • row (int, default: 1 ) –

    description. Defaults to 1.

  • col (int, default: 1 ) –

    description. Defaults to 1.

  • filter (Series | None, default: None ) –

    description. Defaults to None.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/plot/plotly/indicator.py
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
def plot_candlestick(
    dataframe: pd.DataFrame,
    name: str = "Candlestick",
    width: int = 1,
    increasing_line_color="#26c6da",
    decreasing_line_color="#ab47bc",
    row: int = 1,
    col: int = 1,
    filter: pd.Series | None = None,
    **kwargs,
) -> dict:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        name (str, optional): _description_. Defaults to "Candlestick".
        width (int, optional): _description_. Defaults to 1.
        increasing_line_color (str, optional): _description_. Defaults to "#26c6da".
        decreasing_line_color (str, optional): _description_. Defaults to "#ab47bc".
        row (int, optional): _description_. Defaults to 1.
        col (int, optional): _description_. Defaults to 1.
        filter (pd.Series | None, optional): _description_. Defaults to None.

    Returns:
        dict: _description_
    """
    if filter is not None:
        df_name = dataframe.name
        dataframe = dataframe.loc[filter]
        object.__setattr__(dataframe, "name", df_name)

    config = dict(
        items=[
            dict(
                type="candlestick",
                x=dataframe.index,
                open=dataframe["open"],
                high=dataframe["high"],
                low=dataframe["low"],
                close=dataframe["close"],
                name=name,
                line=dict(width=width),
                increasing_line_color=increasing_line_color,
                decreasing_line_color=decreasing_line_color,
                hoverinfo="text",
                hovertext=name,
                row=row,
                col=col,
                **kwargs,
            ),
        ]
    )

    return {f"{dataframe.name}": config}

plot_ichimoku ¤

plot_ichimoku(
    dataframe: DataFrame,
    tenkan_sen="tenkan_sen",
    kijun_sen="kijun_sen",
    senkou_span_a="senkou_span_a",
    senkou_span_b="senkou_span_b",
    chikou_span="chikou_span",
    width=1,
    tenkan_sen_color="#33BDFF",
    kijun_sen_color="#D105F5",
    senkou_span_a_color="#228B22",
    senkou_span_b_color="#FF3342",
    chikou_span_color="#F1F316",
    filter: Series | None = None,
) -> dict

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • tenkan_sen (str, default: 'tenkan_sen' ) –

    description. Defaults to "tenkan_sen".

  • kijun_sen (str, default: 'kijun_sen' ) –

    description. Defaults to "kijun_sen".

  • senkou_span_a (str, default: 'senkou_span_a' ) –

    description. Defaults to "senkou_span_a".

  • senkou_span_b (str, default: 'senkou_span_b' ) –

    description. Defaults to "senkou_span_b".

  • chikou_span (str, default: 'chikou_span' ) –

    description. Defaults to "chikou_span".

  • width (int, default: 1 ) –

    description. Defaults to 1.

  • tenkan_sen_color (str, default: '#33BDFF' ) –

    description. Defaults to "#33BDFF".

  • kijun_sen_color (str, default: '#D105F5' ) –

    description. Defaults to "#D105F5".

  • senkou_span_a_color (str, default: '#228B22' ) –

    description. Defaults to "#228B22".

  • senkou_span_b_color (str, default: '#FF3342' ) –

    description. Defaults to "#FF3342".

  • chikou_span_color (str, default: '#F1F316' ) –

    description. Defaults to "#F1F316".

  • filter (Series | None, default: None ) –

    description. Defaults to None.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/plot/plotly/indicator.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def plot_ichimoku(
    dataframe: pd.DataFrame,
    tenkan_sen="tenkan_sen",
    kijun_sen="kijun_sen",
    senkou_span_a="senkou_span_a",
    senkou_span_b="senkou_span_b",
    chikou_span="chikou_span",
    width=1,
    tenkan_sen_color="#33BDFF",
    kijun_sen_color="#D105F5",
    senkou_span_a_color="#228B22",
    senkou_span_b_color="#FF3342",
    chikou_span_color="#F1F316",
    filter: pd.Series | None = None,
) -> dict:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        tenkan_sen (str, optional): _description_. Defaults to "tenkan_sen".
        kijun_sen (str, optional): _description_. Defaults to "kijun_sen".
        senkou_span_a (str, optional): _description_. Defaults to "senkou_span_a".
        senkou_span_b (str, optional): _description_. Defaults to "senkou_span_b".
        chikou_span (str, optional): _description_. Defaults to "chikou_span".
        width (int, optional): _description_. Defaults to 1.
        tenkan_sen_color (str, optional): _description_. Defaults to "#33BDFF".
        kijun_sen_color (str, optional): _description_. Defaults to "#D105F5".
        senkou_span_a_color (str, optional): _description_. Defaults to "#228B22".
        senkou_span_b_color (str, optional): _description_. Defaults to "#FF3342".
        chikou_span_color (str, optional): _description_. Defaults to "#F1F316".
        filter (pd.Series | None, optional): _description_. Defaults to None.

    Returns:
        dict: _description_
    """
    if filter is not None:
        df_name = dataframe.name
        dataframe = dataframe.loc[filter]
        object.__setattr__(dataframe, "name", df_name)

    items = []

    if tenkan_sen is not None:
        items.append(
            dict(
                type="scatter",
                x=dataframe.index,
                y=dataframe[tenkan_sen],
                name=tenkan_sen,
                mode="lines",
                line=dict(color=tenkan_sen_color, width=width),
            )
        )
    if kijun_sen is not None:
        items.append(
            dict(
                type="scatter",
                x=dataframe.index,
                y=dataframe[kijun_sen],
                name=kijun_sen,
                mode="lines",
                line=dict(color=kijun_sen_color, width=width),
            )
        )
    if senkou_span_a is not None:
        items.append(
            dict(
                type="scatter",
                x=dataframe.index,
                y=dataframe[senkou_span_a],
                name=senkou_span_a,
                mode="lines",
                line=dict(color=senkou_span_a_color, width=width),
            )
        )
    if senkou_span_b is not None:
        items.append(
            dict(
                type="scatter",
                x=dataframe.index,
                y=dataframe[senkou_span_b],
                name=senkou_span_b,
                mode="lines",
                fill="tonexty",
                line=dict(color=senkou_span_b_color, width=width),
            )
        )
    if chikou_span is not None:
        items.append(
            dict(
                type="scatter",
                x=dataframe.index,
                y=dataframe[chikou_span],
                name=chikou_span,
                mode="lines",
                line=dict(color=chikou_span_color, width=width),
            )
        )

    return {f"{dataframe.name}": dict(items=items)}

plot_line ¤

plot_line(
    series: Series | str,
    color: str = "#ffee58",
    width: int = 1,
    name: str | None = None,
    mode: str = "lines",
    fullfill: bool = False,
    dataframe: DataFrame | None = None,
    filter: Series | None = None,
    **kwargs
) -> dict

summary

Parameters:

  • series (Series | str) –

    description

  • color (str, default: '#ffee58' ) –

    description. Defaults to "#ffee58".

  • width (int, default: 1 ) –

    description. Defaults to 1.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • mode (str, default: 'lines' ) –

    description. Defaults to "lines".

  • fullfill (bool, default: False ) –

    description. Defaults to False.

  • dataframe (DataFrame | None, default: None ) –

    description. Defaults to None.

  • filter (Series | None, default: None ) –

    description. Defaults to None.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/plot/plotly/indicator.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
def plot_line(
    series: pd.Series | str,
    color: str = "#ffee58",
    width: int = 1,
    name: str | None = None,
    mode: str = "lines",
    fullfill: bool = False,
    dataframe: pd.DataFrame | None = None,
    filter: pd.Series | None = None,
    **kwargs,
) -> dict:
    """_summary_

    Args:
        series (pd.Series | str): _description_
        color (str, optional): _description_. Defaults to "#ffee58".
        width (int, optional): _description_. Defaults to 1.
        name (str | None, optional): _description_. Defaults to None.
        mode (str, optional): _description_. Defaults to "lines".
        fullfill (bool, optional): _description_. Defaults to False.
        dataframe (pd.DataFrame | None, optional): _description_. Defaults to None.
        filter (pd.Series | None, optional): _description_. Defaults to None.

    Returns:
        dict: _description_
    """
    if isinstance(series, str):
        series = dataframe[series]

    if filter is not None:
        series = series.loc[filter]

    config = dict(
        items=[
            dict(
                type="scatter",
                x=series.index,
                y=series,
                line=dict(color=color, width=width),
                name=name or series.name,
                mode=mode,
                fullfill=fullfill,
                **kwargs,
            )
        ]
    )
    if dataframe is None:
        return config

    return {f"{dataframe.name}": config}

plot_lines ¤

plot_lines(
    *serieses: list[Series | str],
    color: str = "#ffee58",
    width: int = 1,
    name: str | None = None,
    mode: str = "lines",
    fullfill: bool = False,
    dataframe: DataFrame | None = None,
    **kwargs
) -> dict

summary

Parameters:

  • color (str, default: '#ffee58' ) –

    description. Defaults to "#ffee58".

  • width (int, default: 1 ) –

    description. Defaults to 1.

  • name (str | None, default: None ) –

    description. Defaults to None.

  • mode (str, default: 'lines' ) –

    description. Defaults to "lines".

  • fullfill (bool, default: False ) –

    description. Defaults to False.

  • dataframe (DataFrame | None, default: None ) –

    description. Defaults to None.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/plot/plotly/indicator.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def plot_lines(
    *serieses: list[pd.Series | str],
    color: str = "#ffee58",
    width: int = 1,
    name: str | None = None,
    mode: str = "lines",
    fullfill: bool = False,
    dataframe: pd.DataFrame | None = None,
    **kwargs,
) -> dict:
    """_summary_

    Args:
        color (str, optional): _description_. Defaults to "#ffee58".
        width (int, optional): _description_. Defaults to 1.
        name (str | None, optional): _description_. Defaults to None.
        mode (str, optional): _description_. Defaults to "lines".
        fullfill (bool, optional): _description_. Defaults to False.
        dataframe (pd.DataFrame | None, optional): _description_. Defaults to None.

    Returns:
        dict: _description_
    """
    result = {}
    for series in serieses:
        plot_merge(
            result,
            plot_line(
                series=series,
                color=color,
                width=width,
                name=name,
                mode=mode,
                fullfill=fullfill,
                dataframe=dataframe,
                **kwargs,
            ),
        )
    return result

plot_mark ¤

plot_mark(
    series: Series | str,
    color: str = "#ffee58",
    width: int = 1,
    mode: str = "markers",
    name: str | None = None,
    fullfill: bool = False,
    dataframe: DataFrame | None = None,
    filter: Series | None = None,
    **kwargs
) -> dict

summary

Parameters:

  • series (Series | str) –

    description

  • color (str, default: '#ffee58' ) –

    description. Defaults to "#ffee58".

  • width (int, default: 1 ) –

    description. Defaults to 1.

  • mode (str, default: 'markers' ) –

    description. Defaults to "markers".

  • name (str | None, default: None ) –

    description. Defaults to None.

  • fullfill (bool, default: False ) –

    description. Defaults to False.

  • dataframe (DataFrame | None, default: None ) –

    description. Defaults to None.

  • filter (Series | None, default: None ) –

    description. Defaults to None.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/plot/plotly/indicator.py
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
def plot_mark(
    series: pd.Series | str,
    color: str = "#ffee58",
    width: int = 1,
    mode: str = "markers",
    name: str | None = None,
    fullfill: bool = False,
    dataframe: pd.DataFrame | None = None,
    filter: pd.Series | None = None,
    **kwargs,
) -> dict:
    """_summary_

    Args:
        series (pd.Series | str): _description_
        color (str, optional): _description_. Defaults to "#ffee58".
        width (int, optional): _description_. Defaults to 1.
        mode (str, optional): _description_. Defaults to "markers".
        name (str | None, optional): _description_. Defaults to None.
        fullfill (bool, optional): _description_. Defaults to False.
        dataframe (pd.DataFrame | None, optional): _description_. Defaults to None.
        filter (pd.Series | None, optional): _description_. Defaults to None.

    Returns:
        dict: _description_
    """

    return plot_line(
        series=series,
        color=color,
        width=width,
        mode=mode,
        name=name,
        fullfill=fullfill,
        dataframe=dataframe,
        filter=filter,
        **kwargs,
    )

plot_merge ¤

plot_merge(
    source: dict,
    *updates: list[dict],
    recursive: int = 0,
    recursive_max: int = 1
) -> dict

Merge multiple update plot config to source config

Parameters:

  • source (dict) –

    description

  • *updates (list[dict], default: () ) –

    description

Returns:

  • dict ( dict ) –

    Merged config

Source code in lettrade/plot/helper.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def plot_merge(
    source: dict,
    *updates: list[dict],
    recursive: int = 0,
    recursive_max: int = 1,
) -> dict:
    """Merge multiple update plot config to source config

    Args:
        source (dict): _description_
        *updates (list[dict]): _description_

    Returns:
        dict: Merged config
    """
    for update in updates:
        for key, value in update.items():
            if key not in source:
                source[key] = value
                continue

            if isinstance(source[key], list):
                source[key].extend(value)
                continue
            if isinstance(source[key], dict):
                if recursive >= recursive_max:
                    source[key] = value
                    continue

                sub = source[key]
                plot_merge(
                    sub,
                    value,
                    recursive=recursive + 1,
                    recursive_max=recursive_max,
                )
                continue
    return source

plot_parabolic_sar ¤

plot_parabolic_sar(
    dataframe: DataFrame,
    long: str | None = "long",
    short: str | None = "short",
    width: int = 1,
    long_color: str = "#33BDFF",
    short_color: str = "#D105F5",
    filter: Series | None = None,
) -> dict

summary

Parameters:

  • dataframe (DataFrame) –

    description

  • long (str | None, default: 'long' ) –

    description. Defaults to "long".

  • short (str | None, default: 'short' ) –

    description. Defaults to "short".

  • width (int, default: 1 ) –

    description. Defaults to 1.

  • long_color (str, default: '#33BDFF' ) –

    description. Defaults to "#33BDFF".

  • short_color (str, default: '#D105F5' ) –

    description. Defaults to "#D105F5".

  • filter (Series | None, default: None ) –

    description. Defaults to None.

Returns:

  • dict ( dict ) –

    description

Source code in lettrade/plot/plotly/indicator.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def plot_parabolic_sar(
    dataframe: pd.DataFrame,
    long: str | None = "long",
    short: str | None = "short",
    width: int = 1,
    long_color: str = "#33BDFF",
    short_color: str = "#D105F5",
    filter: pd.Series | None = None,
) -> dict:
    """_summary_

    Args:
        dataframe (pd.DataFrame): _description_
        long (str | None, optional): _description_. Defaults to "long".
        short (str | None, optional): _description_. Defaults to "short".
        width (int, optional): _description_. Defaults to 1.
        long_color (str, optional): _description_. Defaults to "#33BDFF".
        short_color (str, optional): _description_. Defaults to "#D105F5".
        filter (pd.Series | None, optional): _description_. Defaults to None.

    Returns:
        dict: _description_
    """
    if filter is not None:
        df_name = dataframe.name
        dataframe = dataframe.loc[filter]
        object.__setattr__(dataframe, "name", df_name)

    config = dict()

    if long is not None:
        plot_merge(
            config,
            plot_mark(
                series=long,
                color=long_color,
                name="psar_long",
                width=width,
                dataframe=dataframe,
            ),
        )
    if short is not None:
        plot_merge(
            config,
            plot_mark(
                series=short,
                color=short_color,
                name="psar_short",
                width=width,
                dataframe=dataframe,
            ),
        )
    return config

rolling_above ¤

rolling_above(
    series1: Series,
    series2: Series,
    window: int = 20,
    min_periods: int | None = None,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
) -> Series

Check a Series is rolling above another Series

Parameters:

  • series1 (Series) –

    first Series

  • series2 (Series) –

    second Series

Returns:

  • Series

    pd.Series: 100 mean series1 is rolling above series2 else 0

Source code in lettrade/indicator/series.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def rolling_above(
    series1: pd.Series,
    series2: pd.Series,
    window: int = 20,
    min_periods: int | None = None,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    # **kwargs,
) -> pd.Series:
    """Check a Series is rolling above another Series

    Args:
        series1 (pd.Series): first Series
        series2 (pd.Series): second Series

    Returns:
        pd.Series: 100 mean series1 is rolling above series2 else 0
    """
    if __debug__:
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    if isinstance(series1, str):
        series1 = dataframe[series1]
    if isinstance(series2, str):
        series2 = dataframe[series2]

    min_periods = window if min_periods is None else min_periods

    i = s_above(series1, series2)
    i = i.rolling(window=window, min_periods=min_periods).min()
    i = i.apply(lambda v: 100 if v >= 100 else 0).astype(int)

    if inplace:
        name = name or f"{prefix}rolling_above"
        dataframe[name] = i

        if plot:
            _plot_mark(dataframe=dataframe, name=name, plot_kwargs=plot_kwargs)

    return i

rolling_below ¤

rolling_below(
    series1: Series,
    series2: Series,
    window: int = 20,
    min_periods: int | None = None,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
) -> Series

Check a Series is rolling below another Series

Parameters:

  • series1 (Series) –

    first Series

  • series2 (Series) –

    second Series

Returns:

  • Series

    pd.Series: -100 mean series1 is rolling below series2 else 0

Source code in lettrade/indicator/series.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def rolling_below(
    series1: pd.Series,
    series2: pd.Series,
    window: int = 20,
    min_periods: int | None = None,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    # **kwargs,
) -> pd.Series:
    """Check a Series is rolling below another Series

    Args:
        series1 (pd.Series): first Series
        series2 (pd.Series): second Series

    Returns:
        pd.Series: -100 mean series1 is rolling below series2 else 0
    """
    if __debug__:
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    if isinstance(series1, str):
        series1 = dataframe[series1]
    if isinstance(series2, str):
        series2 = dataframe[series2]

    min_periods = window if min_periods is None else min_periods

    i = s_below(series1, series2)
    i = i.rolling(window=window, min_periods=min_periods).max()
    i = i.apply(lambda v: -100 if v <= -100 else 0).astype(int)

    if inplace:
        name = name or f"{prefix}rolling_below"
        dataframe[name] = i

        if plot:
            _plot_mark(dataframe=dataframe, name=name, plot_kwargs=plot_kwargs)

    return i

rolling_direction ¤

rolling_direction(
    series1: Series,
    series2: Series,
    window: int = 20,
    min_periods: int | None = None,
    dataframe: DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
) -> Series

Check a Series is rolling on one side with another Series

Parameters:

  • series1 (Series) –

    first Series

  • series2 (Series) –

    second Series

Returns:

  • Series

    pd.Series: -100 mean series1 is rolling direction series2 else 0

Source code in lettrade/indicator/series.py
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
def rolling_direction(
    series1: pd.Series,
    series2: pd.Series,
    window: int = 20,
    min_periods: int | None = None,
    dataframe: pd.DataFrame = None,
    name: str | None = None,
    prefix: str = "",
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_kwargs: dict | None = None,
    # **kwargs,
) -> pd.Series:
    """Check a Series is rolling on one side with another Series

    Args:
        series1 (pd.Series): first Series
        series2 (pd.Series): second Series

    Returns:
        pd.Series: -100 mean series1 is rolling direction series2 else 0
    """
    if __debug__:
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    if isinstance(series1, str):
        series1 = dataframe[series1]
    if isinstance(series2, str):
        series2 = dataframe[series2]

    min_periods = window if min_periods is None else min_periods

    i = s_direction(series1, series2)
    i = i.rolling(window=window, min_periods=min_periods).mean()
    i = i.apply(lambda v: -100 if v <= -100 else 100 if v >= 100 else 0).astype(int)

    if inplace:
        name = name or f"{prefix}rolling_direction"
        dataframe[name] = i

        if plot:
            _plot_mark(dataframe=dataframe, name=name, plot_kwargs=plot_kwargs)

    return i

signal_condiction ¤

signal_condiction(
    dataframe: DataFrame,
    *condictions: list[list[Series | Any]],
    name: str,
    value: int | float = np.nan,
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["line", "mark"] = "line",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

Define a signal with multiple condiction

Parameters:

  • dataframe (DataFrame) –

    description

  • *condictions (list[list[Series | Any]], default: () ) –

    Pairs of condiction [<pandas.Series condiction>, <value>]

  • name (str) –

    Name of signal, column name when add to DataFrame with inplace=True.

  • value (int, default: nan ) –

    Default value when condiction is not matched. Defaults to 0.

  • inplace (bool, default: False ) –

    description. Defaults to False.

  • plot (bool | list, default: False ) –

    description. Defaults to False.

  • plot_type (Literal[&quot;line&quot;, &quot;mark&quot;], default: 'line' ) –

    description. Defaults to "line".

  • plot_kwargs (dict | None, default: None ) –

    description. Defaults to None.

Usage
df.i.signal_condiction(
    [df["close"] > df["open"], 100],
    [df["close"] < df["open"], -100],
    name="cdl_direction",
    inplace=True,
    plot=True,
    plot_kwargs=dict(color="green", width=5),
)

Raises:

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/dataframe.py
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def signal_condiction(
    dataframe: pd.DataFrame,
    *condictions: list[list[pd.Series | Any]],
    name: str,
    value: int | float = np.nan,
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["line", "mark"] = "line",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """Define a signal with multiple condiction

    Args:
        dataframe (pd.DataFrame): _description_
        *condictions (list[list[pd.Series | Any]]): Pairs of condiction [`<pandas.Series condiction>`, `<value>`]
        name (str): Name of signal, column name when add to DataFrame with inplace=True.
        value (int, optional): Default value when condiction is not matched. Defaults to 0.
        inplace (bool, optional): _description_. Defaults to False.
        plot (bool | list, optional): _description_. Defaults to False.
        plot_type (Literal[&quot;line&quot;, &quot;mark&quot;], optional): _description_. Defaults to "line".
        plot_kwargs (dict | None, optional): _description_. Defaults to None.

    Usage:
        ```python
        df.i.signal_condiction(
            [df["close"] > df["open"], 100],
            [df["close"] < df["open"], -100],
            name="cdl_direction",
            inplace=True,
            plot=True,
            plot_kwargs=dict(color="green", width=5),
        )
        ```

    Raises:
        RuntimeError: _description_

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    if __debug__:
        if not isinstance(dataframe, pd.DataFrame):
            raise RuntimeError(
                f"dataframe type '{type(dataframe)}' "
                "is not instance of pandas.DataFrame"
            )
        if plot and not inplace:
            raise RuntimeError("Cannot plot when inplace=False")

    s = pd.Series(value, index=dataframe.index, name=name, **kwargs)
    for condiction in condictions:
        s.loc[condiction[0]] = condiction[1]

    if inplace:
        dataframe[name] = s

        # Plot
        if plot:
            if plot_kwargs is None:
                plot_kwargs = dict()

            plot_kwargs.update(series=name, name=name)

            from lettrade.indicator.plot import IndicatorPlotter
            from lettrade.plot.plotly import plot_line, plot_mark

            plotter = plot_mark if plot_type == "mark" else plot_line
            IndicatorPlotter(dataframe=dataframe, plotter=plotter, **plot_kwargs)

        return dataframe

    return s

signal_direction ¤

signal_direction(
    dataframe: DataFrame,
    up: Series,
    down: Series,
    name: str,
    value: int = 0,
    value_up: int = 100,
    value_down: int = -100,
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["line", "mark"] = "line",
    plot_kwargs: dict | None = None,
    **kwargs
) -> Series | DataFrame

Define a signal with 2 direction Up and Down with fixed value

Parameters:

  • dataframe (DataFrame) –

    description

  • up (Series) –

    description

  • down (Series) –

    description

  • name (str) –

    Name of signal, column name when add to DataFrame with inplace=True.

  • value (int, default: 0 ) –

    Default value when condiction is not matched. Defaults to 0.

  • value_up (int, default: 100 ) –

    description. Defaults to 100.

  • value_down (int, default: -100 ) –

    description. Defaults to -100.

  • inplace (bool, default: False ) –

    Whether to add to the DataFrame and return DataFrame rather than return result. Defaults to False.

  • plot (bool | list, default: False ) –

    description. Defaults to False.

  • plot_type (Literal[&quot;line&quot;, &quot;mark&quot;], default: 'line' ) –

    description. Defaults to "line".

  • plot_kwargs (dict | None, default: None ) –

    description. Defaults to None.

Returns:

  • Series | DataFrame

    pd.Series | pd.DataFrame: description

Source code in lettrade/indicator/dataframe.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def signal_direction(
    dataframe: pd.DataFrame,
    up: pd.Series,
    down: pd.Series,
    name: str,
    value: int = 0,
    value_up: int = 100,
    value_down: int = -100,
    inplace: bool = False,
    plot: bool | list[str] = False,
    plot_type: Literal["line", "mark"] = "line",
    plot_kwargs: dict | None = None,
    **kwargs,
) -> pd.Series | pd.DataFrame:
    """Define a signal with 2 direction Up and Down with fixed value

    Args:
        dataframe (pd.DataFrame): _description_
        up (pd.Series): _description_
        down (pd.Series): _description_
        name (str): Name of signal, column name when add to DataFrame with inplace=True.
        value (int, optional): Default value when condiction is not matched. Defaults to 0.
        value_up (int, optional): _description_. Defaults to 100.
        value_down (int, optional): _description_. Defaults to -100.
        inplace (bool, optional): Whether to add to the DataFrame and return DataFrame rather than return result. Defaults to False.
        plot (bool | list, optional): _description_. Defaults to False.
        plot_type (Literal[&quot;line&quot;, &quot;mark&quot;], optional): _description_. Defaults to "line".
        plot_kwargs (dict | None, optional): _description_. Defaults to None.

    Returns:
        pd.Series | pd.DataFrame: _description_
    """
    return signal_condiction(
        dataframe,
        [up, value_up],
        [down, value_down],
        name=name,
        value=value,
        inplace=inplace,
        plot=plot,
        plot_type=plot_type,
        plot_kwargs=plot_kwargs,
        **kwargs,
    )