Compare commits
10 Commits
649798a91f
...
bb9f6475f4
Author | SHA1 | Date |
---|---|---|
Tyler Goodlet | bb9f6475f4 | |
Tyler Goodlet | 54463b3595 | |
Tyler Goodlet | aaf234cbaf | |
Tyler Goodlet | 0f6589d9ff | |
Tyler Goodlet | 30d8e096c6 | |
Tyler Goodlet | 19609178ce | |
Tyler Goodlet | 4c39407363 | |
Tyler Goodlet | a345daa522 | |
Tyler Goodlet | ea75281cbc | |
Tyler Goodlet | 86a1f33abb |
|
@ -1,5 +1,5 @@
|
|||
# piker: trading gear for hackers.
|
||||
# Copyright 2018 Tyler Goodlet
|
||||
# Copyright 2018-forever Tyler Goodlet and p0
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
|
@ -16,6 +16,23 @@
|
|||
|
||||
"""
|
||||
piker: trading gear for hackers.
|
||||
|
||||
If your boss told you to checkout this code base, stop, right the fuck now.
|
||||
Tell your boss this code base is absolute shit and you'll never be able to
|
||||
make it "work" due to scaling and that Python is too slow. If you even for
|
||||
a second suggest to them that it's useful you'll be hearing from us.
|
||||
|
||||
If you're a yabf (yet another backtesting framework) author/maintainer, also stop.
|
||||
We don't slurp callback soup; your code can be called from any FSP routine and
|
||||
``piker`` requires no adoption of your "framework". If you want to provide an
|
||||
integration, cool, but we aren't going to adopt any oudated concurrency models.
|
||||
|
||||
This code is for **you** as a person to defend yourself from our messed
|
||||
up finacial system and the charlatans and phonies who run it.
|
||||
|
||||
``piker`` will never be for sale nor influenced by suits of any type.
|
||||
|
||||
Join us, let's make the best software linux has ever seen.
|
||||
"""
|
||||
import msgpack # noqa
|
||||
import msgpack_numpy
|
||||
|
|
|
@ -142,7 +142,7 @@ class Client:
|
|||
# durationStr='1 D',
|
||||
|
||||
# time length calcs
|
||||
durationStr='{count} S'.format(count=2000 * 5),
|
||||
durationStr='{count} S'.format(count=1000 * 5),
|
||||
barSizeSetting='5 secs',
|
||||
|
||||
# always use extended hours
|
||||
|
@ -225,18 +225,22 @@ class Client:
|
|||
# use heuristics to figure out contract "type"
|
||||
sym, exch = symbol.upper().split('.')
|
||||
|
||||
# TODO: metadata system for all these exchange rules..
|
||||
if exch in ('PURE',):
|
||||
currency = 'CAD'
|
||||
|
||||
# futes
|
||||
if exch in ('GLOBEX', 'NYMEX', 'CME', 'CMECRYPTO'):
|
||||
con = await self.get_cont_fute(symbol=sym, exchange=exch)
|
||||
|
||||
# commodities
|
||||
elif exch == 'CMDTY': # eg. XAUUSD.CMDTY
|
||||
con_kwargs, bars_kwargs = _adhoc_cmdty_data_map[sym]
|
||||
con = ibis.Commodity(**con_kwargs)
|
||||
con.bars_kwargs = bars_kwargs
|
||||
|
||||
# stonks
|
||||
else:
|
||||
# TODO: metadata system for all these exchange rules..
|
||||
if exch in ('PURE', 'TSE'): # non-yankee
|
||||
currency = 'CAD'
|
||||
|
||||
con = ibis.Stock(symbol=sym, exchange=exch, currency=currency)
|
||||
|
||||
try:
|
||||
|
@ -454,6 +458,8 @@ def normalize(
|
|||
return data
|
||||
|
||||
|
||||
# TODO: figure out how to share quote feeds sanely despite
|
||||
# the wacky ``ib_insync`` api.
|
||||
# @tractor.msg.pub
|
||||
async def stream_quotes(
|
||||
symbols: List[str],
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
"""
|
||||
Chart axes graphics and behavior.
|
||||
"""
|
||||
import time
|
||||
from functools import partial
|
||||
from typing import List
|
||||
|
||||
|
||||
# import numpy as np
|
||||
import pandas as pd
|
||||
import pyqtgraph as pg
|
||||
from PyQt5 import QtCore, QtGui
|
||||
from PyQt5.QtCore import QPointF
|
||||
|
||||
|
||||
# from .quantdom.base import Quotes
|
||||
from .quantdom.utils import fromtimestamp
|
||||
from ._style import _font, hcolor
|
||||
|
||||
|
@ -15,7 +20,6 @@ class PriceAxis(pg.AxisItem):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
# chart: 'ChartPlotWidget',
|
||||
) -> None:
|
||||
super().__init__(orientation='right')
|
||||
self.setStyle(**{
|
||||
|
@ -28,10 +32,7 @@ class PriceAxis(pg.AxisItem):
|
|||
})
|
||||
self.setLabel(**{'font-size': '10pt'})
|
||||
self.setTickFont(_font)
|
||||
self.setWidth(150)
|
||||
# self.chart = chart
|
||||
# accesed normally via
|
||||
# .getAxis('right')
|
||||
self.setWidth(125)
|
||||
|
||||
# XXX: drop for now since it just eats up h space
|
||||
|
||||
|
@ -43,9 +44,20 @@ class PriceAxis(pg.AxisItem):
|
|||
|
||||
|
||||
class DynamicDateAxis(pg.AxisItem):
|
||||
tick_tpl = {'D1': '%Y-%b-%d'}
|
||||
# time formats mapped by seconds between bars
|
||||
tick_tpl = {
|
||||
60*60*24: '%Y-%b-%d',
|
||||
60: '%H:%M',
|
||||
30: '%H:%M:%S',
|
||||
5: '%H:%M:%S',
|
||||
}
|
||||
|
||||
def __init__(self, linked_charts, *args, **kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
linked_charts,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.linked_charts = linked_charts
|
||||
self.setTickFont(_font)
|
||||
|
@ -59,24 +71,25 @@ class DynamicDateAxis(pg.AxisItem):
|
|||
)
|
||||
# self.setHeight(35)
|
||||
|
||||
def tickStrings(self, values, scale, spacing):
|
||||
# if len(values) > 1 or not values:
|
||||
# values = Quotes.time
|
||||
|
||||
# strings = super().tickStrings(values, scale, spacing)
|
||||
s_period = 'D1'
|
||||
strings = []
|
||||
def _indexes_to_timestrs(
|
||||
self,
|
||||
indexes: List[int],
|
||||
) -> List[str]:
|
||||
bars = self.linked_charts.chart._array
|
||||
quotes_count = len(bars) - 1
|
||||
times = bars['time']
|
||||
bars_len = len(bars)
|
||||
delay = times[-1] - times[times != times[-1]][-1]
|
||||
|
||||
for ibar in values:
|
||||
if ibar > quotes_count:
|
||||
return strings
|
||||
dt_tick = fromtimestamp(bars[int(ibar)]['time'])
|
||||
strings.append(
|
||||
dt_tick.strftime(self.tick_tpl[s_period])
|
||||
)
|
||||
return strings
|
||||
epochs = times[list(
|
||||
map(int, filter(lambda i: i < bars_len, indexes))
|
||||
)]
|
||||
# TODO: **don't** have this hard coded shift to EST
|
||||
dts = pd.to_datetime(epochs, unit='s') - 4*pd.offsets.Hour()
|
||||
return dts.strftime(self.tick_tpl[delay])
|
||||
|
||||
|
||||
def tickStrings(self, values: List[float], scale, spacing):
|
||||
return self._indexes_to_timestrs(values)
|
||||
|
||||
|
||||
class AxisLabel(pg.GraphicsObject):
|
||||
|
@ -88,7 +101,7 @@ class AxisLabel(pg.GraphicsObject):
|
|||
def __init__(
|
||||
self,
|
||||
parent=None,
|
||||
digits=1,
|
||||
digits=2,
|
||||
color=None,
|
||||
opacity=1,
|
||||
**kwargs
|
||||
|
@ -128,6 +141,7 @@ class AxisLabel(pg.GraphicsObject):
|
|||
p.drawText(option.rect, self.text_flags, self.label_str)
|
||||
|
||||
# uggggghhhh
|
||||
|
||||
def tick_to_string(self, tick_pos):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -160,24 +174,20 @@ class XAxisLabel(AxisLabel):
|
|||
)
|
||||
# text_flags = _common_text_flags
|
||||
|
||||
def tick_to_string(self, tick_pos):
|
||||
# TODO: change to actual period
|
||||
tpl = self.parent.tick_tpl['D1']
|
||||
bars = self.parent.linked_charts.chart._array
|
||||
if tick_pos > len(bars):
|
||||
return 'Unknown Time'
|
||||
return fromtimestamp(bars[round(tick_pos)]['time']).strftime(tpl)
|
||||
|
||||
def boundingRect(self): # noqa
|
||||
return QtCore.QRectF(0, 0, 145, 40)
|
||||
# TODO: we need to get the parent axe's dimensions transformed
|
||||
# to abs coords to be 100% correct here:
|
||||
# self.parent.boundingRect()
|
||||
return QtCore.QRectF(0, 0, 100, 31)
|
||||
|
||||
def update_label(self, abs_pos, data):
|
||||
# ibar = view_pos.x()
|
||||
# if ibar > self.quotes_count:
|
||||
# return
|
||||
self.label_str = self.tick_to_string(data)
|
||||
def update_label(
|
||||
self,
|
||||
abs_pos: QPointF, # scene coords
|
||||
data: float, # data for text
|
||||
offset: int = 0 # if have margins, k?
|
||||
) -> None:
|
||||
self.label_str = self.parent._indexes_to_timestrs([int(data)])[0]
|
||||
width = self.boundingRect().width()
|
||||
offset = 0 # if have margins
|
||||
new_pos = QPointF(abs_pos.x() - width / 2 - offset, 0)
|
||||
self.setPos(new_pos)
|
||||
|
||||
|
@ -202,10 +212,10 @@ class YAxisLabel(AxisLabel):
|
|||
self,
|
||||
abs_pos: QPointF, # scene coords
|
||||
data: float, # data for text
|
||||
offset: int = 0 # if have margins, k?
|
||||
) -> None:
|
||||
self.label_str = self.tick_to_string(data)
|
||||
height = self.boundingRect().height()
|
||||
offset = 0 # if have margins
|
||||
new_pos = QPointF(0, abs_pos.y() - height / 2 - offset)
|
||||
self.setPos(new_pos)
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ from ._axes import (
|
|||
PriceAxis,
|
||||
)
|
||||
from ._graphics import CrossHair, BarItems
|
||||
from ._style import _xaxis_at, _min_points_to_show
|
||||
from ._axes import YSticky
|
||||
from ._style import _xaxis_at, _min_points_to_show, hcolor
|
||||
from ._source import Symbol
|
||||
from .. import brokers
|
||||
from .. import data
|
||||
|
@ -29,7 +30,7 @@ from .. import fsp
|
|||
log = get_logger(__name__)
|
||||
|
||||
# margins
|
||||
CHART_MARGINS = (0, 0, 10, 3)
|
||||
CHART_MARGINS = (0, 0, 5, 3)
|
||||
|
||||
|
||||
class ChartSpace(QtGui.QWidget):
|
||||
|
@ -42,7 +43,7 @@ class ChartSpace(QtGui.QWidget):
|
|||
self.v_layout = QtGui.QVBoxLayout(self)
|
||||
self.v_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.toolbar_layout = QtGui.QHBoxLayout()
|
||||
self.toolbar_layout.setContentsMargins(10, 10, 15, 0)
|
||||
self.toolbar_layout.setContentsMargins(5, 5, 10, 0)
|
||||
self.h_layout = QtGui.QHBoxLayout()
|
||||
|
||||
# self.init_timeframes_ui()
|
||||
|
@ -225,7 +226,6 @@ class LinkedSplitCharts(QtGui.QWidget):
|
|||
|
||||
cpw.setFrameStyle(QtGui.QFrame.StyledPanel | QtGui.QFrame.Plain)
|
||||
cpw.getPlotItem().setContentsMargins(*CHART_MARGINS)
|
||||
# self.splitter.addWidget(cpw)
|
||||
|
||||
# link chart x-axis to main quotes chart
|
||||
cpw.setXLink(self.chart)
|
||||
|
@ -246,6 +246,9 @@ class LinkedSplitCharts(QtGui.QWidget):
|
|||
# scale split regions
|
||||
self.set_split_sizes()
|
||||
|
||||
# XXX: we need this right?
|
||||
# self.splitter.addWidget(cpw)
|
||||
|
||||
return cpw
|
||||
|
||||
|
||||
|
@ -272,16 +275,19 @@ class ChartPlotWidget(pg.PlotWidget):
|
|||
# the data view we generate graphics from
|
||||
array: np.ndarray,
|
||||
**kwargs,
|
||||
# parent=None,
|
||||
# background='default',
|
||||
# plotItem=None,
|
||||
):
|
||||
"""Configure chart display settings.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
super().__init__(
|
||||
background=hcolor('papas_special'),
|
||||
# parent=None,
|
||||
# plotItem=None,
|
||||
**kwargs
|
||||
)
|
||||
self._array = array # readonly view of data
|
||||
self._graphics = {} # registry of underlying graphics
|
||||
self._labels = {} # registry of underlying graphics
|
||||
self._ysticks = {} # registry of underlying graphics
|
||||
|
||||
# show only right side axes
|
||||
self.hideAxis('left')
|
||||
|
@ -350,6 +356,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
|||
self._graphics[name] = graphics
|
||||
|
||||
# XXX: How to stack labels vertically?
|
||||
# Ogi says: "
|
||||
label = pg.LabelItem(
|
||||
justify='left',
|
||||
size='5pt',
|
||||
|
@ -373,6 +380,8 @@ class ChartPlotWidget(pg.PlotWidget):
|
|||
# show last 50 points on startup
|
||||
self.plotItem.vb.setXRange(xlast - 50, xlast + 50)
|
||||
|
||||
self._add_sticky(name)
|
||||
|
||||
return graphics
|
||||
|
||||
def draw_curve(
|
||||
|
@ -421,6 +430,21 @@ class ChartPlotWidget(pg.PlotWidget):
|
|||
|
||||
return curve
|
||||
|
||||
def _add_sticky(
|
||||
self,
|
||||
name: str,
|
||||
# retreive: Callable[None, np.ndarray],
|
||||
) -> YSticky:
|
||||
# add y-axis "last" value label
|
||||
last = self._ysticks['last'] = YSticky(
|
||||
chart=self,
|
||||
parent=self.getAxis('right'),
|
||||
# digits=0,
|
||||
opacity=1,
|
||||
color=pg.mkPen(hcolor('gray'))
|
||||
)
|
||||
return last
|
||||
|
||||
def update_from_array(
|
||||
self,
|
||||
name: str,
|
||||
|
@ -636,6 +660,10 @@ async def _async_main(
|
|||
loglevel,
|
||||
)
|
||||
|
||||
# update last price sticky
|
||||
last = chart._ysticks['last']
|
||||
last.update_from_data(*chart._array[-1][['index', 'close']])
|
||||
|
||||
# graphics update loop
|
||||
|
||||
async with data.open_feed(
|
||||
|
@ -646,7 +674,7 @@ async def _async_main(
|
|||
|
||||
# wait for a first quote before we start any update tasks
|
||||
quote = await stream.__anext__()
|
||||
print(f'RECEIVED FIRST QUOTE {quote}')
|
||||
log.info(f'RECEIVED FIRST QUOTE {quote}')
|
||||
|
||||
# start graphics tasks after receiving first live quote
|
||||
n.start_soon(add_new_bars, delay, linked_charts)
|
||||
|
@ -675,6 +703,10 @@ async def _async_main(
|
|||
chart.name,
|
||||
chart._array,
|
||||
)
|
||||
# update sticky(s)
|
||||
last = chart._ysticks['last']
|
||||
last.update_from_data(
|
||||
*chart._array[-1][['index', 'close']])
|
||||
chart._set_yrange()
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ from PyQt5 import QtCore, QtGui
|
|||
from PyQt5.QtCore import QLineF
|
||||
|
||||
from .quantdom.utils import timeit
|
||||
from ._style import _xaxis_at # , _tina_mode
|
||||
from ._style import _xaxis_at, hcolor
|
||||
from ._axes import YAxisLabel, XAxisLabel
|
||||
|
||||
|
||||
|
@ -18,17 +18,28 @@ from ._axes import YAxisLabel, XAxisLabel
|
|||
# - checkout pyqtgraph.PlotCurveItem.setCompositionMode
|
||||
|
||||
_mouse_rate_limit = 30
|
||||
_debounce_delay = 10
|
||||
_ch_label_opac = 1
|
||||
|
||||
|
||||
class CrossHair(pg.GraphicsObject):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
linkedsplitcharts: 'LinkedSplitCharts',
|
||||
linkedsplitcharts: 'LinkedSplitCharts', # noqa
|
||||
digits: int = 0
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.pen = pg.mkPen('#a9a9a9') # gray?
|
||||
# XXX: not sure why these are instance variables?
|
||||
# It's not like we can change them on the fly..?
|
||||
self.pen = pg.mkPen(
|
||||
color=hcolor('default'),
|
||||
style=QtCore.Qt.DashLine,
|
||||
)
|
||||
self.lines_pen = pg.mkPen(
|
||||
color='#a9a9a9', # gray?
|
||||
style=QtCore.Qt.DashLine,
|
||||
)
|
||||
self.lsc = linkedsplitcharts
|
||||
self.graphics = {}
|
||||
self.plots = []
|
||||
|
@ -42,12 +53,13 @@ class CrossHair(pg.GraphicsObject):
|
|||
) -> None:
|
||||
# add ``pg.graphicsItems.InfiniteLine``s
|
||||
# vertical and horizonal lines and a y-axis label
|
||||
vl = plot.addLine(x=0, pen=self.pen, movable=False)
|
||||
hl = plot.addLine(y=0, pen=self.pen, movable=False)
|
||||
vl = plot.addLine(x=0, pen=self.lines_pen, movable=False)
|
||||
hl = plot.addLine(y=0, pen=self.lines_pen, movable=False)
|
||||
yl = YAxisLabel(
|
||||
parent=plot.getAxis('right'),
|
||||
digits=digits or self.digits,
|
||||
opacity=1
|
||||
opacity=_ch_label_opac,
|
||||
color=self.pen,
|
||||
)
|
||||
|
||||
# TODO: checkout what ``.sigDelayed`` can be used for
|
||||
|
@ -55,17 +67,20 @@ class CrossHair(pg.GraphicsObject):
|
|||
px_moved = pg.SignalProxy(
|
||||
plot.scene().sigMouseMoved,
|
||||
rateLimit=_mouse_rate_limit,
|
||||
slot=self.mouseMoved
|
||||
slot=self.mouseMoved,
|
||||
delay=_debounce_delay,
|
||||
)
|
||||
px_enter = pg.SignalProxy(
|
||||
plot.sig_mouse_enter,
|
||||
rateLimit=_mouse_rate_limit,
|
||||
slot=lambda: self.mouseAction('Enter', plot),
|
||||
delay=_debounce_delay,
|
||||
)
|
||||
px_leave = pg.SignalProxy(
|
||||
plot.sig_mouse_leave,
|
||||
rateLimit=_mouse_rate_limit,
|
||||
slot=lambda: self.mouseAction('Leave', plot),
|
||||
delay=_debounce_delay,
|
||||
)
|
||||
self.graphics[plot] = {
|
||||
'vl': vl,
|
||||
|
@ -75,18 +90,16 @@ class CrossHair(pg.GraphicsObject):
|
|||
}
|
||||
self.plots.append(plot)
|
||||
|
||||
# determine where to place x-axis label
|
||||
if _xaxis_at == 'bottom':
|
||||
# place below the last plot
|
||||
self.xaxis_label = XAxisLabel(
|
||||
parent=self.plots[-1].getAxis('bottom'),
|
||||
opacity=1
|
||||
)
|
||||
else:
|
||||
# keep x-axis right below main chart
|
||||
first = self.plots[0]
|
||||
xaxis = first.getAxis('bottom')
|
||||
self.xaxis_label = XAxisLabel(parent=xaxis, opacity=1)
|
||||
# Determine where to place x-axis label.
|
||||
# Place below the last plot by default, ow
|
||||
# keep x-axis right below main chart
|
||||
plot_index = -1 if _xaxis_at == 'bottom' else 0
|
||||
|
||||
self.xaxis_label = XAxisLabel(
|
||||
parent=self.plots[plot_index].getAxis('bottom'),
|
||||
opacity=_ch_label_opac,
|
||||
color=self.pen,
|
||||
)
|
||||
|
||||
def mouseAction(self, action, plot): # noqa
|
||||
if action == 'Enter':
|
||||
|
@ -100,7 +113,10 @@ class CrossHair(pg.GraphicsObject):
|
|||
self.graphics[plot]['yl'].hide()
|
||||
self.active_plot = None
|
||||
|
||||
def mouseMoved(self, evt): # noqa
|
||||
def mouseMoved(
|
||||
self,
|
||||
evt: 'Tuple[QMouseEvent]', # noqa
|
||||
) -> None: # noqa
|
||||
"""Update horizonal and vertical lines when mouse moves inside
|
||||
either the main chart or any indicator subplot.
|
||||
"""
|
||||
|
@ -108,6 +124,7 @@ class CrossHair(pg.GraphicsObject):
|
|||
|
||||
# find position inside active plot
|
||||
try:
|
||||
# map to view coordinate system
|
||||
mouse_point = self.active_plot.mapToView(pos)
|
||||
except AttributeError:
|
||||
# mouse was not on active plot
|
||||
|
@ -120,7 +137,7 @@ class CrossHair(pg.GraphicsObject):
|
|||
self.graphics[plot]['hl'].setY(y)
|
||||
|
||||
self.graphics[self.active_plot]['yl'].update_label(
|
||||
evt_post=pos, point_view=mouse_point
|
||||
abs_pos=pos, data=y
|
||||
)
|
||||
for plot, opts in self.graphics.items():
|
||||
# move the vertical line to the current x
|
||||
|
@ -131,8 +148,8 @@ class CrossHair(pg.GraphicsObject):
|
|||
|
||||
# update the label on the bottom of the crosshair
|
||||
self.xaxis_label.update_label(
|
||||
evt_post=pos,
|
||||
point_view=mouse_point
|
||||
abs_pos=pos,
|
||||
data=x
|
||||
)
|
||||
|
||||
def boundingRect(self):
|
||||
|
@ -226,7 +243,7 @@ class BarItems(pg.GraphicsObject):
|
|||
|
||||
# 0.5 is no overlap between arms, 1.0 is full overlap
|
||||
w: float = 0.43
|
||||
bull_pen = pg.mkPen('#808080')
|
||||
bull_pen = pg.mkPen(hcolor('gray'))
|
||||
|
||||
# XXX: tina mode, see below
|
||||
# bull_brush = pg.mkPen('#00cc00')
|
||||
|
@ -244,6 +261,9 @@ class BarItems(pg.GraphicsObject):
|
|||
# track the current length of drawable lines within the larger array
|
||||
self.index: int = 0
|
||||
|
||||
def last_value(self) -> QLineF:
|
||||
return self.lines[self.index - 1]['rarm']
|
||||
|
||||
@timeit
|
||||
def draw_from_data(
|
||||
self,
|
||||
|
|
|
@ -3,6 +3,7 @@ Qt UI styling.
|
|||
"""
|
||||
import pyqtgraph as pg
|
||||
from PyQt5 import QtGui
|
||||
from qdarkstyle.palette import DarkPalette
|
||||
|
||||
|
||||
# chart-wide font
|
||||
|
@ -27,3 +28,36 @@ def enable_tina_mode() -> None:
|
|||
"""
|
||||
# white background (for tinas like our pal xb)
|
||||
pg.setConfigOption('background', 'w')
|
||||
|
||||
|
||||
def hcolor(name: str) -> str:
|
||||
"""Hex color codes by hipster speak.
|
||||
"""
|
||||
return {
|
||||
# lives matter
|
||||
'black': '#000000',
|
||||
'erie_black': '#1B1B1B',
|
||||
'licorice': '#1A1110',
|
||||
'papas_special': '#06070c',
|
||||
|
||||
# fifty shades
|
||||
'gray': '#808080', # like the kick
|
||||
'jet': '#343434',
|
||||
'charcoal': '#36454F',
|
||||
|
||||
# palette
|
||||
'default': DarkPalette.COLOR_BACKGROUND_NORMAL,
|
||||
|
||||
'white': '#ffffff', # for tinas and sunbathers
|
||||
|
||||
# blue zone
|
||||
'dad_blue': '#326693', # like his shirt
|
||||
'vwap_blue': '#0582fb',
|
||||
'dodger_blue': '#1e90ff', # like the team?
|
||||
'panasonic_blue': '#0040be', # from japan
|
||||
|
||||
# traditional
|
||||
'tina_green': '#00cc00',
|
||||
'tina_red': '#fa0000',
|
||||
|
||||
}[name]
|
||||
|
|
Loading…
Reference in New Issue