---
title: "0.3.1 Variables, tipos, operadores y decimal"
description: "Variables, tipos y operadores de Python con precios y órdenes; por qué 0,1 + 0,2 no es 0,3 y cómo redondear a tick con decimal."
url: "https://pythonparatrading.com/curso-python-trading/nivel-0/variables-tipos-y-decimal/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-0/variables-tipos-y-decimal.png"
---

# 0.3.1 · Variables, tipos y operadores; coma flotante y decimal para precios y ticks

> Un precio, una cantidad y un ticker son datos de tipos distintos, y Python los trata de forma distinta. La lección presenta los tipos básicos y sus operadores, y el primer error clásico del trading en código: la coma flotante.

- Curso: [Nivel 0: Python desde cero y mentalidad de datos](https://pythonparatrading.com/curso-python-trading/nivel-0/)
- Módulo: 0.3 · Fundamentos del lenguaje
- Duración: 45 min
- Requisitos: [0.2.3 · Editores (VS Code, Cursor, PyCharm) y notebooks (Jupyter, JupyterLab, marimo)](https://pythonparatrading.com/curso-python-trading/nivel-0/editores-y-notebooks-jupyter-marimo/)

## Al terminar esta lección

- Crear variables de los tipos `int`, `float`, `str` y `bool` y consultar su tipo con `type()`.
- Calcular el valor de una posición, su P&L y los lotes completos de una orden con los operadores aritméticos, `//` y `%`.
- Explicar por qué `0.1 + 0.2 == 0.3` es `False` y comparar precios en coma flotante sin caer en ese error.
- Redondear un precio al tick de un mercado con `Decimal` y `quantize`, eligiendo el modo de redondeo según el lado de la orden.

## Variables: nombres para datos del mercado

Un programa trabaja con datos: el precio de un activo, la cantidad de una orden, el símbolo que se opera. Una
**variable** es un nombre que apunta a uno de esos datos. Se crea con el signo `=`, que en Python no significa
«igual», sino «asigna a este nombre el valor de la derecha»:

```python
ticker = "SINT"
precio = 142.22
cantidad = 100
es_compra = True

print(ticker, precio, cantidad, es_compra)
print(type(ticker), type(precio), type(cantidad), type(es_compra))
```
```output
SINT 142.22 100 True
<class 'str'> <class 'float'> <class 'int'> <class 'bool'>
```

Cada valor tiene un **tipo**, y `type()` lo dice. Los cuatro de la celda son los tipos básicos que aparecen en
cualquier programa de trading:

| Tipo | Qué guarda | Ejemplo de trading | Literal en Python |
|---|---|---|---|
| `int` | Enteros, sin límite de tamaño | Acciones de una orden, contratos, volumen | `100`, `-3`, `2_322_609` |
| `float` | Números con decimales (coma flotante binaria) | Precios, rentabilidades, volatilidad | `142.22`, `-0.035`, `1e-4` |
| `str` | Texto | Ticker, mercado, lado de la orden | `"SINT"`, `"compra"` |
| `bool` | Verdadero o falso | ¿Hay posición abierta? ¿Se tocó el stop? | `True`, `False` |
| `NoneType` | La ausencia de valor | Un stop que todavía no se ha fijado | `None` |

Tres reglas sobre los nombres. Se escriben en minúsculas con guiones bajos (`precio_entrada`, no `PrecioEntrada`):
es la convención de Python y la que sigue todo el curso. No pueden empezar por un número ni coincidir con palabras
reservadas (`if`, `for`, `class`…). Y conviene que digan lo que guardan: `p` o `x2` obligan a leer el resto del código
para saber qué son; `precio_cierre` no.

En el código el separador decimal es el punto (`142.22`), no la coma. El guion bajo dentro de un número solo sirve para
leerlo mejor: `2_322_609` es el mismo `int` que `2322609`.

Una variable se puede reasignar, y desde ese momento apunta al valor nuevo. El tipo lo lleva el valor, no el nombre:

```python
stop = None          # todavía sin stop
stop = 138.50        # ahora es un float
print(stop, type(stop))
```
```output
138.5 <class 'float'>
```

Python muestra `138.5`, no `138.50`: un `float` no guarda cuántos decimales se escribieron, solo el número. Cómo
mostrar siempre dos decimales se ve en la lección 0.3.2.

## Operadores aritméticos

Los operadores combinan valores. Los aritméticos son los de una calculadora, más tres que en trading se usan a diario:
la división entera `//`, el resto `%` y la potencia `**`.

| Operador | Operación | Ejemplo | Resultado |
|---|---|---|---|
| `+` `-` `*` | Suma, resta, producto | `142.22 * 100` | `14222.0` |
| `/` | División (siempre `float`) | `10 / 2` | `5.0` |
| `//` | División entera (hacia abajo) | `1250 // 100` | `12` |
| `%` | Resto de la división entera | `1250 % 100` | `50` |
| `**` | Potencia | `1.05 ** 10` | `1.628…` |

El valor de una posición, su resultado y su rentabilidad salen de multiplicar, restar y dividir:

```python
precio_entrada = 135.40
precio_actual = 142.22
cantidad = 100

valor = precio_actual * cantidad
pnl = (precio_actual - precio_entrada) * cantidad
rentabilidad = precio_actual / precio_entrada - 1

print(valor)
print(pnl)
print(rentabilidad)
```
```output
14222.0
681.9999999999993
0.050369276218611425
```

La salida ya avisa del tema central de la lección: el P&L, que debería ser exactamente 682 €, aparece con una cola de
decimales. Antes de explicarlo, los otros dos operadores.

La división entera y el resto resuelven un problema que aparece en cuanto se calcula el tamaño de una orden: muchas
órdenes solo admiten múltiplos de un lote, y los futuros se operan en contratos enteros. `//` da cuántos caben y `%`
lo que sobra:

```python
acciones = 1_250
lote = 100
print("Lotes completos:", acciones // lote)
print("Acciones sueltas:", acciones % lote)

capital = 25_000
garantia_contrato = 6_000
print("Contratos:", capital // garantia_contrato)
print("Capital sin usar:", capital % garantia_contrato)

print("Capital al 5 % anual en 10 años:", 10_000 * 1.05 ** 10)
```
```output
Lotes completos: 12
Acciones sueltas: 50
Contratos: 4
Capital sin usar: 1000
Capital al 5 % anual en 10 años: 16288.94626777442
```

Dos detalles de esta celda. `print` admite varios valores separados por comas y los escribe con un espacio entre
ellos. Y el orden de las operaciones es el de las matemáticas: primero `**`, después `*`, `/`, `//` y `%`, y al final
`+` y `-`. Por eso `10_000 * 1.05 ** 10` eleva antes de multiplicar. Ante la duda, paréntesis.

Hay una diferencia entre `/` y `//` que conviene fijar desde el principio: `/` devuelve siempre un `float`, aunque la
división sea exacta (`10 / 2` es `5.0`). Si el resultado va a contar acciones o contratos, tiene que salir de `//` o
de un redondeo explícito, nunca de pasar un `float` a `int` sin más (se ve abajo por qué).

## Comparaciones y lógica

Los operadores de comparación (`==`, `!=`, `<`, `<=`, `>`, `>=`) devuelven un `bool`, y `and`, `or` y `not` los
combinan. Son la materia prima de cualquier regla de trading:

```python
cierre = 142.22
media_200 = 131.80
stop = 138.50

por_encima = cierre > media_200
stop_tocado = cierre <= stop
mantener = por_encima and not stop_tocado
print(por_encima, stop_tocado, mantener)
```
```output
True False True
```

Ojo con la diferencia entre `=` (asignar) y `==` (comparar). Qué hacer con estas condiciones —comprar, vender, no
hacer nada— es materia de la lección 0.3.4.

## Coma flotante: por qué 0,1 + 0,2 no es 0,3

Un `float` se guarda en **binario**, con 53 bits de precisión (la norma IEEE 754, la misma en cualquier ordenador).
Igual que 1/3 no tiene una expresión decimal finita (0,3333…), la mayoría de las fracciones decimales no tienen una
expresión binaria finita: 0,1 es en base 2 una fracción periódica. El ordenador guarda el número binario más cercano,
y la diferencia aparece en cuanto se opera:

```python
from decimal import Decimal

print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(Decimal(0.1))
print(Decimal(142.22))
```
```output
0.30000000000000004
False
0.1000000000000000055511151231257827021181583404541015625
142.219999999999998863131622783839702606201171875
```

`Decimal(0.1)` muestra el valor exacto que hay en memoria cuando se escribe `0.1`: casi, pero no exactamente, una
décima. Lo mismo pasa con casi cualquier precio con decimales. `print` lo disimula porque muestra el número más corto
que vuelve a dar el mismo `float`, pero el valor guardado es el de la última línea.

En trading, este error de representación produce tres fallos típicos:

- **Comparar con `==`**. Un precio calculado (`entrada + 3 * tick`) puede no ser igual al precio que llega del
  mercado aunque los dos se lean igual. Una condición `if precio == objetivo` que nunca se cumple es un error difícil
  de ver.
- **Contar ticks con `int()`**. `int` no redondea: corta los decimales. Si la división da 9,99999999999943…, el resultado
  es 9.
- **Redondear al céntimo con `round()`**. `round(2.675, 2)` da `2.67`, porque 2,675 está guardado como
  2,67499999…; además, `round` redondea los empates al par (`round(0.125, 2)` es `0.12`).

```python
tick = 0.01
print((101.03 - 101.00) / tick, int((101.03 - 101.00) / tick))
print((100.05 - 99.95) / tick, int((100.05 - 99.95) / tick))
print(round(2.675, 2), round(0.125, 2))

tick_futuro = 0.25
print((4500.75 - 4500.00) / tick_futuro)
```
```output
3.0000000000001137 3
9.999999999999432 9
2.67 0.12
3.0
```

Entre 99,95 y 100,05 hay diez ticks de 0,01; `int` cuenta nueve. La última línea, en cambio, es exacta: 0,25 es 1/4,
una fracción binaria finita, así que los precios en cuartos se representan sin error. Los ticks de 0,01, 0,05 o 0,1,
no.

Para comparar precios en coma flotante, la regla es comparar con una tolerancia (`math.isclose(a, b)` o
`abs(a - b) < tick / 2`) y, para contar ticks, redondear antes de convertir: `round((b - a) / tick)`.

<aside class="callout" data-kind="aviso">

No es un fallo de Python: JavaScript, C, Java y NumPy usan la misma coma flotante (IEEE 754) y dan los mismos
resultados; Excel también la usa, aunque redondea lo que muestra a 15 cifras. La diferencia es que Python ofrece, sin
instalar nada, un tipo decimal exacto.

</aside>

## Cuánto se acumula el error

El error de cada operación es del orden de 10⁻¹⁶ veces el valor. Lo que importa es si crece al sumar muchas
operaciones, como en un libro de operaciones o en el P&L de un backtest largo. La celda suma 0,10 € de beneficio
una y otra vez y mide la diferencia con el resultado exacto (el bucle `for` repite la suma; se explica en la lección
0.3.4):

```python
from decimal import Decimal

for n in [10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000]:
    total = 0.0
    for _ in range(n):
        total += 0.1
    exacto = Decimal(n) / 10
    error = abs(Decimal(total) - exacto)
    print(f"{n:>10,}  {total!r:<20}  error {float(error):.1e}")
```
```output
        10  0.9999999999999999    error 1.1e-16
       100  9.99999999999998      error 2.0e-14
     1,000  99.9999999999986      error 1.4e-12
    10,000  1000.0000000001588    error 1.6e-10
   100,000  10000.000000018848    error 1.9e-08
 1,000,000  100000.00000133288    error 1.3e-06
10,000,000  999999.9998389754     error 1.6e-04
```

`total += 0.1` es una forma corta de escribir `total = total + 0.1`. La figura de la cabecera traduce esos errores a
**decimales exactos**: cuántos decimales del total coinciden con la cuenta exacta. Tras diez sumas, quince; tras diez
millones, tres. Dos conclusiones, las dos importantes:

- Para estadística, rentabilidades y backtests, el `float` basta. Un error en el cuarto decimal tras diez millones de
  sumas no cambia ninguna decisión, y NumPy y pandas (módulo 0.5) trabajan en `float` por velocidad.
- Para precios de órdenes, saldos de cuenta y conciliación con el bróker, no basta. Ahí el problema no es el tamaño
  del error, sino que exista: un precio que no cae en la rejilla de ticks o un saldo que no cuadra al céntimo es un
  error, aunque sea pequeño.

## Decimal: precios exactos y redondeo a tick

El módulo `decimal` de la biblioteca estándar representa números en base 10, como en la contabilidad. Un `Decimal`
creado **desde una cadena** guarda exactamente lo que se escribe:

```python
from decimal import Decimal

print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3"))
print(Decimal("0.1"), Decimal(0.1) == Decimal("0.1"))

entrada = Decimal("135.40")
salida = Decimal("142.22")
pnl = (salida - entrada) * 100
print(pnl)
print((Decimal("100.05") - Decimal("99.95")) / Decimal("0.01"))
```
```output
True
0.1 False
682.00
10
```

El P&L sale exacto, con sus dos decimales, y el recuento de ticks es 10. La segunda línea muestra la trampa:
`Decimal(0.1)`, sin comillas, copia el `float` con su error. Los precios se pasan a `Decimal` como texto: tal como
llegan del bróker o del fichero, o con `Decimal(str(precio))` si ya son `float`.

Para llevar un precio a la rejilla de ticks hay dos casos. Si el tick es una potencia de diez (0,01, 0,001), basta con
`quantize`, que redondea a los decimales del valor que recibe. Si no lo es (0,05, 0,25), se divide por el tick, se
redondea a entero y se vuelve a multiplicar. En los dos casos, el **modo de redondeo** depende del lado de la orden:
una compra límite se redondea hacia abajo, para no pagar más que el precio calculado, y una venta límite hacia arriba.

```python
from decimal import ROUND_CEILING, ROUND_FLOOR, ROUND_HALF_EVEN

precio = Decimal("101.237")
print(precio.quantize(Decimal("0.01")))
print(precio.quantize(Decimal("0.01"), rounding=ROUND_FLOOR))

tick = Decimal("0.05")
unidades = precio / tick
compra = unidades.quantize(Decimal("1"), rounding=ROUND_FLOOR) * tick
venta = unidades.quantize(Decimal("1"), rounding=ROUND_CEILING) * tick
print("Compra límite:", compra, " Venta límite:", venta)

print(Decimal("2.675").quantize(Decimal("0.01")),
      Decimal("2.665").quantize(Decimal("0.01"),
                                rounding=ROUND_HALF_EVEN))
```
```output
101.24
101.23
Compra límite: 101.20  Venta límite: 101.25
2.68 2.66
```

Sin `rounding`, `quantize` usa el modo por defecto, `ROUND_HALF_EVEN`: el más cercano y, en caso de empate, el par
(el «redondeo del banquero», que no sesga los totales hacia arriba). Con `Decimal`, 2,675 sí es un empate y se
redondea a 2,68; 2,665 se redondea a 2,66, el par. `ROUND_HALF_UP` es el redondeo escolar; `ROUND_FLOOR` y
`ROUND_CEILING`, hacia abajo y hacia arriba.

La exactitud tiene un precio. `Decimal` es más lento que `float` y no se mezcla con él: sumar un `Decimal` y un
`float` es un error, para que nadie meta por descuido el error binario en una cuenta exacta. Python lo señala con una
excepción (las excepciones se ven en la lección 0.3.6):

```python raises
Decimal("142.22") + 0.01
```
```output
TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
```

Y la precisión de `Decimal` también es finita: 28 cifras significativas por defecto. Las divisiones que no son exactas
se redondean ahí, así que `Decimal` no arregla 1/3; arregla los decimales que se escriben en base 10, que son los de
los precios.

## Qué tipo usar en cada caso

| Dato | Tipo | Por qué |
|---|---|---|
| Acciones, contratos, lotes | `int` | Son enteros: con `//` y `%`, nunca con `int()` sobre un `float` |
| Rentabilidades, volatilidad, señales | `float` | Velocidad; el error relativo de 10⁻¹⁶ no cambia ninguna decisión |
| Precios de órdenes, ticks | `Decimal` o `int` en ticks | Tienen que caer exactamente en la rejilla del mercado |
| Saldos, comisiones, impuestos | `Decimal` | Deben cuadrar al céntimo con el extracto del bróker |
| Precios en un backtest vectorial | `float` (NumPy/pandas) | Se comparan con tolerancia y se redondean al tick al generar la orden |

La segunda opción para los precios, guardar el número de ticks como `int` (10.123 ticks de 0,01 en lugar de 101,23),
es la que usan muchos motores de bolsa: los enteros son exactos y rápidos.

## Ejercicios

1. Una cuenta tiene 47.300 € y cada contrato de un futuro exige 5.500 € de garantía. Calcula cuántos contratos se
   pueden abrir, cuánto capital queda sin usar y qué porcentaje de la cuenta queda comprometido como garantía.

**Solución.**

```python
capital = 47_300
garantia = 5_500
contratos = capital // garantia
print("Contratos:", contratos)
print("Sin usar:", capital % garantia)
print("Comprometido:", contratos * garantia / capital)
```
```output
Contratos: 8
Sin usar: 3300
Comprometido: 0.9302325581395349
```

Ocho contratos y 3.300 € sin usar: algo más del 93 % de la cuenta queda como garantía. Que se pueda no significa que
se deba: el tamaño de posición se decide por riesgo (nivel 4), no por la garantía disponible.

2. Un precio de compra calculado por una estrategia es 87,4361 y el mercado cotiza en ticks de 0,05. Con `Decimal`,
   calcula el precio límite de compra (hacia abajo) y el de venta (hacia arriba), y comprueba que los dos caen en la
   rejilla: el resto de dividirlos por el tick tiene que ser cero.

**Solución.**

```python
from decimal import Decimal, ROUND_CEILING, ROUND_FLOOR

precio = Decimal("87.4361")
tick = Decimal("0.05")
uno = Decimal("1")
compra = (precio / tick).quantize(uno, rounding=ROUND_FLOOR) * tick
venta = (precio / tick).quantize(uno, rounding=ROUND_CEILING) * tick
print(compra, venta)
print(compra % tick, venta % tick)
```
```output
87.40 87.45
0.00 0.00
```

El operador `%` también funciona con `Decimal`. Con `float`, el mismo resto daría un número diminuto en lugar de
cero y la comprobación fallaría.

3. Cuenta los ticks de 0,01 que hay entre 99,95 y 100,05 de tres formas: con `int()` sobre el `float`, con `round()`
   sobre el `float` y con `Decimal`. ¿Cuál falla y por qué?

**Solución.**

```python
from decimal import Decimal

print(int((100.05 - 99.95) / 0.01))
print(round((100.05 - 99.95) / 0.01))
print((Decimal("100.05") - Decimal("99.95")) / Decimal("0.01"))
```
```output
9
10
10
```

Falla `int()`, que trunca 9,99999999999943… a 9. `round()` lo arregla porque el error es mucho menor que medio tick, y
`Decimal` da el resultado exacto desde el principio.

## Autoevaluación

1. Una cuenta tiene 25.000 € y cada contrato de un futuro exige 6.000 € de garantía. ¿Qué expresión da el número de contratos que se pueden abrir?

   - A. `25_000 / 6_000`
   - B. `25_000 // 6_000`
   - C. `25_000 % 6_000`
   - D. `round(25_000 / 6_000)`

   *Respuesta B: `25_000 // 6_000`.* `//` es la división entera: 4 contratos. `/` da 4,1666…, `%` da el resto (1.000 €) y `round` daría 4 aquí, pero redondearía hacia arriba con 5,5 contratos y abriría uno que no se puede cubrir.

2. ¿Qué devuelve `0.1 + 0.2 == 0.3` en Python?

   - A. `True`
   - B. `False`, porque 0,1 y 0,2 no tienen representación exacta en binario
   - C. Un error de tipo
   - D. Depende del sistema operativo

   *Respuesta B: `False`, porque 0,1 y 0,2 no tienen representación exacta en binario.* Los `float` se guardan en binario y 0,1 es una fracción periódica en base 2. La suma da 0,30000000000000004. El resultado es el mismo en cualquier máquina: lo fija la norma IEEE 754.

3. Un precio en `float` está 10 ticks de 0,01 por encima de otro, pero `int((100.05 - 99.95) / 0.01)` devuelve 9. ¿Por qué?

   - A. Porque `int` redondea al entero más cercano
   - B. Porque la resta en coma flotante da 9,99999999999943… e `int` trunca los decimales
   - C. Porque 0,01 no es un tick válido
   - D. Por un error del intérprete

   *Respuesta B: Porque la resta en coma flotante da 9,99999999999943… e `int` trunca los decimales.* `int()` corta los decimales, no redondea. Con un error de representación justo por debajo del entero, el recuento de ticks pierde uno. Con `Decimal` creado desde cadenas la resta es exacta.

4. Hay que enviar una orden límite de compra y el precio calculado, 101,237, no está en la rejilla de ticks de 0,05. ¿Qué redondeo es el prudente?

   - A. Hacia arriba, a 101,25, para que se ejecute antes
   - B. Hacia abajo, a 101,20, para no pagar más que el precio calculado
   - C. Al más cercano siempre, sin mirar el lado
   - D. No redondear: el bróker lo acepta

   *Respuesta B: Hacia abajo, a 101,20, para no pagar más que el precio calculado.* Una compra límite redondeada hacia abajo nunca paga más de lo calculado; una venta, hacia arriba. Si el precio no cae en la rejilla, el bróker rechaza la orden.

## Ideas clave

- Cada valor tiene un tipo: `int` para cantidades, `float` para cálculos, `str` para tickers y `bool` para condiciones. `type()` lo muestra.
- `/` siempre da un `float`; `//` y `%` dan la parte entera y el resto: lotes completos, contratos y acciones sueltas.
- Los `float` son binarios: `0.1 + 0.2` no es `0.3`. Nunca se comparan con `==` ni se convierten con `int()` sin redondear antes.
- El error de un `float` es diminuto y se acumula despacio: para estadística y backtests vectoriales basta. Para precios de órdenes y contabilidad, `Decimal`.
- `Decimal` se crea desde cadenas, no desde `float`, y `quantize` redondea al tick con el modo que pida el lado de la orden.

## Recursos

### Research

- [Limit Order Book y liquidez de mercado](https://pythonparatrading.com/limit-order-book-y-liquidez-de-mercado/) · Research-0008 · 11 min

### Documentación

- [Python: «Floating-Point Arithmetic: Issues and Limitations» (tutorial oficial)](https://docs.python.org/3/tutorial/floatingpoint.html) · docs.python.org
- [Python: módulo decimal (documentación oficial)](https://docs.python.org/3/library/decimal.html) · docs.python.org
- [Python: tipos integrados, operadores numéricos (documentación oficial)](https://docs.python.org/3/library/stdtypes.html) · docs.python.org

Lección anterior: [0.2.6 · Introducción a los asistentes de código con IA: prompts, revisión y tests](https://pythonparatrading.com/curso-python-trading/nivel-0/asistentes-de-codigo-con-ia/)

Lección siguiente: [0.3.2 · Cadenas, f-strings y formato de números y fechas](https://pythonparatrading.com/curso-python-trading/nivel-0/cadenas-f-strings-y-formato/)
