4 columns for ticker tape

This commit is contained in:
Joe Lothan 2026-06-15 19:14:34 -04:00
parent c1447e5852
commit 281c3b1fe1

View file

@ -4,9 +4,9 @@
Reads a CSV of trades (timestamp, symbol, lots, price) and generates
ESC/POS commands with 90° rotated text to emulate an old stock ticker tape.
Each character gets its own printed row. When the receipt paper is turned
sideways, symbol characters read across the top and price characters read
across the bottom just like a real ticker tape.
Trades are distributed across 4 parallel columns. Each column independently
prints symbol characters at the top of the tape and price/lot characters at
the bottom just like a real multi-track ticker tape.
"""
import csv
@ -19,32 +19,85 @@ from glyphs import price_to_fraction, upload_fractions
# TM-T88V with 90° rotation: Font A chars are 24 dots wide (instead of 12),
# so 512 printable dots / 24 = 21 chars fit across the 80mm paper width.
LINE_WIDTH = 21
NUM_COLUMNS = 4
COL_WIDTH = LINE_WIDTH // NUM_COLUMNS # 5
def print_trade(p, symbol, lots, price):
"""Print a single trade as ticker tape characters, one per row."""
def format_price(lots, price):
"""Format price/lots string for a trade."""
frac = price_to_fraction(price)
if lots > 1:
price_str = f"{lots}s{frac}"
else:
price_str = frac
return f"{lots}s{frac}"
return frac
# Symbol characters at the top (right-aligned)
for ch in symbol:
p.text(" " * (LINE_WIDTH - 1) + ch + "\n")
# Price characters at the bottom (left-aligned)
for ch in price_str:
p.text(ch + "\n")
def trade_char_count(symbol, price_str):
"""Total characters a trade occupies in a column (symbol + price + separator)."""
return len(symbol) + len(price_str) + 1
# Blank line separator between trades
p.text("\n")
def assign_to_columns(trades):
"""Assign trades to columns, always picking the column with the fewest chars."""
columns = [[] for _ in range(NUM_COLUMNS)]
lengths = [0] * NUM_COLUMNS
for symbol, lots, price in trades:
price_str = format_price(lots, price)
count = trade_char_count(symbol, price_str)
shortest = lengths.index(min(lengths))
columns[shortest].append((symbol, price_str))
lengths[shortest] += count
return columns
def build_column_chars(trades_in_column):
"""Build the character sequence for one column.
Each entry is ('top', ch), ('bottom', ch), or ('blank',) controlling
where the character is placed within the column width.
"""
chars = []
for symbol, price_str in trades_in_column:
for ch in symbol:
chars.append(("top", ch))
for ch in price_str:
chars.append(("bottom", ch))
chars.append(("blank",))
return chars
def print_all(p, trades):
"""Print all trades across multiple columns, one row at a time."""
columns = assign_to_columns(trades)
col_chars = [build_column_chars(col) for col in columns]
max_len = max(len(cc) for cc in col_chars)
for row_idx in range(max_len):
row = [" "] * LINE_WIDTH
for col_idx in range(NUM_COLUMNS):
col_start = col_idx * COL_WIDTH
if row_idx < len(col_chars[col_idx]):
entry = col_chars[col_idx][row_idx]
if entry[0] == "top":
row[col_start + COL_WIDTH - 1] = entry[1]
elif entry[0] == "bottom":
row[col_start] = entry[1]
p.text("".join(row) + "\n")
def main():
csv_file = sys.argv[1] if len(sys.argv) > 1 else "trades_sample_sorted.csv"
output_file = sys.argv[2] if len(sys.argv) > 2 else "ticker_tape.bin"
# Read all trades
trades = []
with open(csv_file) as f:
reader = csv.reader(f)
for row in reader:
_timestamp, symbol, lots_str, price_str = row
trades.append((symbol, int(lots_str), float(price_str)))
p = Dummy(profile="TM-T88V")
p.hw("INIT")
# ESC V 1: 90° clockwise character rotation (not exposed by python-escpos)
@ -55,15 +108,9 @@ def main():
# Upload custom fraction glyphs (⅛ ¼ ⅜ ½ ⅝ ¾ ⅞)
upload_fractions(p)
with open(csv_file) as f:
reader = csv.reader(f)
for row in reader:
_timestamp, symbol, lots_str, price_str = row
lots = int(lots_str)
price = float(price_str)
print_trade(p, symbol, lots, price)
print_all(p, trades)
# Feed past the cutter (~20mm above print head, ~12 lines at 12-dot spacing)
# Feed past the cutter (~20mm above print head)
p.text("\n" * 16)
p.cut()